Merge commit '9bdcff7bdf72dd69f43c2b3d6fc20474130008c0' into remote
Conflicts:
AndroidManifest.xml
InCallUI/src/com/android/incallui/Call.java
InCallUI/src/com/android/incallui/CallButtonPresenter.java
InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java
InCallUI/src/com/android/incallui/StatusBarNotifier.java
InCallUI/src/com/android/incallui/VideoCallPresenter.java
Change-Id: Ice78377f619bcd740170eff4393102329319f43d
diff --git a/Android.mk b/Android.mk
old mode 100644
new mode 100755
index 975d396..2cb292a
--- a/Android.mk
+++ b/Android.mk
@@ -31,12 +31,18 @@
$(phone_common_dir)/src-N
LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
+LOCAL_SRC_FILES += src/org/codeaurora/presenceserv/IPresenceService.aidl \
+ src/org/codeaurora/presenceserv/IPresenceServiceCB.aidl
LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs)) \
$(support_library_root_dir)/v7/cardview/res \
$(support_library_root_dir)/v7/recyclerview/res \
$(support_library_root_dir)/v7/appcompat/res \
$(support_library_root_dir)/design/res
+LOCAL_JAVA_LIBRARIES := telephony-common \
+ telephony-ext \
+ ims-common
+
LOCAL_AAPT_FLAGS := \
--auto-add-overlay \
--extra-packages android.support.v7.appcompat \
@@ -57,7 +63,10 @@
android-support-design \
com.android.vcard \
guava \
- libphonenumber
+ libphonenumber \
+ ims-ext-common \
+ phonebook_wrapper \
+ telephony-common
LOCAL_PACKAGE_NAME := Dialer
LOCAL_CERTIFICATE := shared
@@ -65,7 +74,7 @@
LOCAL_PROGUARD_FLAG_FILES := proguard.flags $(incallui_dir)/proguard.flags
-LOCAL_SDK_VERSION := current
+# LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6528fdb..7bbd37f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -39,13 +39,17 @@
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="com.qualcomm.permission.USE_PHONE_SERVICE" />
+ <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
@@ -56,6 +60,10 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<!-- This tells the activity manager to not delay any of our activity
start requests, even if they happen immediately after the user
presses home. -->
@@ -73,6 +81,7 @@
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIBXgtCEKQ6W0PXVnW-ZVia2KmlV2AxsTw3GjAeQ" />
+ <uses-library android:name="com.qualcomm.qti.smartsearch" android:required="false" />
<!-- The entrance point for Phone UI.
stateAlwaysHidden is set to suppress keyboard show up on
dialpad screen. -->
@@ -163,6 +172,7 @@
</activity>
<activity android:name="com.android.dialer.calllog.CallLogActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/call_log_activity_title"
android:theme="@style/DialtactsThemeWithoutActionBarOverlay"
android:icon="@mipmap/ic_launcher_phone">
@@ -230,6 +240,12 @@
android:theme="@style/BackgroundOnlyTheme"
android:exported="false"/>
+ <activity android:name=".VideoCallWelcomeActivity"
+ android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:exported="false" />
+
<!-- vCard related -->
<activity android:name="com.android.contacts.common.vcard.ImportVCardActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
@@ -309,10 +325,10 @@
fullScreenIntent of a notification (for incoming calls.) -->
<activity android:name="com.android.incallui.InCallActivity"
android:theme="@style/Theme.InCallScreen"
- android:label="@string/phoneAppLabel"
+ android:label=""
android:excludeFromRecents="true"
android:launchMode="singleInstance"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden"
+ android:configChanges="smallestScreenSize|screenLayout|keyboardHidden"
android:exported="false"
android:screenOrientation="nosensor"
android:directBootAware="true"
@@ -360,5 +376,13 @@
android:exported="false"
android:multiprocess="false"
/>
+
+ <activity android:name=".SpeedDialListActivity"
+ android:theme="@style/SpeedDialtactsTheme"
+ android:label="@string/speed_dial_settings" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/InCallUI/res/drawable-hdpi/btn_start_record.png b/InCallUI/res/drawable-hdpi/btn_start_record.png
new file mode 100644
index 0000000..f43a572
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/btn_start_record.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/btn_stop_record.png b/InCallUI/res/drawable-hdpi/btn_stop_record.png
new file mode 100644
index 0000000..c1a68f7
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/btn_stop_record.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_hd2_24dp.png b/InCallUI/res/drawable-hdpi/ic_hd2_24dp.png
new file mode 100644
index 0000000..b4cbe6d
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_hd2_24dp.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_recording_indicator.png b/InCallUI/res/drawable-hdpi/ic_recording_indicator.png
new file mode 100644
index 0000000..a98b837
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_recording_indicator.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_big.9.png b/InCallUI/res/drawable-hdpi/ic_zoom_big.9.png
new file mode 100644
index 0000000..8c6cdea
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_big.9.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_big_dark.9.png b/InCallUI/res/drawable-hdpi/ic_zoom_big_dark.9.png
new file mode 100644
index 0000000..63ba20e
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_big_dark.9.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_dark.png b/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_dark.png
new file mode 100644
index 0000000..89b5f15
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_light.png b/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_light.png
new file mode 100644
index 0000000..9751ca3
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_light.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_dark.png b/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_dark.png
new file mode 100644
index 0000000..f4a2589
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_light.png b/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_light.png
new file mode 100644
index 0000000..ba094ac
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_light.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_slider.png b/InCallUI/res/drawable-hdpi/ic_zoom_slider.png
new file mode 100644
index 0000000..8427e4d
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_slider.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/vb_active.png b/InCallUI/res/drawable-hdpi/vb_active.png
new file mode 100644
index 0000000..2a82ca8
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/vb_active.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/vb_disable.png b/InCallUI/res/drawable-hdpi/vb_disable.png
new file mode 100644
index 0000000..c2710b0
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/vb_disable.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/vb_normal.png b/InCallUI/res/drawable-hdpi/vb_normal.png
new file mode 100644
index 0000000..b78c30e
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/vb_normal.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/btn_start_record.png b/InCallUI/res/drawable-mdpi/btn_start_record.png
new file mode 100644
index 0000000..e340a94
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/btn_start_record.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/btn_stop_record.png b/InCallUI/res/drawable-mdpi/btn_stop_record.png
new file mode 100644
index 0000000..7a21c2f
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/btn_stop_record.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_hd2_24dp.png b/InCallUI/res/drawable-mdpi/ic_hd2_24dp.png
new file mode 100644
index 0000000..f4bc997
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_hd2_24dp.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_recording_indicator.png b/InCallUI/res/drawable-mdpi/ic_recording_indicator.png
new file mode 100644
index 0000000..2a4c19e
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_recording_indicator.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_big.9.png b/InCallUI/res/drawable-mdpi/ic_zoom_big.9.png
new file mode 100644
index 0000000..f5e31b4
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_big.9.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_big_dark.9.png b/InCallUI/res/drawable-mdpi/ic_zoom_big_dark.9.png
new file mode 100644
index 0000000..919db3f
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_big_dark.9.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_dark.png b/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_dark.png
new file mode 100644
index 0000000..4f33278
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_light.png b/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_light.png
new file mode 100644
index 0000000..3238a39
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_light.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_dark.png b/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_dark.png
new file mode 100644
index 0000000..2631894
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_light.png b/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_light.png
new file mode 100644
index 0000000..8113dab
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_light.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_slider.png b/InCallUI/res/drawable-mdpi/ic_zoom_slider.png
new file mode 100644
index 0000000..16aacf1
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_slider.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/vb_active.png b/InCallUI/res/drawable-mdpi/vb_active.png
new file mode 100644
index 0000000..a4d5261
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/vb_active.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/vb_disable.png b/InCallUI/res/drawable-mdpi/vb_disable.png
new file mode 100644
index 0000000..e2e4a7d
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/vb_disable.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/vb_normal.png b/InCallUI/res/drawable-mdpi/vb_normal.png
new file mode 100644
index 0000000..702f619
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/vb_normal.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/btn_start_record.png b/InCallUI/res/drawable-xhdpi/btn_start_record.png
new file mode 100644
index 0000000..310b5a5
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/btn_start_record.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/btn_stop_record.png b/InCallUI/res/drawable-xhdpi/btn_stop_record.png
new file mode 100644
index 0000000..a2f735a
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/btn_stop_record.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/ic_hd2_24dp.png b/InCallUI/res/drawable-xhdpi/ic_hd2_24dp.png
new file mode 100644
index 0000000..4ac0961
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/ic_hd2_24dp.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/ic_recording_indicator.png b/InCallUI/res/drawable-xhdpi/ic_recording_indicator.png
new file mode 100644
index 0000000..33e6875
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/ic_recording_indicator.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/vb_active.png b/InCallUI/res/drawable-xhdpi/vb_active.png
new file mode 100644
index 0000000..78f80b9
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/vb_active.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/vb_disable.png b/InCallUI/res/drawable-xhdpi/vb_disable.png
new file mode 100644
index 0000000..777b36f
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/vb_disable.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/vb_normal.png b/InCallUI/res/drawable-xhdpi/vb_normal.png
new file mode 100644
index 0000000..7b5468c
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/vb_normal.png
Binary files differ
diff --git a/InCallUI/res/drawable-xxhdpi/ic_hd2_24dp.png b/InCallUI/res/drawable-xxhdpi/ic_hd2_24dp.png
new file mode 100644
index 0000000..f1e6f1f
--- /dev/null
+++ b/InCallUI/res/drawable-xxhdpi/ic_hd2_24dp.png
Binary files differ
diff --git a/InCallUI/res/drawable/ic_zoom_in.xml b/InCallUI/res/drawable/ic_zoom_in.xml
new file mode 100644
index 0000000..d441630
--- /dev/null
+++ b/InCallUI/res/drawable/ic_zoom_in.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012 - 2015, The Linux Foundation. All rights reserved.
+ Not a Contribution, Apache license notifications and license are retained
+ for attribution purposes only.
+
+ Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_activated="true" android:drawable="@drawable/ic_zoom_in_holo_light" />
+ <item android:drawable="@drawable/ic_zoom_in_holo_dark" />
+</selector>
+
diff --git a/InCallUI/res/drawable/ic_zoom_out.xml b/InCallUI/res/drawable/ic_zoom_out.xml
new file mode 100644
index 0000000..1211c33
--- /dev/null
+++ b/InCallUI/res/drawable/ic_zoom_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012 - 2105, The Linux Foundation. All rights reserved.
+ Not a Contribution, Apache license notifications and license are retained
+ for attribution purposes only.
+
+ Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_activated="true" android:drawable="@drawable/ic_zoom_out_holo_light" />
+ <item android:drawable="@drawable/ic_zoom_out_holo_dark" />
+</selector>
+
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video.xml b/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video.xml
new file mode 100644
index 0000000..5f86565
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
+ ~ Not a Contribution.
+ ~ 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
+ -->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="false"
+ android:drawable="@drawable/qti_ic_lockscreen_answer_rx_video_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/qti_ic_lockscreen_answer_rx_video_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/qti_ic_lockscreen_answer_rx_video_activated_layer" />
+</selector>
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video_activated_layer.xml b/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video_activated_layer.xml
new file mode 100644
index 0000000..24b14cc
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video_activated_layer.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
+ ~ Not a Contribution.
+ ~ 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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/fab_red" />
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_rx_videocam"
+ android:tint="@color/glowpad_widget_active_color"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video_normal_layer.xml b/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video_normal_layer.xml
new file mode 100644
index 0000000..affd928
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_answer_rx_video_normal_layer.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
+ ~ Not a Contribution.
+ ~ 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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- A fake circle to fix the size of this layer asset. -->
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00000000"/>
+ <size
+ android:width="@dimen/incoming_call_widget_circle_size"
+ android:height="@dimen/incoming_call_widget_circle_size" />
+ </shape>
+ </item>
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_rx_videocam"
+ android:tint="@color/glowpad_call_widget_normal_tint"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video.xml b/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video.xml
new file mode 100644
index 0000000..258c1be
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
+ ~ Not a Contribution.
+ ~ 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
+ -->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="false"
+ android:drawable="@drawable/qti_ic_lockscreen_answer_tx_video_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/qti_ic_lockscreen_answer_tx_video_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/qti_ic_lockscreen_answer_tx_video_activated_layer" />
+</selector>
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video_activated_layer.xml b/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video_activated_layer.xml
new file mode 100644
index 0000000..42d09f9
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video_activated_layer.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
+ ~ Not a Contribution.
+ ~ 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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/fab_green" />
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_tx_videocam"
+ android:tint="@color/glowpad_widget_active_color"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video_normal_layer.xml b/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video_normal_layer.xml
new file mode 100644
index 0000000..dc208db
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_answer_tx_video_normal_layer.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
+ ~ Not a Contribution.
+ ~ 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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- A fake circle to fix the size of this layer asset. -->
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00000000"/>
+ <size
+ android:width="@dimen/incoming_call_widget_circle_size"
+ android:height="@dimen/incoming_call_widget_circle_size" />
+ </shape>
+ </item>
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_tx_videocam"
+ android:tint="@color/glowpad_call_widget_normal_tint"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_deflect.xml b/InCallUI/res/drawable/qti_ic_lockscreen_deflect.xml
new file mode 100644
index 0000000..75f6d23
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_deflect.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="false"
+ android:drawable="@drawable/qti_ic_lockscreen_deflect_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/qti_ic_lockscreen_deflect_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/qti_ic_lockscreen_deflect_activated_layer" />
+</selector>
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_deflect_activated_layer.xml b/InCallUI/res/drawable/qti_ic_lockscreen_deflect_activated_layer.xml
new file mode 100644
index 0000000..f22b87e
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_deflect_activated_layer.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/fab_green"/>
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/fab_ic_call"
+ android:tint="@color/glowpad_widget_active_color"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/qti_ic_lockscreen_deflect_normal_layer.xml b/InCallUI/res/drawable/qti_ic_lockscreen_deflect_normal_layer.xml
new file mode 100644
index 0000000..31b884f
--- /dev/null
+++ b/InCallUI/res/drawable/qti_ic_lockscreen_deflect_normal_layer.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- A fake circle to fix the size of this layer asset. -->
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00000000"/>
+ <size
+ android:width="@dimen/incoming_call_widget_circle_size"
+ android:height="@dimen/incoming_call_widget_circle_size" />
+ </shape>
+ </item>
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/fab_ic_call"
+ android:tint="@color/glowpad_call_widget_normal_tint"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/vowifi_in_call_fair.xml b/InCallUI/res/drawable/vowifi_in_call_fair.xml
new file mode 100644
index 0000000..6cd5f30
--- /dev/null
+++ b/InCallUI/res/drawable/vowifi_in_call_fair.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<vector android:autoMirrored="true" android:height="24dp"
+ android:viewportHeight="588.0" android:viewportWidth="731.0"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FFFFFF" android:pathData="M143.4,436.09998c-90.1,-91.2 -135,-209 -133.6,-322.6c0.2,-16.1 13.3,-29.2 29.4,-29.4l103.1,-1.3c8.5,-0.1 15.6,6.3 16.3,14.8l11.6,134L114.7,294c-2.9,3.2 -3.4,7.9 -1.3,11.7c18.4,33.4 41.4,64.2 68.9,92l0.7,0.6c27.5,27.8 58,51.3 91.2,70c3.7,2.1 8.4,1.7 11.7,-1.2l63.1,-54.7l133.9,13.3c8.4,0.8 14.8,8 14.6,16.5l-2.6,103c-0.4,16.1 -13.6,29.1 -29.7,29.1c-113.7,0 -230.8,-46.4 -320.9,-137.7L143.4,436.09998z"/>
+ <path android:fillAlpha="0.53" android:fillColor="#FFFFFF" android:pathData="M636.2,171.20001l22.3,-25l25.1,-28.1l34.1,-38.3c-71.4,-39.9 -163.3,-64 -263.6,-64s-192.2,24.1 -263.6,64l33.3,38.6l24.5,28.4L270,172c55.4,-18.6 117.9,-29.3 184.1,-29.3C519.6,142.6 581.3,153 636.2,171.20001z"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M636.2,171.20001c-54.9,-18.2 -116.6,-28.6 -182.1,-28.6c-66.3,0 -128.7,10.7 -184.1,29.3l179.7,208.3L636.2,171.20001z"/>
+</vector>
diff --git a/InCallUI/res/drawable/vowifi_in_call_good.xml b/InCallUI/res/drawable/vowifi_in_call_good.xml
new file mode 100644
index 0000000..52d397c
--- /dev/null
+++ b/InCallUI/res/drawable/vowifi_in_call_good.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<vector android:autoMirrored="true" android:height="24dp"
+ android:viewportHeight="588.0" android:viewportWidth="731.0"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FFFFFF" android:pathData="M144.4,436.09998c-90.1,-91.2 -135,-209 -133.6,-322.6c0.2,-16.1 13.3,-29.2 29.4,-29.4l103.1,-1.3c8.5,-0.1 15.6,6.3 16.3,14.8l11.6,134L115.7,294c-2.9,3.2 -3.4,7.9 -1.3,11.7c18.4,33.4 41.4,64.2 68.9,92l0.7,0.6c27.5,27.8 58,51.3 91.2,70c3.7,2.1 8.4,1.7 11.7,-1.2l63.1,-54.7l133.9,13.3c8.4,0.8 14.8,8 14.6,16.5l-2.6,103c-0.4,16.1 -13.6,29.1 -29.7,29.1c-113.7,0 -230.8,-46.4 -320.9,-137.7L144.4,436.09998z"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M454.90002,15.5c-100.3,0 -192.2,24.1 -263.6,64l33.3,38.6l24.5,28.4L450.5,380l208.8,-234.1l25.1,-28.1l34.1,-38.3C647.1,39.600006 555.2,15.5 454.90002,15.5z"/>
+</vector>
diff --git a/InCallUI/res/drawable/vowifi_in_call_poor.xml b/InCallUI/res/drawable/vowifi_in_call_poor.xml
new file mode 100644
index 0000000..663ce90
--- /dev/null
+++ b/InCallUI/res/drawable/vowifi_in_call_poor.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<vector android:autoMirrored="true" android:height="24dp"
+ android:viewportHeight="588.0" android:viewportWidth="731.0"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FFFFFF" android:pathData="M143.4,435.09998c-90.1,-91.2 -135,-209 -133.6,-322.6c0.2,-16.1 13.3,-29.2 29.4,-29.4l103.1,-1.3c8.5,-0.1 15.6,6.3 16.3,14.8l11.6,134L114.7,293c-2.9,3.2 -3.4,7.9 -1.3,11.7c18.4,33.4 41.4,64.2 68.9,92l0.7,0.6c27.5,27.8 58,51.3 91.2,70c3.7,2.1 8.4,1.7 11.7,-1.2l63.1,-54.7l133.9,13.3c8.4,0.8 14.8,8 14.6,16.5l-2.6,103c-0.4,16.1 -13.6,29.1 -29.7,29.1c-113.7,0 -230.8,-46.4 -320.9,-137.7L143.4,435.09998z"/>
+ <path android:fillAlpha="0.53" android:fillColor="#FFFFFF" android:pathData="M537.3,281.4l121.5,-136.2l25.1,-28.1l34.1,-38.3c-71.4,-39.9 -163.3,-64 -263.6,-64s-192.2,24.1 -263.6,64l33.3,38.6l24.5,28.4l117.6,136.3c28.4,-4.2 57.8,-6.6 88.2,-6.6C482.90002,275.6 510.5,277.7 537.3,281.4z"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M537.3,281.4c-26.8,-3.7 -54.4,-5.8 -82.9,-5.8c-30.3,0 -59.7,2.4 -88.2,6.6l83.7,97.1L537.3,281.4z"/>
+</vector>
diff --git a/InCallUI/res/drawable/zoom_slider_bar.xml b/InCallUI/res/drawable/zoom_slider_bar.xml
new file mode 100644
index 0000000..923e4ba
--- /dev/null
+++ b/InCallUI/res/drawable/zoom_slider_bar.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012 - 2015, The Linux Foundation. All rights reserved.
+ Not a Contribution, Apache license notifications and license are retained
+ for attribution purposes only.
+
+ Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_activated="true" android:drawable="@drawable/ic_zoom_big" />
+ <item android:drawable="@drawable/ic_zoom_big_dark" />
+</selector>
+
diff --git a/InCallUI/res/layout-h400dp/call_card_fragment.xml b/InCallUI/res/layout-h400dp/call_card_fragment.xml
index 2ef6e52..ccff385 100644
--- a/InCallUI/res/layout-h400dp/call_card_fragment.xml
+++ b/InCallUI/res/layout-h400dp/call_card_fragment.xml
@@ -116,17 +116,63 @@
<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.Material.ProgressBar"
- android:layout_gravity="center"
+ android:layout_gravity="left|center_vertical"
android:layout_width="48dp"
android:layout_height="48dp"
android:indeterminate="true" />
</FrameLayout>
-
- <include layout="@layout/manage_conference_call_button"
+ <LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_centerHorizontal="true"
+ android:layout_below="@id/primary_call_info_container">
+
+ <include layout="@layout/manage_conference_call_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="5dp"
+ android:layout_alignParentBottom="true"/>
+
+ <!-- Volume boost and Volume enhancements in-call UI -->
+ <ImageButton android:id="@+id/volumeBoost"
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:visibility="gone"
+ android:soundEffectsEnabled="false"
+ android:background="@drawable/vb_normal"/>
+
+ </LinearLayout>
+
+ <!-- Call recorder infor -->
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true">
+
+ <TextView android:id="@+id/recordingIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="24dp"
+ android:drawableRight="@drawable/ic_recording_indicator"
+ android:paddingBottom="119dp"
+ android:visibility="invisible"/>
+ <TextView android:id="@+id/recordingTime"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_toRightOf="@id/recordingIcon"
+ android:paddingBottom="120dp"
+ android:singleLine="true"
+ android:text="@string/recording_time_text"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/incall_call_banner_text_color"
+ android:visibility="invisible"/>
+ </RelativeLayout>
<!-- Placeholder for various fragments that are added dynamically underneath the caller info. -->
<FrameLayout
@@ -169,4 +215,4 @@
</LinearLayout>
<!-- Secondary "Call info" block, for the background ("on hold") call. -->
<include layout="@layout/secondary_call_info" />
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/InCallUI/res/layout-w500dp-land/call_card_fragment.xml b/InCallUI/res/layout-w500dp-land/call_card_fragment.xml
index c71cf07..049f41f 100644
--- a/InCallUI/res/layout-w500dp-land/call_card_fragment.xml
+++ b/InCallUI/res/layout-w500dp-land/call_card_fragment.xml
@@ -16,47 +16,13 @@
~ limitations under the License
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
-
- <LinearLayout
- android:id="@+id/primary_call_info_container"
- android:layout_centerVertical="true"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="vertical"
- android:elevation="@dimen/primary_call_elevation"
- android:background="@drawable/rounded_call_card_background"
- android:paddingTop="@dimen/call_banner_primary_call_container_top_padding"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:alpha="0.9"
- android:layout_margin="10dp">
-
- <include layout="@layout/primary_call_info" />
-
- <fragment android:name="com.android.incallui.CallButtonFragment"
- android:id="@+id/callButtonFragment"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|center_horizontal"
- android:layout_marginBottom="@dimen/call_buttons_bottom_margin" />
-
- <!-- Secondary "Call info" block, for the background ("on hold") call. -->
- <include layout="@layout/secondary_call_info"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom" />
-
- </LinearLayout>
+ android:layout_height="match_parent">
<FrameLayout
android:layout_height="match_parent"
- android:layout_width="0dp"
- android:layout_weight="1">
+ android:layout_width="match_parent">
<FrameLayout
android:layout_height="match_parent"
@@ -96,11 +62,6 @@
</FrameLayout>
- <include layout="@layout/manage_conference_call_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignTop="@id/photoLarge" />
-
<!-- Progress spinner, useful for indicating pending operations such as upgrade to video. -->
<FrameLayout
android:id="@+id/progressSpinner"
@@ -114,7 +75,7 @@
<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.Material.ProgressBar"
- android:layout_gravity="center"
+ android:layout_gravity="left|center_vertical"
android:layout_width="48dp"
android:layout_height="48dp"
android:indeterminate="true" />
@@ -127,32 +88,121 @@
android:id="@+id/videoCallFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ </FrameLayout>
- <!-- Placeholder for the dialpad which is replaced with the dialpad fragment when shown. -->
- <FrameLayout
- android:id="@+id/answer_and_dialpad_container"
- android:layout_gravity="bottom"
+ <!-- Call recorder infor -->
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true">
+
+ <TextView android:id="@+id/recordingIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="24dp"
+ android:drawableRight="@drawable/ic_recording_indicator"
+ android:paddingBottom="119dp"
+ android:visibility="invisible"/>
+
+ <TextView android:id="@+id/recordingTime"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_toRightOf="@id/recordingIcon"
+ android:paddingBottom="120dp"
+ android:singleLine="true"
+ android:text="@string/recording_time_text"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/incall_call_banner_text_color"
+ android:visibility="invisible"/>
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_centerHorizontal="true">
+
+ <include layout="@layout/manage_conference_call_button"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="wrap_content"
+ android:elevation="5dp"
+ android:layout_alignParentBottom="true"/>
+
+ <!-- Volume boost and Volume enhancements in-call UI -->
+ <ImageButton android:id="@+id/volumeBoost"
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:visibility="gone"
+ android:soundEffectsEnabled="false"
+ android:background="@drawable/vb_normal"/>
+ </LinearLayout>
+
+ <!-- Secondary "Call info" block, for the background ("on hold") call. -->
+ <include layout="@layout/secondary_call_info"
+ android:id="@+id/secondary_call_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true" />
+
+ <LinearLayout
+ android:id="@+id/primary_call_info_container"
+ android:layout_alignParentStart="true"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_above="@id/secondary_call_info"
+ android:orientation="vertical"
+ android:elevation="@dimen/primary_call_elevation"
+ android:background="@drawable/rounded_call_card_background"
+ android:paddingTop="@dimen/call_banner_primary_call_container_top_padding"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:alpha="0.9"
+ android:layout_margin="10dp">
+
+ <include layout="@layout/primary_call_info" />
<FrameLayout
- android:id="@+id/floating_end_call_action_button_container"
- android:layout_width="@dimen/end_call_floating_action_button_diameter"
- android:layout_height="@dimen/end_call_floating_action_button_diameter"
- android:background="@drawable/fab_red"
- android:layout_gravity="bottom|center_horizontal"
- android:layout_marginBottom="@dimen/end_call_button_margin_bottom">
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" >
- <ImageButton android:id="@+id/floating_end_call_action_button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/end_call_background"
- android:src="@drawable/fab_ic_end_call"
- android:scaleType="center"
- android:contentDescription="@string/onscreenEndCallText" />
+ <fragment android:name="com.android.incallui.CallButtonFragment"
+ android:id="@+id/callButtonFragment"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom" />
</FrameLayout>
- </FrameLayout>
+ </LinearLayout>
-</LinearLayout>
+ <!-- Placeholder for the dialpad which is replaced with the dialpad fragment when shown. -->
+ <FrameLayout
+ android:id="@+id/answer_and_dialpad_container"
+ android:layout_toEndOf="@id/primary_call_info_container"
+ android:layout_gravity="end|center_vertical"
+ android:layout_alignParentEnd="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <FrameLayout
+ android:id="@+id/floating_end_call_action_button_container"
+ android:layout_width="@dimen/end_call_floating_action_button_diameter"
+ android:layout_height="@dimen/end_call_floating_action_button_diameter"
+ android:background="@drawable/fab_red"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentBottom="true"
+ android:layout_marginRight="@dimen/end_call_button_margin_right"
+ android:layout_marginBottom="@dimen/end_call_button_margin_bottom">
+ <ImageButton android:id="@+id/floating_end_call_action_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/end_call_background"
+ android:src="@drawable/fab_ic_end_call"
+ android:scaleType="center"
+ android:contentDescription="@string/onscreenEndCallText" />
+ </FrameLayout>
+
+</RelativeLayout>
diff --git a/InCallUI/res/layout/call_button_fragment.xml b/InCallUI/res/layout/call_button_fragment.xml
index 802e3de..9ed4e56 100644
--- a/InCallUI/res/layout/call_button_fragment.xml
+++ b/InCallUI/res/layout/call_button_fragment.xml
@@ -151,6 +151,37 @@
android:background="@drawable/btn_merge"
android:contentDescription="@string/onscreenMergeCallsText"
android:visibility="gone" />
+ <!-- "Add Participant" -->
+ <ImageButton android:id="@+id/addParticipant"
+ style="@style/InCallButton"
+ android:background="@drawable/btn_addparticipant"
+ android:contentDescription="@string/onscreenAddParticipant"
+ android:visibility="gone" />
+
+ <!-- "Blind transfer" -->
+ <ImageButton android:id="@+id/blindTransfer"
+ style="@style/InCallButton"
+ android:background="@drawable/btn_add"
+ android:contentDescription="@string/qti_ims_onscreenBlindTransfer"
+ android:visibility="gone" />
+ <!-- "Assured transfer" -->
+ <ImageButton android:id="@+id/assuredTransfer"
+ style="@style/InCallButton"
+ android:background="@drawable/btn_add"
+ android:contentDescription="@string/qti_ims_onscreenAssuredTransfer"
+ android:visibility="gone" />
+ <!-- "Consultative transfer" -->
+ <ImageButton android:id="@+id/consultativeTransfer"
+ style="@style/InCallButton"
+ android:background="@drawable/btn_add"
+ android:contentDescription="@string/qti_ims_onscreenConsultativeTransfer"
+ android:visibility="gone" />
+ <!-- "Record Call" -->
+ <ImageButton android:id="@+id/recordButton"
+ style="@style/InCallButton"
+ android:background="@drawable/btn_start_record"
+ android:contentDescription="@string/menu_start_record"
+ android:visibility="gone"/>
<!-- "Overflow" -->
<ImageButton android:id="@+id/overflowButton"
@@ -166,6 +197,24 @@
android:contentDescription="@string/onscreenManageConferenceText"
android:visibility="gone" />
+ <ImageButton android:id="@+id/rxtxVideoCallButton"
+ style="@style/InCallButton"
+ android:background="@drawable/btn_change_to_video"
+ android:contentDescription="@string/overflowBothCallMenuItemText"
+ android:visibility="gone" />
+
+ <ImageButton android:id="@+id/rxVedioCallButton"
+ style="@style/InCallButton"
+ android:background="@drawable/btn_change_to_hm_video"
+ android:contentDescription="@string/overflowRXCallMenuItemText"
+ android:visibility="gone" />
+
+ <ImageButton android:id="@+id/volteCallButton"
+ style="@style/InCallButton"
+ android:background="@drawable/btn_compound_audio"
+ android:contentDescription="@string/overflowVOCallMenuItemText"
+ android:visibility="gone" />
+
</LinearLayout>
</LinearLayout>
diff --git a/InCallUI/res/layout/call_card_fragment.xml b/InCallUI/res/layout/call_card_fragment.xml
index fabde37..2a4c3e2 100644
--- a/InCallUI/res/layout/call_card_fragment.xml
+++ b/InCallUI/res/layout/call_card_fragment.xml
@@ -60,12 +60,6 @@
android:elevation="4dp"
android:layout_alignParentBottom="true" />
- <include layout="@layout/manage_conference_call_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:elevation="5dp"
- android:layout_alignParentBottom="true"/>
-
<FrameLayout
android:id="@+id/floating_end_call_action_button_container"
android:layout_width="@dimen/end_call_floating_action_button_diameter"
@@ -97,13 +91,63 @@
<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.Material.ProgressBar"
- android:layout_gravity="center"
+ android:layout_gravity="left|center_vertical"
android:layout_width="48dp"
android:layout_height="48dp"
android:indeterminate="true" />
</FrameLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_centerHorizontal="true"
+ android:layout_below="@id/primary_call_info_container">
+
+ <include layout="@layout/manage_conference_call_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="5dp"
+ android:layout_alignParentBottom="true"/>
+
+ <!-- Volume boost and Volume enhancements in-call UI -->
+ <ImageButton android:id="@+id/volumeBoost"
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:visibility="gone"
+ android:soundEffectsEnabled="false"
+ android:background="@drawable/vb_normal"/>
+
+ </LinearLayout>
+
+ <!-- Call recorder infor -->
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true">
+ <TextView android:id="@+id/recordingIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="24dp"
+ android:drawableRight="@drawable/ic_recording_indicator"
+ android:paddingBottom="119dp"
+ android:visibility="invisible"/>
+ <TextView android:id="@+id/recordingTime"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_toRightOf="@id/recordingIcon"
+ android:paddingBottom="120dp"
+ android:singleLine="true"
+ android:text="@string/recording_time_text"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/incall_call_banner_text_color"
+ android:visibility="invisible"/>
+ </RelativeLayout>
+
<fragment android:name="com.android.incallui.VideoCallFragment"
android:layout_alignParentStart="true"
android:layout_gravity="start|center_vertical"
diff --git a/InCallUI/res/layout/caller_in_conference.xml b/InCallUI/res/layout/caller_in_conference.xml
index ac78096..dd14002 100644
--- a/InCallUI/res/layout/caller_in_conference.xml
+++ b/InCallUI/res/layout/caller_in_conference.xml
@@ -89,6 +89,15 @@
</LinearLayout>
+ <TextView android:id="@+id/conferenceCallerState"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:textSize="18sp"
+ android:ellipsize="marquee"
+ android:gravity="center_vertical"
+ android:textColor="@color/conference_call_manager_secondary_text_color"
+ android:singleLine="true"/>
+
<!-- "Separate" (i.e. "go private") button for this caller -->
<ImageView android:id="@+id/conferenceCallerSeparate"
android:src="@drawable/ic_call_split_white_24dp"
diff --git a/InCallUI/res/layout/manage_conference_call_button.xml b/InCallUI/res/layout/manage_conference_call_button.xml
index 01ca1bd..024b5a2 100644
--- a/InCallUI/res/layout/manage_conference_call_button.xml
+++ b/InCallUI/res/layout/manage_conference_call_button.xml
@@ -40,32 +40,22 @@
android:background="?android:attr/selectableItemBackground">
<!-- Call status of the background call, usually the string "On hold". -->
- <TextView android:id="@+id/conferenceLabel"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="center_vertical"
- android:paddingEnd="18dp"
- android:text="@string/onscreenConferenceText"
- android:textColor="@color/incall_banner_secondary_text_color"
- android:textSize="@dimen/secondary_call_info_text_size"
- android:singleLine="true" />
+ <ImageView android:id="@+id/manageConferenceButtonImage"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_group_white_24dp"
+ android:tint="@color/incall_banner_secondary_text_color"
+ android:paddingEnd="16dp"
+ android:importantForAccessibility="no" />
- <ImageView android:id="@+id/manageConferenceImage"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/ic_group_white_24dp"
- android:tint="@color/incall_banner_secondary_text_color"
- android:paddingEnd="16dp"/>
-
- <TextView android:id="@+id/manageConferenceLabel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="@color/incall_banner_secondary_text_color"
- android:textSize="@dimen/secondary_call_info_text_size"
- android:textAlignment="viewStart"
- android:text="@string/onscreenManageText"
- android:singleLine="true"/>
+ <TextView android:id="@+id/manageConferenceButtonLabel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:textColor="@color/incall_banner_secondary_text_color"
+ android:textSize="@dimen/secondary_call_info_text_size"
+ android:text="@string/onscreenManageConferenceText"
+ android:importantForAccessibility="no" />
</LinearLayout>
diff --git a/InCallUI/res/layout/msim_tab_sub_info.xml b/InCallUI/res/layout/msim_tab_sub_info.xml
new file mode 100644
index 0000000..8b33439
--- /dev/null
+++ b/InCallUI/res/layout/msim_tab_sub_info.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tab_sub_info"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal"
+ android:background="@color/incall_background_color">
+
+ <TextView
+ android:id="@+id/tabSubText"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"/>
+
+</LinearLayout>
diff --git a/InCallUI/res/layout/primary_call_info.xml b/InCallUI/res/layout/primary_call_info.xml
index 2aa583c..2fa3b94 100644
--- a/InCallUI/res/layout/primary_call_info.xml
+++ b/InCallUI/res/layout/primary_call_info.xml
@@ -23,7 +23,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/primary_call_banner"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/call_banner_side_padding"
android:paddingEnd="@dimen/call_banner_side_padding"
@@ -75,7 +75,6 @@
android:layout_width="24dp"
android:layout_height="match_parent"
android:layout_marginEnd="10dp"
- android:tint="@color/incall_accent_color"
android:alpha="0.0"
android:scaleType="fitCenter"
android:visibility="gone" />
@@ -100,7 +99,6 @@
android:textColor="@color/incall_accent_color"
android:textSize="@dimen/call_status_text_size"
android:alpha="0.7"
- android:singleLine="true"
android:gravity="start"
android:ellipsize="end"
ex:resizing_text_min_size="@dimen/call_status_text_min_size" />
@@ -123,6 +121,7 @@
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/call_name_text_size"
android:singleLine="true"
+ android:ellipsize="end"
ex:resizing_text_min_size="@dimen/call_name_text_min_size" />
<!-- Contact photo for primary call info -->
@@ -138,7 +137,7 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/in_call_layout_height"
android:orientation="horizontal"
android:clipChildren="false"
android:clipToPadding="false">
@@ -146,7 +145,7 @@
<ImageView android:id="@+id/hdAudioIcon"
android:src="@drawable/ic_hd_24dp"
android:layout_width="24dp"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:tint="@color/incall_call_banner_subtext_color"
android:scaleType="fitCenter"
diff --git a/InCallUI/res/layout/qti_video_call_picture_mode_menu.xml b/InCallUI/res/layout/qti_video_call_picture_mode_menu.xml
new file mode 100644
index 0000000..bcb15d5
--- /dev/null
+++ b/InCallUI/res/layout/qti_video_call_picture_mode_menu.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ~
+ -->
+
+<!-- Video Call Picture mode menu for InCall UI -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:theme="@style/Theme.InCallScreen"
+ android:orientation="vertical">
+
+ <CheckBox android:id="@+id/preview_video"
+ android:text="@string/video_call_picture_mode_preview_video"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginLeft="10dp"
+ />
+
+ <CheckBox android:id="@+id/incoming_video"
+ android:text="@string/video_call_picture_mode_incoming_video"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/preview_video"
+ android:layout_marginTop="10dp"
+ android:layout_marginLeft="10dp"
+ />
+</LinearLayout>
diff --git a/InCallUI/res/layout/qti_video_call_zoom_control.xml b/InCallUI/res/layout/qti_video_call_zoom_control.xml
new file mode 100644
index 0000000..51bc907
--- /dev/null
+++ b/InCallUI/res/layout/qti_video_call_zoom_control.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ~
+ -->
+
+<!-- The xml contains Qti zoom control resource -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="20dp"
+ android:theme="@style/Theme.InCallScreen" >
+
+ <com.android.incallui.ZoomControlBar
+ android:id="@+id/zoom_control"
+ android:layout_gravity="center|clip_horizontal|clip_vertical"
+ android:layout_width="match_parent"
+ android:visibility="visible"
+ android:layout_height="20dp" />
+</FrameLayout>
diff --git a/InCallUI/res/layout/video_call_views.xml b/InCallUI/res/layout/video_call_views.xml
index d514f6d..1926fcc 100644
--- a/InCallUI/res/layout/video_call_views.xml
+++ b/InCallUI/res/layout/video_call_views.xml
@@ -20,11 +20,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
- <TextureView
- android:id="@+id/incomingVideo"
- android:layout_gravity="center"
+ <FrameLayout
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent">
+ <TextureView
+ android:id="@+id/incomingVideo"
+ android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </FrameLayout>
<!-- The width and height are replaced at runtime based on the selected camera. -->
<FrameLayout
android:id="@+id/previewVideoContainer"
diff --git a/InCallUI/res/values-mcc460-mnc00/qticonfig.xml b/InCallUI/res/values-mcc460-mnc00/qticonfig.xml
new file mode 100644
index 0000000..de8962c
--- /dev/null
+++ b/InCallUI/res/values-mcc460-mnc00/qticonfig.xml
@@ -0,0 +1,42 @@
+<!--
+ ~ Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources>
+ <!-- Config to show/hide call duration in call detail -->
+ <bool name="call_duration_enabled">false</bool>
+ <!-- Config to enable/disable ims call log -->
+ <bool name="ims_call_type_enabled">true</bool>
+ <!-- Flag indicating if conference dialer is enabled -->
+ <bool name="config_enable_conference_dialer">true</bool>
+ <!-- Config to enable/disable conference state display -->
+ <bool name="config_conference_call_show_participant_status">true</bool>
+ <!-- Config to enable/disable add multi participants -->
+ <bool name="add_multi_participants_enabled">true</bool>
+ <!-- Config to add participant only in conferencall call-->
+ <bool name="add_participant_only_in_conference">true</bool>
+</resources>
diff --git a/InCallUI/res/values-mcc460-mnc01/qticonfig.xml b/InCallUI/res/values-mcc460-mnc01/qticonfig.xml
new file mode 100644
index 0000000..5f789c5
--- /dev/null
+++ b/InCallUI/res/values-mcc460-mnc01/qticonfig.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources>
+ <!-- Config to show/hide HD Icon2 -->
+ <bool name="config_show_hd2">true</bool>
+</resources>
diff --git a/InCallUI/res/values-mcc460-mnc02/qticonfig.xml b/InCallUI/res/values-mcc460-mnc02/qticonfig.xml
new file mode 100644
index 0000000..9613155
--- /dev/null
+++ b/InCallUI/res/values-mcc460-mnc02/qticonfig.xml
@@ -0,0 +1,42 @@
+<!--
+ ~ Copyright (c) 2015,2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources>
+ <!-- Config to show/hide call duration in call detail -->
+ <bool name="call_duration_enabled">false</bool>
+ <!-- Config to enable/disable ims call log -->
+ <bool name="ims_call_type_enabled">true</bool>
+ <!-- Flag indicating if conference dialer is enabled -->
+ <bool name="config_enable_conference_dialer">true</bool>
+ <!-- Config to enable/disable conference state display -->
+ <bool name="config_conference_call_show_participant_status">true</bool>
+ <!-- Config to enable/disable add multi participants -->
+ <bool name="add_multi_participants_enabled">true</bool>
+ <!-- Config to add participant only in conferencall call-->
+ <bool name="add_participant_only_in_conference">true</bool>
+</resources>
diff --git a/InCallUI/res/values-mcc460-mnc06/qticonfig.xml b/InCallUI/res/values-mcc460-mnc06/qticonfig.xml
new file mode 100644
index 0000000..5f789c5
--- /dev/null
+++ b/InCallUI/res/values-mcc460-mnc06/qticonfig.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources>
+ <!-- Config to show/hide HD Icon2 -->
+ <bool name="config_show_hd2">true</bool>
+</resources>
diff --git a/InCallUI/res/values-mcc460-mnc07/qticonfig.xml b/InCallUI/res/values-mcc460-mnc07/qticonfig.xml
new file mode 100644
index 0000000..de8962c
--- /dev/null
+++ b/InCallUI/res/values-mcc460-mnc07/qticonfig.xml
@@ -0,0 +1,42 @@
+<!--
+ ~ Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources>
+ <!-- Config to show/hide call duration in call detail -->
+ <bool name="call_duration_enabled">false</bool>
+ <!-- Config to enable/disable ims call log -->
+ <bool name="ims_call_type_enabled">true</bool>
+ <!-- Flag indicating if conference dialer is enabled -->
+ <bool name="config_enable_conference_dialer">true</bool>
+ <!-- Config to enable/disable conference state display -->
+ <bool name="config_conference_call_show_participant_status">true</bool>
+ <!-- Config to enable/disable add multi participants -->
+ <bool name="add_multi_participants_enabled">true</bool>
+ <!-- Config to add participant only in conferencall call-->
+ <bool name="add_participant_only_in_conference">true</bool>
+</resources>
diff --git a/InCallUI/res/values-mcc460-mnc08/qticonfig.xml b/InCallUI/res/values-mcc460-mnc08/qticonfig.xml
new file mode 100644
index 0000000..de8962c
--- /dev/null
+++ b/InCallUI/res/values-mcc460-mnc08/qticonfig.xml
@@ -0,0 +1,42 @@
+<!--
+ ~ Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources>
+ <!-- Config to show/hide call duration in call detail -->
+ <bool name="call_duration_enabled">false</bool>
+ <!-- Config to enable/disable ims call log -->
+ <bool name="ims_call_type_enabled">true</bool>
+ <!-- Flag indicating if conference dialer is enabled -->
+ <bool name="config_enable_conference_dialer">true</bool>
+ <!-- Config to enable/disable conference state display -->
+ <bool name="config_conference_call_show_participant_status">true</bool>
+ <!-- Config to enable/disable add multi participants -->
+ <bool name="add_multi_participants_enabled">true</bool>
+ <!-- Config to add participant only in conferencall call-->
+ <bool name="add_participant_only_in_conference">true</bool>
+</resources>
diff --git a/InCallUI/res/values-mcc460-mnc09/qticonfig.xml b/InCallUI/res/values-mcc460-mnc09/qticonfig.xml
new file mode 100644
index 0000000..5f789c5
--- /dev/null
+++ b/InCallUI/res/values-mcc460-mnc09/qticonfig.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources>
+ <!-- Config to show/hide HD Icon2 -->
+ <bool name="config_show_hd2">true</bool>
+</resources>
diff --git a/InCallUI/res/values-w500dp-land/dimens.xml b/InCallUI/res/values-w500dp-land/dimens.xml
index 112ec5f..01cf165 100644
--- a/InCallUI/res/values-w500dp-land/dimens.xml
+++ b/InCallUI/res/values-w500dp-land/dimens.xml
@@ -32,4 +32,6 @@
<dimen name="dialpad_elevation">2dp</dimen>
<dimen name="video_preview_margin">20dp</dimen>
+
+ <dimen name="end_call_button_margin_right">80dp</dimen>
</resources>
diff --git a/InCallUI/res/values-zh-rCN/qtistrings.xml b/InCallUI/res/values-zh-rCN/qtistrings.xml
new file mode 100644
index 0000000..120af0a
--- /dev/null
+++ b/InCallUI/res/values-zh-rCN/qtistrings.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2016 The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ~
+ -->
+
+<!-- The xml contains Qti specific resource strings neede for any value added features. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- In-call screen: status label for an incoming conference call [CHAR LIMIT=25] -->
+ <string name="card_title_incoming_conf_call">"电话会议来电"</string>
+ <!-- In-call screen: status label for an incoming video conference call [CHAR LIMIT=25] -->
+ <string name="card_title_incoming_video_conf_call">"视频会议来电"</string>
+ <!-- The "label" of the in-call Notification for an incoming conference ringing call. [CHAR LIMIT=60] -->
+ <string name="notification_incoming_conf_call">"电话会议来电"</string>
+ <!-- Title displayed in the overlay for incoming conference calls which include the name of the provider.
+ [CHAR LIMIT=40] -->
+ <string name="incoming_conf_via_template">"通过 <xliff:g id="provider_name">%s</xliff:g>会议来电"</string>
+
+ <!-- In-call screen: Modify Call Options for IMS call -->
+ <string name="modify_call_option_title">选择通话类型?</string>
+ <string name="modify_call_option_vt">双向视频</string>
+ <string name="modify_call_option_vt_tx">视频发送</string>
+ <string name="modify_call_option_vt_rx">视频接收</string>
+ <string name="modify_call_option_voice">仅限语音</string>
+ <!-- Call substate label -->
+ <string name="call_substate_label">" 通话质量不佳 - \u000a"</string>
+ <!-- Call substate label for call resumed -->
+ <string name="call_substate_call_resumed">"通话继续\u000a"</string>
+ <!-- Call substate label for call connected suspended (audio) -->
+ <string name="call_substate_connected_suspended_audio">"暂停连接\u000a"</string>
+ <!-- Call substate label for call connected suspended (video) -->
+ <string name="call_substate_connected_suspended_video">"视频电话暂停\u000a"</string>
+ <!-- Call substate label for avp retry -->
+ <string name="call_substate_avp_retry">"AVP 重试\u000a"</string>
+ <!-- Video quality changed message -->
+ <string name="video_quality_changed">" 视频画质变为\u0020"</string>
+ <!-- Video quality High -->
+ <string name="video_quality_high">"高画质"</string>
+ <!-- Video quality Medium -->
+ <string name="video_quality_medium">"中画质"</string>
+ <!-- Video quality Low -->
+ <string name="video_quality_low">"低画质"</string>
+ <!-- Video quality Unknown -->
+ <string name="video_quality_unknown">"未知"</string>
+ <!-- Message indicating that Video Started flowing for IMS-VT calls -->
+ <string name="player_started">"播放器已启动"</string>
+ <!-- Message indicating that Video Stopped flowing for IMS-VT calls -->
+ <string name="player_stopped">"播放暂停"</string>
+ <!-- Message indicating that camera failure has occurred for the selected camera and
+ as result camera is not ready -->
+ <string name="camera_not_ready">"摄像头故障"</string>
+ <!-- Message indicating that camera is ready/available -->
+ <string name="camera_ready">"摄像头就绪"</string>
+ <!-- Message indicating unknown call session event -->
+ <string name="unknown_call_session_event">"未知电话"</string>
+ <!-- Message indicating data usage -->
+ <string name="data_usage_label">"流量使用: "</string>
+ <!-- Modify call error cause -->
+ <string name="modify_call_failed_due_to_low_battery">"由于电池电量不足,无法修改通话。"</string>
+ <!-- Message indicating video calls not allowed if user enabled TTY Mode -->
+ <string name="video_call_not_allowed_if_tty_enabled">"请关闭 TTY 模式以便升级到视频电话。"</string>
+ <!-- Text for the onscreen "Add Participant" button -->
+ <string name="onscreenAddParticipant">"添加参与者"</string>
+ <!-- Description of the deflect target in the Slide unlock screen. [CHAR LIMIT=NONE] -->
+ <string name="qti_description_target_deflect">"转移"</string>
+ <string name="qti_description_deflect_error">"号码未设置。通过 IMS 设置提供号码,然后重试。"</string>
+ <string name="qti_description_deflect_service_error">"不支持呼叫转移服务。"</string>
+ <!-- Message indicating call failed due to handover not feasible -->
+ <string name="call_failed_ho_not_feasible">"无法从 LTE 切换到 3G/2G,通话已结束。"</string>
+ <!-- Title of the IMS to CS redial dialog -->
+ <string name="cs_redial_option">"重拨选项"</string>
+ <!-- Message text of the IMS to CS redial dialog -->
+ <string name="cs_redial_msg">"无法拨打 IMS 视频通话,是否要拨打非 IMS 的语音通话?"</string>
+ <!-- Yes option of the IMS to CS redial dialog -->
+ <string name="cs_redial_yes">"是"</string>
+ <!-- No option of the IMS to CS redial dialog -->
+ <string name="cs_redial_no">"否"</string>
+ <!-- Session modify cause unspecified -->
+ <string name="session_modify_cause_unspecified"></string>
+ <!-- Session modify cause code upgrade local request -->
+ <string name="session_modify_cause_upgrade_local_request">"应用户请求,通话已升级"</string>
+ <!-- Session modify cause code upgrade remote request -->
+ <string name="session_modify_cause_upgrade_remote_request">"应远程用户请求,通话已升级"</string>
+ <!-- Session modify cause code downgrade local request -->
+ <string name="session_modify_cause_downgrade_local_request">"应用户请求,通话已降级"</string>
+ <!-- Session modify cause code downgrade remote request -->
+ <string name="session_modify_cause_downgrade_remote_request">"应远程用户请求,通话已降级"</string>
+ <!-- Session modify cause code downgrade rtp timeout -->
+ <string name="session_modify_cause_downgrade_rtp_timeout">"由于 RTP 超时,通话已降级"</string>
+ <!-- Session modify cause code downgrade qos -->
+ <string name="session_modify_cause_downgrade_qos">"由于服务质量,通话已降级"</string>
+ <!-- Session modify cause code downgrade packet loss -->
+ <string name="session_modify_cause_downgrade_packet_loss">"由于数据包丢失,通话已降级"</string>
+ <!-- Session modify cause code downgrade low thrput -->
+ <string name="session_modify_cause_downgrade_low_thrput">"由于吞吐量低,通话已降级"</string>
+ <!-- Session modify cause code downgrade thermal mitigation -->
+ <string name="session_modify_cause_downgrade_thermal_mitigation">"由于设备过热,通话已降级"</string>
+ <!-- Session modify cause code downgrade lipsync -->
+ <string name="session_modify_cause_downgrade_lipsync">"由于嘴唇同步功能,通话已降级"</string>
+ <!-- Session modify cause code downgrade generic error -->
+ <string name="session_modify_cause_downgrade_generic_error">"由于一般错误,通话已降级"</string>
+ <!-- Title for low battery alert dialogue -->
+ <string name="low_battery">"电池电量不足"</string>
+ <!-- Yes option of the low battery alert dialog -->
+ <string name="low_battery_yes">"是"</string>
+ <!-- No option of the low battery alert dialog -->
+ <string name="low_battery_no">"否"</string>
+ <!-- Message text of the low battery alert dialog in cases when video call can be downgraded to voice call -->
+ <string name="low_battery_downgrade_to_voice_msg">"要将视频通话转换为音频通话吗?"</string>
+ <!-- Message text of the low battery alert dialog in cases video call doesn't have downgrade capabilities -->
+ <string name="low_battery_hangup_msg">"要挂断电话吗?"</string>
+ <!-- Description of the call transfer related strings [CHAR LIMIT=NONE] -->
+ <string name="qti_ims_transfer_num_error">"号码未设置。通过 IMS 设置提供号码,然后重试。"</string>
+ <string name="qti_ims_transfer_request_error">"呼叫转移请求失败。"</string>
+ <string name="qti_ims_transfer_request_success">"呼叫转移请求成功。"</string>
+ <!-- Text for the onscreen "blind transfer" button -->
+ <string name="qti_ims_onscreenBlindTransfer">"无条件转移"</string>
+ <!-- Text for the onscreen "assured transfer" button -->
+ <string name="qti_ims_onscreenAssuredTransfer">"固定转移"</string>
+ <!-- Text for the onscreen "consultative transfer" button -->
+ <string name="qti_ims_onscreenConsultativeTransfer">"询问转移"</string>
+ <!-- Text for operator specific emergency number -->
+ <add-resource type="string" name="emergency_call_dialog_number_for_display_operator"/>
+ <string name="emergency_call_dialog_number_for_display_operator">"紧急呼救"</string>
+</resources>
diff --git a/InCallUI/res/values-zh-rCN/strings.xml b/InCallUI/res/values-zh-rCN/strings.xml
index e51a719..04d7af3 100644
--- a/InCallUI/res/values-zh-rCN/strings.xml
+++ b/InCallUI/res/values-zh-rCN/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<!--
~ Copyright (C) 2013 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -167,6 +167,12 @@
<string name="preference_category_ringtone" msgid="6246687516643676729">"铃声和振动"</string>
<string name="manageConferenceLabel" msgid="7237614418556336108">"管理电话会议"</string>
<string name="emergency_call_dialog_number_for_display" msgid="7244995877625769187">"紧急呼救号码"</string>
+ <string name="volume_boost_notify_enabled">"开启音量增强"</string>
+ <string name="volume_boost_notify_disabled">"关闭音量增强"</string>
+ <string name="volume_boost_notify_unavailable">"有线/蓝牙耳机连接时,音效增强不可用。"</string>
+ <string name="menu_start_record">通话录音</string>
+ <string name="menu_stop_record">停止录音</string>
+ <string name="recording_time_text">录音中</string>
<plurals name="duration_seconds" formatted="false" msgid="2544699588744957418">
<item quantity="other"><xliff:g id="COUNT">%d</xliff:g> 秒</item>
<item quantity="one">1 秒</item>
@@ -196,4 +202,5 @@
<string name="closed_today_at" msgid="4060072663433467233">"已于今天<xliff:g id="CLOSE_TIME">%s</xliff:g>结束营业"</string>
<string name="open_now" msgid="4615706338669555999">"营业中"</string>
<string name="closed_now" msgid="2635314668145282080">"现已结束营业"</string>
+ <string name="too_many_recipients">4G会议电话的参与人数上限为6人。</string>
</resources>
diff --git a/InCallUI/res/values/array.xml b/InCallUI/res/values/array.xml
index 7877ec8..afd9f55 100644
--- a/InCallUI/res/values/array.xml
+++ b/InCallUI/res/values/array.xml
@@ -132,4 +132,38 @@
<item>@string/description_direction_left</item>
<item>@null</item>
</array>
+
+ <array name="enhance_incoming_call_widget_video_without_sms_targets">
+ <item>@drawable/ic_lockscreen_answer</item>
+ <item>@drawable/ic_enhance_answer_rx_video</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/ic_enhance_answer_video</item>
+ </array>
+ <array name="enhance_incoming_call_widget_video_with_sms_targets">
+ <item>@drawable/ic_enhance_answer_video</item>
+ <item>@drawable/ic_lockscreen_text</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/ic_lockscreen_answer</item>
+ <item>@drawable/ic_enhance_answer_rx_video</item>
+ <item>@null</item>
+ </array>
+ <array name="enhance_incoming_call_widget_video_upgrade_request_targets">
+ <item>@drawable/ic_enhance_answer_video</item>
+ <item>@null</item>
+ <item>@drawable/ic_enhance_decline_video</item>
+ <item>@drawable/ic_enhance_answer_rx_video</item>
+ </array>
+ <array name="enhance_incoming_call_bidirectional_video_accept_request_targets">
+ <item>@drawable/ic_enhance_answer_video</item>
+ <item>@drawable/ic_enhance_decline_video</item>
+ </array>
+ <array name="enhance_incoming_call_video_transmit_accept_request_targets">
+ <item>@drawable/ic_enhance_answer_tx_video</item>
+ <item>@drawable/ic_enhance_decline_video</item>
+ </array>
+ <array name="enhance_incoming_call_video_receive_accept_request_targets">
+ <item>@drawable/ic_enhance_answer_rx_video</item>
+ <item>@drawable/ic_enhance_decline_video</item>
+ </array>
+
</resources>
diff --git a/InCallUI/res/values/config.xml b/InCallUI/res/values/config.xml
index b81ba3c..763b0e2 100644
--- a/InCallUI/res/values/config.xml
+++ b/InCallUI/res/values/config.xml
@@ -24,4 +24,10 @@
<!-- The number of milliseconds after which a video call will automatically enter fullscreen
mode (requires video_call_auto_fullscreen to be true). -->
<integer name="video_call_auto_fullscreen_timeout">5000</integer>
-</resources>
\ No newline at end of file
+ <!-- When international prefix is enabled in CDMA mode, whether with international prefix -->
+ <bool name="phone_number_with_intl_prefix">false</bool>
+
+ <bool name="config_regional_number_patterns_video_call">false</bool>
+ <!-- When set to true, this config enables enhance videocall ui -->
+ <bool name="config_enable_enhance_video_call_ui">false</bool>
+</resources>
diff --git a/InCallUI/res/values/customize.xml b/InCallUI/res/values/customize.xml
new file mode 100644
index 0000000..45cc4aa
--- /dev/null
+++ b/InCallUI/res/values/customize.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<resources>
+
+ <!--
+ customize the default value to check idp, default value is none,
+ 0-none, 1-CDMA, 2-GSM, 3-CDMA/GSM
+ -->
+ <integer name="check_idp" translatable="false">0</integer>
+ <!-- the international_idp array index that check phone roaming
+ state, -1:none-->
+ <integer name="check_idp_roaming_idx" translatable="false">-1</integer>
+ <!-- the international_idp array index that to be reconverted,
+ -1:none-->
+ <integer name="check_idp_reconvert_idx" translatable="false">-1</integer>
+
+</resources>
diff --git a/InCallUI/res/values/dimens.xml b/InCallUI/res/values/dimens.xml
index 59da786..15520e5 100644
--- a/InCallUI/res/values/dimens.xml
+++ b/InCallUI/res/values/dimens.xml
@@ -63,6 +63,9 @@
<dimen name="primary_call_elevation">0dp</dimen>
<dimen name="dialpad_elevation">2dp</dimen>
+ <!-- layout height -->
+ <dimen name="in_call_layout_height">40dp</dimen>
+
<!-- The InCallUI dialpad will sometimes want digits sizes that are different from dialer.
Note: These are the default sizes for small (<600dp height) devices: larger screen sizes
apply the values in values-h600dp/dimens.xml. -->
diff --git a/InCallUI/res/values/qtiarray.xml b/InCallUI/res/values/qtiarray.xml
new file mode 100644
index 0000000..3e7e1e8
--- /dev/null
+++ b/InCallUI/res/values/qtiarray.xml
@@ -0,0 +1,285 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (c) 2015, 2016 The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+
+<!-- Array resources for the Phone app. -->
+<resources>
+ <!-- "Target" resources for the GlowPadView widget used for incoming calls;
+ see InCallTouchUi.showIncomingCallWidget() and incall_touch_ui.xml. -->
+
+ <!-- For video calls, if respond via SMS is disabled:
+ - Answer as video call (drag right)
+ - Decline (drag left)
+ - Answer as audio call (drag down) -->
+ <array name="qti_incoming_call_widget_video_without_sms_targets">
+ <item>@drawable/ic_lockscreen_answer_video</item>
+ <item>@null</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/ic_lockscreen_answer</item>
+ <item>@drawable/qti_ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/qti_ic_lockscreen_answer_rx_video</item>
+ </array>
+ <array name="qti_incoming_call_widget_video_without_sms_target_descriptions">
+ <item>@string/description_target_answer_video_call</item>
+ <item>@null</item>
+ <item>@string/description_target_decline</item>
+ <item>@string/description_target_answer_audio_call</item>
+ </array>
+ <array name="qti_incoming_call_widget_video_without_sms_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@null</item>
+ <item>@string/description_direction_left</item>
+ <item>@string/description_direction_down</item>
+ </array>
+
+ <!-- For video transmit(Tx), if respond via SMS is disabled:
+ - Answer as video transmit(Tx) (drag right)
+ - Decline (drag left)
+ - Answer as audio call (drag down) -->
+ <array name="qti_incoming_call_widget_tx_video_without_sms_targets">
+ <item>@drawable/qti_ic_lockscreen_answer_tx_video</item>
+ <item>@null</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/ic_lockscreen_answer</item>>
+ </array>
+ <array name="qti_incoming_call_widget_tx_video_without_sms_target_descriptions">
+ <item>@string/description_target_answer_video_call</item>
+ <item>@null</item>
+ <item>@string/description_target_decline</item>
+ <item>@string/description_target_answer_audio_call</item>
+ </array>
+ <array name="qti_incoming_call_widget_tx_video_without_sms_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@null</item>
+ <item>@string/description_direction_left</item>
+ <item>@string/description_direction_down</item>
+ </array>
+
+ <!-- For video receive(Rx), if respond via SMS is disabled:
+ - Answer as video receive(Rx) (drag right)
+ - Decline (drag left)
+ - Answer as audio call (drag down) -->
+ <array name="qti_incoming_call_widget_rx_video_without_sms_targets">
+ <item>@drawable/qti_ic_lockscreen_answer_rx_video</item>
+ <item>@null</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/ic_lockscreen_answer</item>>
+ </array>
+ <array name="qti_incoming_call_widget_rx_video_without_sms_target_descriptions">
+ <item>@string/description_target_answer_video_call</item>
+ <item>@null</item>
+ <item>@string/description_target_decline</item>
+ <item>@string/description_target_answer_audio_call</item>
+ </array>
+ <array name="qti_incoming_call_widget_rx_video_without_sms_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@null</item>
+ <item>@string/description_direction_left</item>
+ <item>@string/description_direction_down</item>
+ </array>
+
+ <!-- For video calls, if respond via SMS is enabled:
+ - Answer as video call (drag right)
+ - Respond via SMS (drag up)
+ - Decline (drag left)
+ - Answer as audio call (drag down) -->
+ <array name="qti_incoming_call_widget_video_with_sms_targets">
+ <item>@drawable/ic_lockscreen_answer_video</item>
+ <item>@drawable/ic_lockscreen_text</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/ic_lockscreen_answer</item>
+ <item>@drawable/qti_ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/qti_ic_lockscreen_answer_rx_video</item>
+ </array>
+ <array name="qti_incoming_call_widget_video_with_sms_target_descriptions">
+ <item>@string/description_target_answer_video_call</item>
+ <item>@string/description_target_send_sms</item>
+ <item>@string/description_target_decline</item>
+ <item>@string/description_target_answer_audio_call</item>
+ </array>
+ <array name="qti_incoming_call_widget_video_with_sms_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@string/description_direction_up</item>
+ <item>@string/description_direction_left</item>
+ <item>@string/description_direction_down</item>
+ </array>
+
+ <!-- For video transmit(Tx) calls, if respond via SMS is enabled:
+ - Answer as video transmit(Tx) call (drag right)
+ - Respond via SMS (drag up)
+ - Decline (drag left)
+ - Answer as audio call (drag down) -->
+ <array name="qti_incoming_call_widget_tx_video_with_sms_targets">
+ <item>@drawable/qti_ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/ic_lockscreen_text</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/ic_lockscreen_answer</item>
+ </array>
+ <array name="qti_incoming_call_widget_tx_video_with_sms_target_descriptions">
+ <item>@string/description_target_answer_video_call</item>
+ <item>@string/description_target_send_sms</item>
+ <item>@string/description_target_decline</item>
+ <item>@string/description_target_answer_audio_call</item>
+ </array>
+ <array name="qti_incoming_call_widget_tx_video_with_sms_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@string/description_direction_up</item>
+ <item>@string/description_direction_left</item>
+ <item>@string/description_direction_down</item>
+ </array>
+
+ <!-- For video receive(Rx) calls, if respond via SMS is enabled:
+ - Answer as video receive call (drag right)
+ - Respond via SMS (drag up)
+ - Decline (drag left)
+ - Answer as audio call (drag down) -->
+ <array name="qti_incoming_call_widget_rx_video_with_sms_targets">
+ <item>@drawable/qti_ic_lockscreen_answer_rx_video</item>
+ <item>@drawable/ic_lockscreen_text</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/ic_lockscreen_answer</item>
+ </array>
+ <array name="qti_incoming_call_widget_rx_video_with_sms_target_descriptions">
+ <item>@string/description_target_answer_video_call</item>
+ <item>@string/description_target_send_sms</item>
+ <item>@string/description_target_decline</item>
+ <item>@string/description_target_answer_audio_call</item>
+ </array>
+ <array name="qti_incoming_call_widget_rx_video_with_sms_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@string/description_direction_up</item>
+ <item>@string/description_direction_left</item>
+ <item>@string/description_direction_down</item>
+ </array>
+
+ <!-- For accept/reject upgrade to video in active video call
+ - Accept upgrade to video request (drag right)
+ - Decline upgrade to video request (drag left)-->
+ <array name="qti_incoming_call_widget_video_request_targets">
+ <item>@drawable/ic_lockscreen_answer_video</item>
+ <item>@drawable/ic_lockscreen_decline_video</item>
+ <item>@drawable/ic_lockscreen_answer</item>
+ <item>@drawable/qti_ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/qti_ic_lockscreen_answer_rx_video</item>
+ </array>
+
+ <array name="qti_incoming_call_widget_video_request_target_descriptions">
+ <item>@string/description_target_accept_upgrade_to_video_request</item>
+ <item>@null</item>
+ <item>@string/description_target_decline_upgrade_to_video_request</item>
+ <item>@null</item>"
+ </array>
+ <array name="qti_incoming_call_widget_video_request_target_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@null</item>
+ <item>@string/description_direction_left</item>
+ <item>@null</item>
+ </array>
+
+ <!-- For accept/reject upgrade to video in active video call
+ - Accept upgrade to video request (drag right)
+ - Decline upgrade to video request (drag left)-->
+ <array name="qti_incoming_call_widget_bidirectional_video_accept_reject_request_targets">
+ <item>@drawable/ic_lockscreen_answer_video</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ </array>
+
+ <!-- For accept/reject upgrade to video transmit in active video call
+ - Accept upgrade to video request (drag right)
+ - Decline upgrade to video request (drag left)-->
+ <array name="qti_incoming_call_widget_video_transmit_accept_reject_request_targets">
+ <item>@drawable/qti_ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ </array>
+ <array name="qti_incoming_call_widget_video_transmit_request_target_descriptions">
+ <item>@string/description_target_accept_upgrade_to_video_transmit_request</item>
+ <item>@string/description_target_decline_upgrade_to_video_transmit_request</item>
+ </array>
+
+ <!-- For accept/reject upgrade to video receive in active video call
+ - Accept upgrade to video request (drag right)
+ - Decline upgrade to video request (drag left)-->
+ <array name="qti_incoming_call_widget_video_receive_accept_reject_request_targets">
+ <item>@drawable/qti_ic_lockscreen_answer_rx_video</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ </array>
+ <array name="qti_incoming_call_widget_video_receive_request_target_descriptions">
+ <item>@string/description_target_accept_upgrade_to_video_receive_request</item>
+ <item>@string/description_target_decline_upgrade_to_video_receive_request</item>
+ </array>
+
+ <!-- For audio calls, if respond via SMS is disabled & call deflection is enabled:
+ - Answer (drag right)
+ - Decline (drag left)
+ - Deflect (drag down) -->
+ <array name="qti_incoming_call_widget_audio_without_sms_targets">
+ <item>@drawable/ic_lockscreen_answer</item>
+ <item>@null</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/qti_ic_lockscreen_deflect</item>
+ </array>
+ <array name="qti_incoming_call_widget_audio_without_sms_target_descriptions">
+ <item>@string/description_target_answer</item>
+ <item>@null</item>
+ <item>@string/description_target_decline</item>
+ <item>@string/qti_description_target_deflect</item>
+ </array>
+ <array name="qti_incoming_call_widget_audio_without_sms_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@null</item>
+ <item>@string/description_direction_left</item>
+ <item>@string/description_direction_down</item>
+ </array>
+
+ <!-- For audio calls, if respond via SMS is enabled & call deflection is enabled:
+ - Answer (drag right)
+ - Respond via SMS (drag up)
+ - Decline (drag left)
+ - Deflect (drag down) -->
+ <array name="qti_incoming_call_widget_audio_with_sms_targets">
+ <item>@drawable/ic_lockscreen_answer</item>
+ <item>@drawable/ic_lockscreen_text</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ <item>@drawable/qti_ic_lockscreen_deflect</item>
+ </array>
+ <array name="qti_incoming_call_widget_audio_with_sms_target_descriptions">
+ <item>@string/description_target_answer</item>
+ <item>@string/description_target_send_sms</item>
+ <item>@string/description_target_decline</item>
+ <item>@string/qti_description_target_deflect</item>
+ </array>
+ <array name="qti_incoming_call_widget_audio_with_sms_direction_descriptions">
+ <item>@string/description_direction_right</item>
+ <item>@string/description_direction_up</item>
+ <item>@string/description_direction_left</item>
+ <item>@string/description_direction_down</item>
+ </array>
+
+</resources>
diff --git a/InCallUI/res/values/qticonfig.xml b/InCallUI/res/values/qticonfig.xml
new file mode 100644
index 0000000..7cf7410
--- /dev/null
+++ b/InCallUI/res/values/qticonfig.xml
@@ -0,0 +1,53 @@
+<!--
+ ~ Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources>
+ <!--
+ customize the default value to control Clear-Code feature,
+ default value is false, true is for LatamOpenAMX
+ -->
+ <bool name="def_incallui_clearcode_enabled">false</bool>
+ <!-- Config to show/hide call duration in call detail -->
+ <bool name="call_duration_enabled">true</bool>
+ <!-- Config to enable/disable ims call log -->
+ <bool name="ims_call_type_enabled">false</bool>
+ <!-- Flag indicating if conference dialer is enabled -->
+ <bool name="config_enable_conference_dialer">false</bool>
+ <!-- Config to enable/disable conference state display -->
+ <bool name="config_conference_call_show_participant_status">false</bool>
+ <!-- Config to enable/disable add multi participants -->
+ <bool name="add_multi_participants_enabled">false</bool>
+ <!-- Config to add participant only in conferencall call-->
+ <bool name="add_participant_only_in_conference">false</bool>
+ <!-- Config to if show preview before the receiver accepts a show me upgrade video call -->
+ <bool name="config_enable_modify_call_preview">false</bool>
+ <!-- Config to show/hide HD Icon2 -->
+ <bool name="config_show_hd2">false</bool>
+ <!-- Config to enalbe call record -->
+ <bool name="enable_call_record">false</bool>
+</resources>
diff --git a/InCallUI/res/values/qtistrings.xml b/InCallUI/res/values/qtistrings.xml
new file mode 100644
index 0000000..eba800a
--- /dev/null
+++ b/InCallUI/res/values/qtistrings.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2015, 2016 The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ~
+ -->
+
+<!-- The xml contains Qti specific resource strings neede for any value added features. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Dtermine whether emergency calls should be marked. -->
+ <bool name="mark_emergency_call">true</bool>
+ <!-- Call substate label -->
+ <string name="call_substate_label"> Call substate - \u000a</string>
+ <!-- Call substate label for call resumed -->
+ <string name="call_substate_call_resumed">Resumed \u000a</string>
+ <!-- Call substate label for call connected suspended (audio) -->
+ <string name="call_substate_connected_suspended_audio">Connected Suspended (Audio) \u000a</string>
+ <!-- Call substate label for call connected suspended (video) -->
+ <string name="call_substate_connected_suspended_video">Connected Suspended (Video) \u000a</string>
+ <!-- Call substate label for avp retry -->
+ <string name="call_substate_avp_retry">Avp Retry \u000a</string>
+
+ <!-- Video quality changed message -->
+ <string name="video_quality_changed"> Video quality changed to \u0020</string>
+ <!-- Video quality High -->
+ <string name="video_quality_high">High</string>
+ <!-- Video quality Medium -->
+ <string name="video_quality_medium">Medium</string>
+ <!-- Video quality Low -->
+ <string name="video_quality_low">Low</string>
+ <!-- Video quality Unknown -->
+ <string name="video_quality_unknown">Unknown</string>
+
+ <!-- Message indicating that Video Started flowing for IMS-VT calls -->
+ <string name="player_started">Player Started</string>
+ <!-- Message indicating that Video Stopped flowing for IMS-VT calls -->
+ <string name="player_stopped">Player Stopped</string>
+ <!-- Message indicating that camera failure has occurred for the selected camera and
+ as result camera is not ready -->
+ <string name="camera_not_ready">Camera not ready</string>
+ <!-- Message indicating that camera is ready/available -->
+ <string name="camera_ready">Camera ready</string>
+ <!-- Message indicating unknown call session event -->
+ <string name="unknown_call_session_event">"Unkown call session event"</string>
+
+ <!-- Message indicating data usage -->
+ <string name="wlan_data_usage_label">"Wlan Data usage : "</string>
+ <string name="lte_data_usage_label">"Lte Data usage : "</string>
+
+ <!-- In-call screen: Modify Call Options for IMS call -->
+ <string name="modify_call_option_title">Which type of call?</string>
+ <string name="modify_call_option_vt">Video bidirectional</string>
+ <string name="modify_call_option_vt_tx">Video Transmit</string>
+ <string name="modify_call_option_vt_rx">Video Receive</string>
+ <string name="modify_call_option_voice">Voice Only</string>
+
+ <!-- Modify call error cause -->
+ <string name="modify_call_failed_due_to_low_battery">Modify call failed due to low battery.</string>
+
+ <!-- Description of the call transfer related strings [CHAR LIMIT=NONE] -->
+ <string name="qti_ims_transfer_num_error">Number not set. Provide the number via IMS settings and retry.</string>
+ <string name="qti_ims_transfer_request_error">Call Transfer request had failed.</string>
+ <string name="qti_ims_transfer_request_success">Call Transfer request is successful.</string>
+ <!-- Text for the onscreen "blind transfer" button -->
+ <string name="qti_ims_onscreenBlindTransfer">Blind Transfer</string>
+ <!-- Text for the onscreen "assured transfer" button -->
+ <string name="qti_ims_onscreenAssuredTransfer">Assured Transfer</string>
+ <!-- Text for the onscreen "consultative transfer" button -->
+ <string name="qti_ims_onscreenConsultativeTransfer">Consultative Transfer</string>
+
+ <!-- Message indicating video calls not allowed if user enabled TTY Mode -->
+ <string name="video_call_not_allowed_if_tty_enabled">Please disable TTY Mode to upgrade to video calls.</string>
+
+ <!-- Description of the deflect target in the Slide unlock screen. [CHAR LIMIT=NONE] -->
+ <string name="qti_description_target_deflect">Deflect</string>
+ <string name="qti_description_deflect_error">Number not set. Provide the number via IMS settings and retry.</string>
+ <string name="qti_description_deflect_service_error">Call Deflection service is not supported.</string>
+ <!-- Message indicating call failed due to handover not feasible -->
+ <string name="call_failed_ho_not_feasible">Call was ended as LTE to 3G/2G handover was not feasible.</string>
+ <!-- Text for the onscreen "Add Participant" button -->
+ <string name="onscreenAddParticipant">Add Participant</string>
+
+ <!-- Title of the IMS to CS redial dialog -->
+ <string name="cs_redial_option">Redial Option</string>
+ <!-- Message text of the IMS to CS redial dialog -->
+ <string name="cs_redial_msg">Unable to make an IMS Video call, try to dial as non-IMS voice call
+?</string>
+ <!-- Yes option of the IMS to CS redial dialog -->
+ <string name="cs_redial_yes">Yes</string>
+ <!-- No option of the IMS to CS redial dialog -->
+ <string name="cs_redial_no">No</string>
+ <!-- Session modify cause unspecified -->
+ <string name="session_modify_cause_unspecified"></string>
+ <!-- Session modify cause code upgrade local request -->
+ <string name="session_modify_cause_upgrade_local_request">Call upgraded on user request</string>
+ <!-- Session modify cause code upgrade remote request -->
+ <string name="session_modify_cause_upgrade_remote_request">Call upgraded on remote user request</string>
+ <!-- Session modify cause code downgrade local request -->
+ <string name="session_modify_cause_downgrade_local_request">Call downgraded on user request</string>
+ <!-- Session modify cause code downgrade remote request -->
+ <string name="session_modify_cause_downgrade_remote_request">Call downgraded on remote user request</string>
+ <!-- Session modify cause code downgrade rtp timeout -->
+ <string name="session_modify_cause_downgrade_rtp_timeout">Call downgraded due to RTP timeout</string>
+ <!-- Session modify cause code downgrade qos -->
+ <string name="session_modify_cause_downgrade_qos">Call downgraded due to quality of service</string>
+ <!-- Session modify cause code downgrade packet loss -->
+ <string name="session_modify_cause_downgrade_packet_loss">Call downgraded due to packet loss</string>
+ <!-- Session modify cause code downgrade low thrput -->
+ <string name="session_modify_cause_downgrade_low_thrput">Call downgraded due to low throughput</string>
+ <!-- Session modify cause code downgrade thermal mitigation -->
+ <string name="session_modify_cause_downgrade_thermal_mitigation">Call downgraded due to thermal mitigation</string>
+ <!-- Session modify cause code downgrade lipsync -->
+ <string name="session_modify_cause_downgrade_lipsync">Call downgraded due to lipsync</string>
+ <!-- Session modify cause code downgrade generic error -->
+ <string name="session_modify_cause_downgrade_generic_error">Call downgraded due to generic error</string>
+
+ <!-- In-call screen: status label for an incoming conference call [CHAR LIMIT=25] -->
+ <string name="card_title_incoming_conf_call">Incoming conference call</string>
+ <!-- In-call screen: status label for an incoming video conference call [CHAR LIMIT=25] -->
+ <string name="card_title_incoming_video_conf_call">Incoming video conference</string>
+ <!-- The "label" of the in-call Notification for an incoming conference ringing call. [CHAR LIMIT=60] -->
+ <string name="notification_incoming_conf_call">Incoming conference call</string>
+ <!-- Title displayed in the overlay for incoming conference calls which include the name of the provider.
+ [CHAR LIMIT=40] -->
+ <string name="incoming_conf_via_template">Incoming conference via <xliff:g id="provider_name">%s</xliff:g></string>
+
+ <!-- Message indicating call failed due to low battery -->
+ <string name="call_failed_due_to_low_battery">Call is failed due to low battery</string>
+
+ <!-- VoWifi call quality indicators -->
+ <string name="vowifi_call_quality_good">Good Voice quality</string>
+ <string name="vowifi_call_quality_fair">Fair Voice quality</string>
+ <string name="vowifi_call_quality_poor">Poor Voice quality</string>
+
+ <!-- Title for low battery alert dialogue -->
+ <string name="low_battery">Low Battery Warning</string>
+ <!-- Yes option of the low battery alert dialog -->
+ <string name="low_battery_yes">Yes</string>
+ <!-- No option of the low battery alert dialog for active video calls -->
+ <string name="low_battery_no">No</string>
+ <!-- Message text of the low battery alert dialog in cases video call doesn't have downgrade capabilities -->
+ <string name="low_battery_hangup_msg">Do you want to hangup the call?</string>
+ <!-- Message text of the low battery alert dialog for MO/MT video calls -->
+ <string name="low_battery_msg">Your battery level is below 15%. Do you want to continue with the video call or convert it to Voice call</string>
+ <!-- No option of the low battery alert dialog for MO/MT Video calls -->
+ <string name="low_battery_convert">Convert</string>
+ <!-- International prefix source-->
+ <string-array name="international_idp" translatable="false">
+ <item>+33</item>
+ </string-array>
+
+ <!-- International prefix converted value-->
+ <string-array name="international_idp_values" translatable="false">
+ <item>"0033"</item>
+ </string-array>
+ <string name="video_call_downgrade_without_lte_toast">Move out of LTE coverage area downgrade the call.</string>
+ <string name="video_call">Video Calling</string>
+ <string name="video_call_cannot_upgrade">cannot accept video calls at this time</string>
+
+ <!-- Pop up menu options for picture mode -->
+ <string name="video_call_picture_mode_menu_title">Choose Picture Mode</string>
+ <string name="video_call_picture_mode_preview_video">Camera Preview</string>
+ <string name="video_call_picture_mode_incoming_video">Incoming Video</string>
+ <string name="video_call_picture_mode_cancel_option">Cancel</string>
+ <string name="video_call_picture_mode_save_option">Save</string>
+</resources>
diff --git a/InCallUI/res/values/strings.xml b/InCallUI/res/values/strings.xml
index 84eb14c..02e4bcf 100644
--- a/InCallUI/res/values/strings.xml
+++ b/InCallUI/res/values/strings.xml
@@ -450,7 +450,13 @@
<item>silent</item>
<item>never</item>
</string-array>
+ <string name="menu_start_record">Record call</string>
+ <string name="menu_stop_record">Stop recording</string>
+ <string name="recording_time_text">Recording</string>
+ <string name="volume_boost_notify_enabled">"Volume boost ON."</string>
+ <string name="volume_boost_notify_disabled">"Volume boost OFF."</string>
+ <string name="volume_boost_notify_unavailable">"Extra volume is not available when Wired/Bluetooth headset is used."</string>
<!-- Setting option name to pick ringtone (a list dialog comes up). [CHAR LIMIT=30] -->
<string name="ringtone_title" msgid="5379026328015343686">Phone ringtone</string>
@@ -493,6 +499,10 @@
<!-- Description of the "camera off" icon displayed when the device's camera is disabled during
a video call. [CHAR LIMIT=NONE] -->
<string name="camera_off_description">Camera off</string>
+ <!-- Set Subscription screen: label sub 1 -->
+ <string name="sub_1">SUB 1</string>
+ <!-- Set Subscription screen: label sub 2 -->
+ <string name="sub_2">SUB 2</string>
<!-- Used to inform the user that a call was received via a number other than the primary
phone number associated with their device. [CHAR LIMIT=16] -->
@@ -536,4 +546,12 @@
<string name="open_now">Open now</string>
<!-- Displayed when a place is closed. -->
<string name="closed_now">Closed now</string>
+ <string name="call_failed_due_to_validate_number">Unable to make video call, incorrect number format</string>
+ <string name="call_state_dialing">Dialing</string>
+ <string name="call_state_holding">Holding</string>
+ <string name="call_state_active">Active</string>
+ <string name="call_state_unknown">Unknown</string>
+ <string name="call_state_disconnecting">Disconnecting</string>
+ <string name="call_state_disconnected">Disconnected</string>
+ <string name="too_many_recipients">The maximum allowed participants in a 4G conference call are 6.</string>
</resources>
diff --git a/InCallUI/res/values/styles.xml b/InCallUI/res/values/styles.xml
index 11d6362..6f3d3d7 100644
--- a/InCallUI/res/values/styles.xml
+++ b/InCallUI/res/values/styles.xml
@@ -76,7 +76,7 @@
InCallActivity to have the correct Material style. -->
<style name="Theme.InCallScreen" parent="@android:style/Theme.Material.Light">
<item name="android:windowAnimationStyle">@null</item>
- <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowIsTranslucent">false</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="dialpad_key_button_touch_tint">@color/incall_dialpad_touch_tint</item>
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java
index 44ddfcd..5333058 100644
--- a/InCallUI/src/com/android/incallui/AnswerFragment.java
+++ b/InCallUI/src/com/android/incallui/AnswerFragment.java
@@ -51,6 +51,19 @@
public static final int TARGET_SET_FOR_VIDEO_WITH_SMS = 3;
public static final int TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST = 4;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_WITHOUT_SMS = 1000;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_WITH_SMS = 1001;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_ACCEPT_REJECT_REQUEST = 1003;
+ public static final int TARGET_SET_FOR_QTI_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST = 1004;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST = 1005;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST = 1006;
+ public static final int TARGET_SET_FOR_QTI_AUDIO_WITHOUT_SMS = 1007;
+ public static final int TARGET_SET_FOR_QTI_AUDIO_WITH_SMS = 1008;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_WITHOUT_SMS = 1009;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_WITH_SMS = 1010;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_WITHOUT_SMS = 1011;
+ public static final int TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_WITH_SMS = 1012;
+
/**
* This fragment implement no UI at all. Derived class should do it.
*/
@@ -261,6 +274,10 @@
getPresenter().onText();
}
+ public void onDeflect(Context context) {
+ getPresenter().onDeflect(context);
+ }
+
/**
* OnItemClickListener for the "Respond via SMS" popup.
*/
diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java
index 883b54f..b8b5185 100644
--- a/InCallUI/src/com/android/incallui/AnswerPresenter.java
+++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java
@@ -17,6 +17,9 @@
package com.android.incallui;
import android.content.Context;
+import android.provider.Settings;
+import android.telecom.VideoProfile;
+import android.telephony.SubscriptionManager;
import com.android.dialer.compat.UserManagerCompat;
import com.android.dialer.util.TelecomUtil;
@@ -24,6 +27,12 @@
import java.util.List;
+import org.codeaurora.ims.internal.IQtiImsExt;
+import org.codeaurora.ims.QtiImsException;
+import org.codeaurora.ims.QtiImsExtListenerBaseImpl;
+import org.codeaurora.ims.QtiImsExtManager;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
/**
* Presenter for the Incoming call widget. The {@link AnswerPresenter} handles the logic during
* incoming calls. It is also in charge of responding to incoming calls, so there needs to be
@@ -36,51 +45,101 @@
public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
implements CallList.CallUpdateListener, InCallPresenter.InCallUiListener,
InCallPresenter.IncomingCallListener,
- CallList.Listener {
+ CallList.Listener, CallList.ActiveSubChangeListener {
private static final String TAG = AnswerPresenter.class.getSimpleName();
- private String mCallId;
- private Call mCall = null;
+ private String mCallId[] = new String[InCallServiceImpl.sPhoneCount];
+ private Call mCall[] = new Call[InCallServiceImpl.sPhoneCount];
+ private final CallList mCalls = CallList.getInstance();
private boolean mHasTextMessages = false;
+ // Currently mVideoState is beeing used only for incoming calls.
+ // As there is only one incoming call allowed there is no need of array here.
+ private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
+
+ /* QtiImsExtListenerBaseImpl instance to handle call deflection response */
+ private QtiImsExtListenerBaseImpl imsInterfaceListener =
+ new QtiImsExtListenerBaseImpl() {
+
+ /* Handles call deflect response */
+ @Override
+ public void receiveCallDeflectResponse(int result) {
+ Log.w(this, "receiveCallDeflectResponse: " + result);
+ }
+ };
+ private static final int INVALID_PHONEID = -1;
@Override
public void onUiShowing(boolean showing) {
if (showing) {
- CallList.getInstance().addListener(this);
- final CallList calls = CallList.getInstance();
+ mCalls.addListener(this);
+ mCalls.addActiveSubChangeListener(this);
Call call;
- call = calls.getIncomingCall();
- if (call != null) {
- processIncomingCall(call);
+ // Consider incoming/waiting calls on both subscriptions
+ // for DSDA.
+ for (int i = 0; i < InCallServiceImpl.sPhoneCount; i++) {
+ int subId = QtiCallUtils.getSubId(i);
+ if (checkSubId(i)) {
+ call = mCalls.getCallWithState(Call.State.INCOMING, 0, subId);
+ if (call == null) {
+ call = mCalls.getCallWithState(Call.State.CALL_WAITING, 0, subId);
+ }
+ if (call != null) {
+ processIncomingCall(call);
+ }
+ } else {
+ Log.d(TAG, "No valid sub");
+ }
}
- call = calls.getVideoUpgradeRequestCall();
+ call = mCalls.getVideoUpgradeRequestCall();
Log.d(this, "getVideoUpgradeRequestCall call =" + call);
if (call != null) {
showAnswerUi(true);
processVideoUpgradeRequestCall(call);
}
} else {
- CallList.getInstance().removeListener(this);
+ mCalls.removeListener(this);
// This is necessary because the activity can be destroyed while an incoming call exists.
// This happens when back button is pressed while incoming call is still being shown.
- if (mCallId != null) {
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
+ for (int i = 0; i < InCallServiceImpl.sPhoneCount; i++) {
+ int subId = QtiCallUtils.getSubId(i);
+ if (checkSubId(i)) {
+ Call call = mCalls.getCallWithState(Call.State.INCOMING, 0, subId);
+ if (call == null) {
+ call = mCalls.getCallWithState(Call.State.CALL_WAITING, 0, subId);
+ }
+ if (call == null) {
+ call = mCalls.getCallWithState(Call.State.ACTIVE, 0, subId);
+ }
+ if (mCallId[i] != null && call == null) {
+ mCalls.removeCallUpdateListener(mCallId[i], this);
+ mCalls.removeActiveSubChangeListener(this);
+ }
+ } else {
+ Log.d(TAG, "No valid sub");
+ }
}
}
}
+ private boolean checkSubId(int phoneId) {
+ int subId = QtiCallUtils.getSubId(phoneId);
+ return (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
@Override
public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
+ int subId = call.getSubId();
+ int phoneId = QtiCallUtils.getPhoneId(subId);
Log.d(this, "onIncomingCall: " + this);
- Call modifyCall = CallList.getInstance().getVideoUpgradeRequestCall();
+ Call modifyCall = mCalls.getVideoUpgradeRequestCall();
if (modifyCall != null) {
showAnswerUi(false);
Log.d(this, "declining upgrade request id: ");
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
+ mCalls.removeCallUpdateListener(mCallId[phoneId], this);
InCallPresenter.getInstance().declineUpgradeRequest();
}
- if (!call.getId().equals(mCallId)) {
+ if (!call.getId().equals(mCallId[phoneId])) {
// A new call is coming in.
processIncomingCall(call);
}
@@ -97,15 +156,24 @@
@Override
public void onDisconnect(Call call) {
// no-op
+ int subId = call.getSubId();
+ int phoneId = QtiCallUtils.getPhoneId(subId);
+ if (call.equals(mCall[phoneId])) {
+ mCall[phoneId] = null;
+ }
}
- public void onSessionModificationStateChange(int sessionModificationState) {
+ public void onSessionModificationStateChange(Call call, int sessionModificationState) {
boolean isUpgradePending = sessionModificationState ==
Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
if (!isUpgradePending) {
// Stop listening for updates.
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
+ for (int i = 0; i < InCallServiceImpl.sPhoneCount; i++) {
+ if (mCallId[i] != null) {
+ mCalls.removeCallUpdateListener(mCallId[i], this);
+ }
+ }
showAnswerUi(false);
}
}
@@ -143,15 +211,19 @@
}
private void processIncomingCall(Call call) {
- mCallId = call.getId();
- mCall = call;
+ int subId = call.getSubId();
+ int phoneId = QtiCallUtils.getPhoneId(subId);
+ mCallId[phoneId] = call.getId();
+ mCall[phoneId] = call;
+ mCalls.addListener(this);
// Listen for call updates for the current call.
- CallList.getInstance().addCallUpdateListener(mCallId, this);
+ mCalls.addCallUpdateListener(mCallId[phoneId], this);
- Log.d(TAG, "Showing incoming for call id: " + mCallId + " " + this);
+ Log.d(TAG, "Showing incoming for call id: " + mCallId[phoneId] + " " + this);
if (showAnswerUi(true)) {
- final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId());
+ mVideoState = call.getVideoState();
+ final List<String> textMsgs = mCalls.getTextResponses(call.getId());
configureAnswerTargetsForSms(call, textMsgs);
}
}
@@ -171,11 +243,13 @@
private void processVideoUpgradeRequestCall(Call call) {
Log.d(this, " processVideoUpgradeRequestCall call=" + call);
- mCallId = call.getId();
- mCall = call;
+ int subId = call.getSubId();
+ int phoneId = QtiCallUtils.getPhoneId(subId);
+ mCallId[phoneId] = call.getId();
+ mCall[phoneId] = call;
// Listen for call updates for the current call.
- CallList.getInstance().addCallUpdateListener(mCallId, this);
+ CallList.getInstance().addCallUpdateListener(mCallId[phoneId], this);
final int currentVideoState = call.getVideoState();
final int modifyToVideoState = call.getRequestedVideoState();
@@ -192,8 +266,9 @@
return;
}
showAnswerUi(true);
- ui.showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST,
- modifyToVideoState);
+ ui.showTargets(QtiCallUtils.getSessionModificationOptions(getUi().getContext(),
+ currentVideoState, modifyToVideoState));
+
}
private boolean isEnabled(int videoState, int mask) {
@@ -205,12 +280,14 @@
Log.d(this, "onCallStateChange() " + call + " " + this);
if (call.getState() != Call.State.INCOMING) {
boolean isUpgradePending = isVideoUpgradePending(call);
+ int subId = call.getSubId();
+ int phoneId = QtiCallUtils.getPhoneId(subId);
if (!isUpgradePending) {
// Stop listening for updates.
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
+ mCalls.removeCallUpdateListener(mCallId[phoneId], this);
}
- final Call incall = CallList.getInstance().getIncomingCall();
+ final Call incall = mCalls.getIncomingCall();
if (incall != null || isUpgradePending) {
showAnswerUi(true);
} else {
@@ -218,26 +295,43 @@
}
mHasTextMessages = false;
- } else if (!mHasTextMessages) {
- final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId());
- if (textMsgs != null) {
- configureAnswerTargetsForSms(call, textMsgs);
- }
+ } else if (!mHasTextMessages || (mVideoState != call.getVideoState())) {
+ final List<String> textMsgs = mCalls.getTextResponses(call.getId());
+ mVideoState = call.getVideoState();
+ configureAnswerTargetsForSms(call, textMsgs);
}
}
+ // get active phoneId, for which call is visible to user
+ private int getActivePhoneId() {
+ int phoneId = INVALID_PHONEID;
+ if (mCalls.isDsdaEnabled()) {
+ int subId = mCalls.getActiveSubId();
+ phoneId = QtiCallUtils.getPhoneId(subId);
+ } else {
+ for (int i = 0; i < mCall.length; i++) {
+ if (mCall[i] != null) {
+ phoneId = i;
+ }
+ }
+ }
+ return phoneId;
+ }
+
public void onAnswer(int videoState, Context context) {
- if (mCallId == null) {
+ int phoneId = getActivePhoneId();
+ Log.i(this, "onAnswer mCallId:" + mCallId + "phoneId:" + phoneId);
+ if (mCallId == null || phoneId == INVALID_PHONEID) {
return;
}
- if (mCall.getSessionModificationState()
+ if (mCall[phoneId].getSessionModificationState()
== Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Log.d(this, "onAnswer (upgradeCall) mCallId=" + mCallId + " videoState=" + videoState);
InCallPresenter.getInstance().acceptUpgradeRequest(videoState, context);
} else {
Log.d(this, "onAnswer (answerCall) mCallId=" + mCallId + " videoState=" + videoState);
- TelecomAdapter.getInstance().answerCall(mCall.getId(), videoState);
+ InCallPresenter.getInstance().answerIncomingCall(context, videoState);
}
}
@@ -246,12 +340,13 @@
* reject since it seems to be more prevalent.
*/
public void onDecline(Context context) {
- Log.d(this, "onDecline " + mCallId);
- if (mCall.getSessionModificationState()
+ int phoneId = getActivePhoneId();
+ Log.d(this, "onDecline mCallId:" + mCallId + "phoneId:" + phoneId);
+ if (mCall[phoneId].getSessionModificationState()
== Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
InCallPresenter.getInstance().declineUpgradeRequest(context);
} else {
- TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null);
+ TelecomAdapter.getInstance().rejectCall(mCall[phoneId].getId(), false, null);
}
}
@@ -262,9 +357,42 @@
}
}
+ /**
+ * Deflect the incoming call.
+ */
+ public void onDeflect(Context context) {
+ if (mCallId == null) {
+ return;
+ }
+ Log.d(this, "onDeflect " + mCallId);
+
+ String deflectCallNumber = QtiImsExtUtils.getCallDeflectNumber(
+ context.getContentResolver());
+ /* If not set properly, inform user via toast */
+ if (deflectCallNumber == null) {
+ Log.w(this, "getCallDeflectNumber is null or Empty.");
+ QtiCallUtils.displayToast(context, R.string.qti_description_deflect_error);
+ } else {
+ int phoneId = 0;
+ try {
+ Log.d(this, "Sending deflect request with Phone id " + phoneId +
+ " to " + deflectCallNumber);
+ QtiImsExtManager.getInstance().sendCallDeflectRequest(phoneId,
+ deflectCallNumber, imsInterfaceListener);
+ } catch (QtiImsException e) {
+ Log.e(this, "sendCallDeflectRequest exception " + e);
+ QtiCallUtils.displayToast(getUi().getContext(),
+ R.string.qti_description_deflect_service_error);
+ }
+ }
+ }
+
public void rejectCallWithMessage(String message) {
- Log.d(this, "sendTextToDefaultActivity()...");
- TelecomAdapter.getInstance().rejectCall(mCall.getId(), true, message);
+ int phoneId = getActivePhoneId();
+ Log.i(this, "sendTextToDefaultActivity()...phoneId:" + phoneId);
+ if (phoneId != INVALID_PHONEID) {
+ TelecomAdapter.getInstance().rejectCall(mCall[phoneId].getId(), true, message);
+ }
onDismissDialog();
}
@@ -284,12 +412,25 @@
// Only present the user with the option to answer as a video call if the incoming call is
// a bi-directional video call.
- if (VideoUtils.isBidirectionalVideoCall(call)) {
+ if (VideoUtils.isVideoCall(call)) {
if (withSms) {
- getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITH_SMS);
+ getUi().showTargets(QtiCallUtils.getIncomingCallAnswerOptions(
+ getUi().getContext(), call.getVideoState(), withSms));
getUi().configureMessageDialog(textMsgs);
} else {
- getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITHOUT_SMS);
+ getUi().showTargets(QtiCallUtils.getIncomingCallAnswerOptions(
+ getUi().getContext(), call.getVideoState(), withSms));
+ }
+ } else if (isCallDeflectSupported()) {
+ /**
+ * Only present the user with the option to deflect call,
+ * if the incoming call is only an audio call.
+ */
+ if (withSms) {
+ getUi().showTargets(AnswerFragment.TARGET_SET_FOR_QTI_AUDIO_WITH_SMS);
+ getUi().configureMessageDialog(textMsgs);
+ } else {
+ getUi().showTargets(AnswerFragment.TARGET_SET_FOR_QTI_AUDIO_WITHOUT_SMS);
}
} else {
if (withSms) {
@@ -301,6 +442,23 @@
}
}
+ /**
+ * Checks the Settings to conclude on the call deflect support.
+ * Returns true if call deflect is possible, false otherwise.
+ */
+ private boolean isCallDeflectSupported() {
+ int value = 0;
+ try{
+ value = android.provider.Settings.Global.getInt(
+ getUi().getContext().getContentResolver(),
+ QtiImsExtUtils.QTI_IMS_DEFLECT_ENABLED);
+ } catch(Settings.SettingNotFoundException e) {
+ //do Nothing
+ Log.e(this, "isCallDeflectSupported exception " + e);
+ }
+ return (value == 1);
+ }
+
interface AnswerUi extends Ui {
public void onShowAnswerUi(boolean shown);
public void showTargets(int targetSet);
@@ -309,4 +467,23 @@
public void configureMessageDialog(List<String> textResponses);
public Context getContext();
}
+
+ @Override
+ public void onActiveSubChanged(int subId) {
+ final Call call = mCalls.getIncomingCall();
+ int phoneId = QtiCallUtils.getPhoneId(subId);
+ if ((call != null) && (call.getId() == mCallId[phoneId])) {
+ Log.d(this, "Show incoming for call id: " + mCallId[phoneId] + " " + this);
+ if (showAnswerUi(true)) {
+ final List<String> textMsgs = mCalls.getTextResponses(
+ call.getId());
+ configureAnswerTargetsForSms(call, textMsgs);
+ }
+ } else if ((call == null) && (mCalls.hasAnyLiveCall(subId))) {
+ Log.d(this, "Hide incoming for call id: " + mCallId[phoneId] + " " + this);
+ showAnswerUi(false);
+ } else {
+ Log.d(this, "No incoming call present for sub = " + subId + " " + this);
+ }
+ }
}
diff --git a/InCallUI/src/com/android/incallui/AudioModeProvider.java b/InCallUI/src/com/android/incallui/AudioModeProvider.java
index ea56dd6..a27371b 100644
--- a/InCallUI/src/com/android/incallui/AudioModeProvider.java
+++ b/InCallUI/src/com/android/incallui/AudioModeProvider.java
@@ -49,6 +49,10 @@
public void onAudioModeChange(int newMode, boolean muted) {
if (mAudioMode != newMode) {
mAudioMode = newMode;
+ InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity();
+ if (inCallActivity != null && inCallActivity.getCallCardFragment() != null) {
+ inCallActivity.getCallCardFragment().updateVbByAudioMode(newMode);
+ }
for (AudioModeListener l : mListeners) {
l.onAudioMode(mAudioMode);
}
diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java
index d552ecf..42890f2 100644
--- a/InCallUI/src/com/android/incallui/Call.java
+++ b/InCallUI/src/com/android/incallui/Call.java
@@ -20,7 +20,9 @@
import android.hardware.camera2.CameraCharacteristics;
import android.net.Uri;
import android.os.Bundle;
+import android.os.SystemClock;
import android.os.Trace;
+import android.os.RemoteException;
import android.telecom.Call.Details;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -29,7 +31,9 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
import android.telecom.VideoProfile;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import com.android.contacts.common.CallUtil;
@@ -46,6 +50,13 @@
import java.util.Locale;
import java.util.Objects;
+import org.codeaurora.ims.internal.IQtiImsExt;
+import org.codeaurora.ims.QtiCallConstants;
+import org.codeaurora.ims.QtiImsException;
+import org.codeaurora.ims.QtiImsExtListenerBaseImpl;
+import org.codeaurora.ims.QtiImsExtManager;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
/**
* Describes a single call and its state.
*/
@@ -359,6 +370,8 @@
}
};
+ boolean mIsActiveSub = false;
+ public static final String ACTIVE_SUBSCRIPTION = "active_sub";
private android.telecom.Call mTelecomCall;
private boolean mIsEmergencyCall;
private Uri mHandle;
@@ -381,6 +394,7 @@
private String mLastForwardedNumber;
private String mCallSubject;
private PhoneAccountHandle mPhoneAccountHandle;
+ private long mBaseChronometerTime = 0;
/**
* Indicates whether the phone account associated with this call supports specifying a call
@@ -581,6 +595,9 @@
mCallSubject = callSubject;
}
}
+ if (callExtras.containsKey(ACTIVE_SUBSCRIPTION)) {
+ mIsActiveSub = callExtras.getBoolean(ACTIVE_SUBSCRIPTION);
+ }
}
/**
@@ -657,6 +674,10 @@
}
}
+ public int getTrueState(){
+ return mState;
+ }
+
public void setState(int state) {
mState = state;
if (mState == State.INCOMING) {
@@ -742,9 +763,29 @@
int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
if ((capabilities & android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
+ if (CallList.getInstance().isDsdaEnabled()) {
+ List<android.telecom.Call> conferenceableCalls =
+ mTelecomCall.getConferenceableCalls();
+ boolean hasConferenceableCall = false;
+ if (!conferenceableCalls.isEmpty()){
+ int subId = getSubId();
+ for (android.telecom.Call call : conferenceableCalls) {
+ PhoneAccountHandle phHandle = call.getDetails().getAccountHandle();
+ if ((phHandle != null) && ((Integer.parseInt(phHandle.getId())) == subId)) {
+ hasConferenceableCall = true;
+ break;
+ }
+ }
+ }
+ if (!hasConferenceableCall &&
+ ((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE
+ & supportedCapabilities) == 0)) {
+ // Cannot merge calls if there are no calls to merge with.
+ return false;
+ }
// We allow you to merge if the capabilities allow it or if it is a call with
// conferenceable calls.
- if (mTelecomCall.getConferenceableCalls().isEmpty() &&
+ } else if (mTelecomCall.getConferenceableCalls().isEmpty() &&
((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE
& supportedCapabilities) == 0)) {
// Cannot merge calls if there are no calls to merge with.
@@ -776,6 +817,22 @@
return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
}
+ public int getSubId() {
+ PhoneAccountHandle ph = getAccountHandle();
+ if (ph != null) {
+ try {
+ if (ph.getId() != null) {
+ return Integer.parseInt(getAccountHandle().getId());
+ }
+ } catch (NumberFormatException e) {
+ Log.w(this, "sub id is not a number" + e);
+ }
+ return SubscriptionManager.getDefaultVoiceSubscriptionId();
+ } else {
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+ }
+
/**
* @return The {@link VideoCall} instance associated with the {@link android.telecom.Call}.
* Will return {@code null} until {@link #updateFromTelecomCall()} has registered a valid
@@ -855,7 +912,9 @@
* repeated calls to isEmergencyNumber.
*/
private void updateEmergencyCallState() {
- mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
+ Uri handle = mTelecomCall.getDetails().getHandle();
+ mIsEmergencyCall = QtiCallUtils.isEmergencyNumber
+ (handle == null ? "" : handle.getSchemeSpecificPart());
}
/**
@@ -898,6 +957,37 @@
return mSessionModificationState;
}
+ /* QtiImsExtListenerBaseImpl instance to handle call transfer response */
+ private QtiImsExtListenerBaseImpl mQtiImsInterfaceListener =
+ new QtiImsExtListenerBaseImpl() {
+
+ /* Handles call transfer response */
+ @Override
+ public void receiveCallTransferResponse(int result) {
+ Log.w(this, "receiveCallTransferResponse: " + result);
+ }
+ };
+
+ public int getTransferCapabilities() {
+ Bundle extras = getExtras();
+ return (extras == null)? 0 :
+ extras.getInt(QtiImsExtUtils.QTI_IMS_TRANSFER_EXTRA_KEY, 0);
+ }
+
+ public boolean sendCallTransferRequest(int type, String number) {
+ int phoneId = 0;
+ try {
+ Log.d(this, "sendCallTransferRequest: Phoneid-" + phoneId + " type-" + type +
+ " number: " + number);
+ QtiImsExtManager.getInstance().sendCallTransferRequest(phoneId, type, number,
+ mQtiImsInterfaceListener);
+ } catch (QtiImsException e) {
+ Log.e(this, "sendCallDeflectRequest exception " + e);
+ return false;
+ }
+ return true;
+ }
+
public LogState getLogState() {
return mLogState;
}
@@ -961,7 +1051,8 @@
}
return String.format(Locale.US, "[%s, %s, %s, %s, children:%s, parent:%s, " +
- "conferenceable:%s, videoState:%s, mSessionModificationState:%d, VideoSettings:%s]",
+ "conferenceable:%s, videoState:%s, mSessionModificationState:%d, VideoSettings:%s" +
+ ", mIsActivSub:%b]" ,
mId,
State.toString(getState()),
Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
@@ -971,10 +1062,38 @@
this.mTelecomCall.getConferenceableCalls(),
VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
mSessionModificationState,
- getVideoSettings());
+ getVideoSettings(), mIsActiveSub);
}
public String toSimpleString() {
return super.toString();
}
+
+ public boolean isIncomingConfCall() {
+ int callState = getState();
+ if (callState == State.INCOMING || callState == State.CALL_WAITING) {
+ Bundle extras = getExtras();
+ boolean incomingConf = (extras == null)? false :
+ extras.getBoolean(QtiImsExtUtils.QTI_IMS_INCOMING_CONF_EXTRA_KEY, false);
+ Log.d(this, "isIncomingConfCall = " + incomingConf);
+ return incomingConf;
+ }
+ return false;
+ }
+
+ public int getWifiQuality() {
+ Bundle extras = getExtras();
+ return (extras == null)? QtiCallConstants.VOWIFI_QUALITY_NONE :
+ extras.getInt(QtiCallConstants.VOWIFI_CALL_QUALITY_EXTRA_KEY,
+ QtiCallConstants.VOWIFI_QUALITY_NONE);
+ }
+
+ public void triggerCalcBaseChronometerTime() {
+ mBaseChronometerTime = getConnectTimeMillis() - System.currentTimeMillis()
+ + SystemClock.elapsedRealtime();
+ }
+
+ public long getCallDuration() {
+ return SystemClock.elapsedRealtime() - mBaseChronometerTime;
+ }
}
diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java
index 6b633ea..7694c5f 100644
--- a/InCallUI/src/com/android/incallui/CallButtonFragment.java
+++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java
@@ -28,7 +28,15 @@
import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_PAUSE_VIDEO;
import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWAP;
import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWITCH_CAMERA;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_TRANSFER_ASSURED;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_TRANSFER_BLIND;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_TRANSFER_CONSULTATIVE;
import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_UPGRADE_TO_VIDEO;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_RECORD;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_RXTX_VIDEO_CALL;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_RX_VIDEO_CALL;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_VO_VIDEO_CALL;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_ADD_PARTICIPANT;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -40,6 +48,8 @@
import android.graphics.drawable.StateListDrawable;
import android.os.Bundle;
import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+import android.telephony.PhoneNumberUtils;
import android.util.SparseIntArray;
import android.view.ContextThemeWrapper;
import android.view.HapticFeedbackConstants;
@@ -53,10 +63,14 @@
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnDismissListener;
import android.widget.PopupMenu.OnMenuItemClickListener;
+import android.widget.Toast;
+import com.android.contacts.common.CallUtil;
import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
import com.android.dialer.R;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
/**
* Fragment for call control buttons
*/
@@ -81,13 +95,21 @@
public static final int BUTTON_HOLD = 3;
public static final int BUTTON_SWAP = 4;
public static final int BUTTON_UPGRADE_TO_VIDEO = 5;
- public static final int BUTTON_SWITCH_CAMERA = 6;
- public static final int BUTTON_DOWNGRADE_TO_AUDIO = 7;
+ public static final int BUTTON_DOWNGRADE_TO_AUDIO = 6;
+ public static final int BUTTON_SWITCH_CAMERA = 7;
public static final int BUTTON_ADD_CALL = 8;
public static final int BUTTON_MERGE = 9;
public static final int BUTTON_PAUSE_VIDEO = 10;
public static final int BUTTON_MANAGE_VIDEO_CONFERENCE = 11;
- public static final int BUTTON_COUNT = 12;
+ public static final int BUTTON_TRANSFER_BLIND = 12;
+ public static final int BUTTON_TRANSFER_ASSURED = 13;
+ public static final int BUTTON_TRANSFER_CONSULTATIVE = 14;
+ public static final int BUTTON_RECORD = 15;
+ public static final int BUTTON_RXTX_VIDEO_CALL = 16;
+ public static final int BUTTON_RX_VIDEO_CALL = 17;
+ public static final int BUTTON_VO_VIDEO_CALL = 18;
+ public static final int BUTTON_ADD_PARTICIPANT = 19;
+ public static final int BUTTON_COUNT = 20;
}
private SparseIntArray mButtonVisibilityMap = new SparseIntArray(BUTTON_COUNT);
@@ -105,6 +127,14 @@
private CompoundButton mPauseVideoButton;
private ImageButton mOverflowButton;
private ImageButton mManageVideoCallConferenceButton;
+ private ImageButton mBlindTransferButton;
+ private ImageButton mAssuredTransferButton;
+ private ImageButton mConsultativeTransferButton;
+ private ImageButton mAddParticipantButton;
+ private ImageButton mRecordButton;
+ private ImageButton mRxTxVideoCallButton;
+ private ImageButton mRxVideoCallButton;
+ private ImageButton mVoVideoCallButton;
private PopupMenu mAudioModePopup;
private boolean mAudioModePopupVisible;
@@ -168,11 +198,27 @@
mMergeButton.setOnClickListener(this);
mPauseVideoButton = (CompoundButton) parent.findViewById(R.id.pauseVideoButton);
mPauseVideoButton.setOnClickListener(this);
+ mBlindTransferButton = (ImageButton) parent.findViewById(R.id.blindTransfer);
+ mBlindTransferButton.setOnClickListener(this);
+ mAssuredTransferButton = (ImageButton) parent.findViewById(R.id.assuredTransfer);
+ mAssuredTransferButton.setOnClickListener(this);
+ mConsultativeTransferButton = (ImageButton) parent.findViewById(R.id.consultativeTransfer);
+ mConsultativeTransferButton.setOnClickListener(this);
+ mAddParticipantButton = (ImageButton) parent.findViewById(R.id.addParticipant);
+ mAddParticipantButton.setOnClickListener(this);
mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton);
mOverflowButton.setOnClickListener(this);
mManageVideoCallConferenceButton = (ImageButton) parent.findViewById(
R.id.manageVideoCallConferenceButton);
mManageVideoCallConferenceButton.setOnClickListener(this);
+ mRecordButton = (ImageButton) parent.findViewById(R.id.recordButton);
+ mRecordButton.setOnClickListener(this);
+ mRxTxVideoCallButton = (ImageButton) parent.findViewById(R.id.rxtxVideoCallButton);
+ mRxTxVideoCallButton.setOnClickListener(this);
+ mRxVideoCallButton = (ImageButton) parent.findViewById(R.id.rxVedioCallButton);
+ mRxVideoCallButton.setOnClickListener(this);
+ mVoVideoCallButton = (ImageButton) parent.findViewById(R.id.volteCallButton);
+ mVoVideoCallButton.setOnClickListener(this);
return parent;
}
@@ -214,6 +260,8 @@
getPresenter().swapClicked();
} else if (id == R.id.dialpadButton) {
getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected());
+ } else if (id == R.id.addParticipant) {
+ getPresenter().addParticipantClicked();
} else if (id == R.id.changeToVideoButton) {
getPresenter().changeToVideoClicked();
} else if (id == R.id.changeToVoiceButton) {
@@ -224,12 +272,33 @@
} else if (id == R.id.pauseVideoButton) {
getPresenter().pauseVideoClicked(
!mPauseVideoButton.isSelected() /* pause */);
+ } else if (id == R.id.blindTransfer) {
+ getPresenter().callTransferClicked(QtiImsExtUtils.QTI_IMS_BLIND_TRANSFER);
+ } else if (id == R.id.assuredTransfer) {
+ getPresenter().callTransferClicked(QtiImsExtUtils.QTI_IMS_ASSURED_TRANSFER);
+ } else if (id == R.id.consultativeTransfer) {
+ getPresenter().callTransferClicked(QtiImsExtUtils.QTI_IMS_CONSULTATIVE_TRANSFER);
} else if (id == R.id.overflowButton) {
if (mOverflowPopup != null) {
+ updateRecordMenu();
mOverflowPopup.show();
}
} else if (id == R.id.manageVideoCallConferenceButton) {
onManageVideoCallConferenceClicked();
+ } else if (id == R.id.recordButton) {
+ if (!((InCallActivity) getActivity()).isCallRecording()) {
+ ((InCallActivity) getActivity()).startInCallRecorder();
+ mRecordButton.setBackgroundResource(R.drawable.btn_stop_record);
+ } else {
+ ((InCallActivity) getActivity()).stopInCallRecorder();
+ mRecordButton.setBackgroundResource(R.drawable.btn_start_record);
+ }
+ } else if(id == R.id.rxtxVideoCallButton){
+ getPresenter().changeToVideo(VideoProfile.STATE_BIDIRECTIONAL);
+ } else if(id == R.id.rxVedioCallButton){
+ getPresenter().changeToVideo(VideoProfile.STATE_RX_ENABLED);
+ } else if(id == R.id.volteCallButton){
+ getPresenter().changeToVideo(VideoProfile.STATE_AUDIO_ONLY);
} else {
Log.wtf(this, "onClick: unexpected");
return;
@@ -240,6 +309,14 @@
HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
+ private void updateRecordMenu() {
+ MenuItem item = mOverflowPopup.getMenu().findItem(BUTTON_RECORD);
+ if (item != null) {
+ item.setTitle(((InCallActivity) getActivity()).isCallRecording() ?
+ R.string.menu_stop_record : R.string.menu_start_record);
+ }
+ }
+
public void updateColors() {
MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors();
@@ -247,7 +324,7 @@
return;
}
- View[] compoundButtons = {
+ CompoundButton[] compoundButtons = {
mAudioButton,
mMuteButton,
mShowDialpadButton,
@@ -256,10 +333,18 @@
mPauseVideoButton
};
- for (View button : compoundButtons) {
+ for (CompoundButton button : compoundButtons) {
+ // Before applying background color, uncheck the button and re apply the
+ // saved checked state after background is changed. This is to fix
+ // an issue where button checked state is displayed wrongly after updating colors.
+ boolean isChecked = button.isChecked();
+ if (isChecked) Log.d(this, "updateColors: button:" + button + " is in checked state");
+ button.setChecked(false);
final LayerDrawable layers = (LayerDrawable) button.getBackground();
final RippleDrawable btnCompoundDrawable = compoundBackgroundDrawable(themeColors);
layers.setDrawableByLayerId(R.id.compoundBackgroundItem, btnCompoundDrawable);
+ button.setChecked(isChecked);
+ button.requestLayout();
}
ImageButton[] normalButtons = {
@@ -268,6 +353,9 @@
mChangeToVoiceButton,
mAddCallButton,
mMergeButton,
+ mBlindTransferButton,
+ mAssuredTransferButton,
+ mConsultativeTransferButton,
mOverflowButton
};
@@ -275,6 +363,7 @@
final LayerDrawable layers = (LayerDrawable) button.getBackground();
final RippleDrawable btnDrawable = backgroundDrawable(themeColors);
layers.setDrawableByLayerId(R.id.backgroundItem, btnDrawable);
+ button.requestLayout();
}
mCurrentThemeColors = themeColors;
@@ -360,8 +449,16 @@
mAddCallButton.setEnabled(isEnabled);
mMergeButton.setEnabled(isEnabled);
mPauseVideoButton.setEnabled(isEnabled);
+ mBlindTransferButton.setEnabled(isEnabled);
+ mAssuredTransferButton.setEnabled(isEnabled);
+ mConsultativeTransferButton.setEnabled(isEnabled);
mOverflowButton.setEnabled(isEnabled);
mManageVideoCallConferenceButton.setEnabled(isEnabled);
+ mAddParticipantButton.setEnabled(isEnabled);
+ mRecordButton.setEnabled(isEnabled);
+ mRxTxVideoCallButton.setEnabled(isEnabled);
+ mRxVideoCallButton.setEnabled(isEnabled);
+ mVoVideoCallButton.setEnabled(isEnabled);
}
@Override
@@ -396,12 +493,28 @@
return mSwitchCameraButton;
} else if (id == BUTTON_ADD_CALL) {
return mAddCallButton;
+ } else if (id == BUTTON_ADD_PARTICIPANT) {
+ return mAddParticipantButton;
} else if (id == BUTTON_MERGE) {
return mMergeButton;
} else if (id == BUTTON_PAUSE_VIDEO) {
return mPauseVideoButton;
} else if (id == BUTTON_MANAGE_VIDEO_CONFERENCE) {
return mManageVideoCallConferenceButton;
+ } else if (id == BUTTON_TRANSFER_BLIND) {
+ return mBlindTransferButton;
+ } else if (id == BUTTON_TRANSFER_ASSURED) {
+ return mAssuredTransferButton;
+ } else if (id == BUTTON_TRANSFER_CONSULTATIVE) {
+ return mConsultativeTransferButton;
+ } else if (id == BUTTON_RECORD) {
+ return mRecordButton;
+ } else if (id == BUTTON_RXTX_VIDEO_CALL) {
+ return mRxTxVideoCallButton;
+ } else if (id == BUTTON_RX_VIDEO_CALL) {
+ return mRxVideoCallButton;
+ } else if (id == BUTTON_VO_VIDEO_CALL) {
+ return mVoVideoCallButton;
} else {
Log.w(this, "Invalid button id");
return null;
diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
index defafda..8af8835 100644
--- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
@@ -26,17 +26,29 @@
import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_PAUSE_VIDEO;
import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWAP;
import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWITCH_CAMERA;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_TRANSFER_ASSURED;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_TRANSFER_BLIND;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_TRANSFER_CONSULTATIVE;
import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_UPGRADE_TO_VIDEO;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_RECORD;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_RXTX_VIDEO_CALL;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_RX_VIDEO_CALL;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_VO_VIDEO_CALL;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_ADD_PARTICIPANT;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.telecom.CallAudioState;
import android.telecom.InCallService.VideoCall;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
import android.telecom.VideoProfile;
+import android.widget.Toast;
import com.android.contacts.common.compat.CallSdkCompat;
import com.android.contacts.common.compat.SdkVersionOverride;
+import com.android.dialer.util.PresenceHelper;
import com.android.dialer.compat.UserManagerCompat;
import com.android.incallui.AudioModeProvider.AudioModeListener;
import com.android.incallui.InCallCameraManager.Listener;
@@ -46,12 +58,14 @@
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
/**
* Logic for call buttons.
*/
public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
implements InCallStateListener, AudioModeListener, IncomingCallListener,
- InCallDetailsListener, CanAddCallListener, Listener {
+ InCallDetailsListener, CanAddCallListener, CallList.ActiveSubChangeListener, Listener {
private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted";
private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state";
@@ -59,6 +73,12 @@
private Call mCall;
private boolean mAutomaticallyMuted = false;
private boolean mPreviousMuteState = false;
+ private static final int MAX_PARTICIPANTS_LIMIT = 6;
+ private boolean mEnhanceEnable = false;
+
+ // NOTE: Capability constant definition has been duplicated to avoid bundling
+ // the Dialer with Frameworks.
+ private static final int CAPABILITY_ADD_PARTICIPANT = 0x02000000;
public CallButtonPresenter() {
}
@@ -67,6 +87,8 @@
public void onUiReady(CallButtonUi ui) {
super.onUiReady(ui);
+ mEnhanceEnable = ui.getContext().getResources().getBoolean(
+ R.bool.config_enable_enhance_video_call_ui);
AudioModeProvider.getInstance().addListener(this);
// register for call state changes last
@@ -76,6 +98,7 @@
inCallPresenter.addDetailsListener(this);
inCallPresenter.addCanAddCallListener(this);
inCallPresenter.getInCallCameraManager().addCameraSelectionListener(this);
+ CallList.getInstance().addActiveSubChangeListener(this);
// Update the buttons state immediately for the current call
onStateChange(InCallState.NO_CALLS, inCallPresenter.getInCallState(),
@@ -92,6 +115,7 @@
InCallPresenter.getInstance().removeDetailsListener(this);
InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this);
InCallPresenter.getInstance().removeCanAddCallListener(this);
+ CallList.getInstance().removeActiveSubChangeListener(this);
}
@Override
@@ -244,15 +268,45 @@
}
public void mergeClicked() {
+ if (getUi().getContext().getResources().getBoolean(
+ R.bool.add_multi_participants_enabled)){
+ int participantsCount = 0;
+ if (mCall.isConferenceCall()) {
+ participantsCount = mCall.getChildCallIds().size();
+ } else {
+ Call backgroundCall = CallList.getInstance().getBackgroundCall();
+ if (backgroundCall != null && backgroundCall.isConferenceCall()) {
+ participantsCount = backgroundCall.getChildCallIds().size();
+ }
+ }
+ Log.i(this, "Number of participantsCount is " + participantsCount);
+ if (participantsCount >= MAX_PARTICIPANTS_LIMIT) {
+ Toast.makeText(getUi().getContext(),
+ R.string.too_many_recipients, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ }
TelecomAdapter.getInstance().merge(mCall.getId());
+ InCallAudioManager.getInstance().onMergeClicked();
+ }
+
+ public void addParticipantClicked() {
+ if (getUi().getContext().getResources().getBoolean(
+ R.bool.add_multi_participants_enabled)){
+ InCallPresenter.getInstance().sendAddMultiParticipantsIntent();
+ return;
+ }
+ InCallPresenter.getInstance().sendAddParticipantIntent();
}
public void addCallClicked() {
- // Automatically mute the current call
- mAutomaticallyMuted = true;
- mPreviousMuteState = AudioModeProvider.getInstance().getMute();
- // Simulate a click on the mute button
- muteClicked(true);
+ if (!QtiImsExtUtils.isCarrierOneSupported()) {
+ // Automatically mute the current call
+ mAutomaticallyMuted = true;
+ mPreviousMuteState = AudioModeProvider.getInstance().getMute();
+ // Simulate a click on the mute button
+ muteClicked(true);
+ }
TelecomAdapter.getInstance().addCall();
}
@@ -264,6 +318,11 @@
VideoProfile videoProfile = new VideoProfile(VideoProfile.STATE_AUDIO_ONLY);
videoCall.sendSessionModifyRequest(videoProfile);
+
+ if (QtiCallUtils.useCustomVideoUi(getUi().getContext())) {
+ InCallAudioManager.getInstance().onModifyCallClicked(mCall,
+ VideoProfile.STATE_AUDIO_ONLY);
+ }
}
public void showDialpadClicked(boolean checked) {
@@ -272,6 +331,12 @@
}
public void changeToVideoClicked() {
+ final Context context = getUi().getContext();
+ if (QtiCallUtils.useExt(context)) {
+ QtiCallUtils.displayModifyCallOptions(mCall, context);
+ return;
+ }
+
VideoCall videoCall = mCall.getVideoCall();
if (videoCall == null) {
return;
@@ -283,8 +348,28 @@
VideoProfile videoProfile = new VideoProfile(currUnpausedVideoState);
videoCall.sendSessionModifyRequest(videoProfile);
mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE);
+
+ if (QtiCallUtils.useCustomVideoUi(context)) {
+ InCallAudioManager.getInstance().onModifyCallClicked(mCall,
+ currUnpausedVideoState);
+ }
}
+ public void changeToVideo(int videoState) {
+ final Context context = getUi().getContext();
+ if(mCall == null) {
+ return;
+ }
+
+ if(VideoProfile.isVideo(videoState) &&
+ !PresenceHelper.getVTCapability(mCall.getNumber())) {
+ Toast.makeText(context,context.getString(R.string.video_call_cannot_upgrade),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ final VideoProfile videoProfile = new VideoProfile(videoState);
+ QtiCallUtils.changeToVideoCall(mCall, videoProfile, context);
+ }
/**
* Switches the camera between the front-facing and back-facing camera.
* @param useFrontFacingCamera True if we should switch to using the front-facing camera, or
@@ -340,6 +425,28 @@
getUi().setVideoPaused(pause);
}
+ public void callTransferClicked(int type) {
+ String number = null;
+ Context mContext = getUi().getContext();
+ if (type != QtiImsExtUtils.QTI_IMS_CONSULTATIVE_TRANSFER) {
+ /**
+ * Since there are no editor options available to provide a number during
+ * blind or assured transfer, for now, making use of the existing
+ * call deflection editor to provide the required number.
+ */
+ number = QtiImsExtUtils.getCallDeflectNumber(mContext.getContentResolver());
+ if (number == null) {
+ QtiCallUtils.displayToast(mContext, R.string.qti_ims_transfer_num_error);
+ return;
+ }
+ }
+
+ boolean status = mCall.sendCallTransferRequest(type, number);
+ if (!status) {
+ QtiCallUtils.displayToast(mContext, R.string.qti_ims_transfer_request_error);
+ }
+ }
+
private void updateUi(InCallState state, Call call) {
Log.d(this, "Updating call UI for call: ", call);
@@ -384,9 +491,53 @@
&& UserManagerCompat.isUserUnlocked(ui.getContext());
final boolean showMerge = call.can(
android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
- final boolean showUpgradeToVideo = !isVideo && hasVideoCallCapabilities(call);
+ final boolean useExt = QtiCallUtils.useExt(ui.getContext());
+ final boolean useCustomVideoUi =
+ QtiCallUtils.useCustomVideoUi(ui.getContext());
+ final boolean isCallActive = call.getState() == Call.State.ACTIVE;
+
+ final boolean showUpgradeToVideo =
+ /* When useExt is true, show upgrade button for an active/held
+ call if the call has either voice or video capabilities */
+ ((useExt && QtiCallUtils.hasVoiceOrVideoCapabilities(call)) ||
+ /* When useCustomVideoUi is true, show upgrade button for an active/held
+ voice call only if the current call has video capabilities */
+ (useCustomVideoUi && !isVideo && hasVideoCallCapabilities(call))
+ && (isCallActive || isCallOnHold)) ||
+ /* When useExt and custom UI are false, default to Google behaviour */
+ (!isVideo && !useExt && !useCustomVideoUi && hasVideoCallCapabilities(call));
+
final boolean showDowngradeToAudio = isVideo && isDowngradeToAudioSupported(call);
+ final int callState = call.getState();
+
+ final boolean showRecord = ((callState == Call.State.ACTIVE
+ || callState == Call.State.ONHOLD)
+ && (ui.getContext().getResources().getBoolean(R.bool.enable_call_record)));
+
final boolean showMute = call.can(android.telecom.Call.Details.CAPABILITY_MUTE);
+ int callTransferCapabilities = call.isEmergencyCall()? 0 : call.getTransferCapabilities();
+ boolean showAddParticipant = call.can(CAPABILITY_ADD_PARTICIPANT);
+ if (ui.getContext().getResources().getBoolean(
+ R.bool.add_participant_only_in_conference)) {
+ showAddParticipant = showAddParticipant&&(call.isConferenceCall());
+ }
+
+ boolean showRxTx = false;
+ boolean showRx = false;
+ boolean showVolte = false;
+
+ if (mEnhanceEnable && hasVideoCallCapabilities(call)) {
+ boolean isAudioAndVtCap = (VideoProfile.isAudioOnly(mCall.getVideoState()) &&
+ PresenceHelper.getVTCapability(call.getNumber()));
+ showRxTx = ((VideoProfile.isReceptionEnabled(mCall.getVideoState()) &&
+ !VideoProfile.isBidirectional(mCall.getVideoState())) || isAudioAndVtCap);
+ //"hide me" show be show if call is video call or voice call only, "hide me"
+ //is mean that call can upgrade to Rx video call for voice call only.
+ showRx = (VideoProfile.isBidirectional(mCall.getVideoState()) || isAudioAndVtCap);
+ showVolte = VideoProfile.isVideo(mCall.getVideoState());
+ Log.v(this, "updateButtonsState showRxTx = " + showRxTx +
+ " showRx" + showRx + " showVolte = " + showVolte);
+ }
ui.showButton(BUTTON_AUDIO, true);
ui.showButton(BUTTON_SWAP, showSwap);
@@ -394,17 +545,42 @@
ui.setHold(isCallOnHold);
ui.showButton(BUTTON_MUTE, showMute);
ui.showButton(BUTTON_ADD_CALL, showAddCall);
- ui.showButton(BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo);
- ui.showButton(BUTTON_DOWNGRADE_TO_AUDIO, showDowngradeToAudio);
+ ui.showButton(BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo && !mEnhanceEnable);
+ ui.showButton(BUTTON_DOWNGRADE_TO_AUDIO, showDowngradeToAudio && !useExt);
ui.showButton(BUTTON_SWITCH_CAMERA, isVideo);
- ui.showButton(BUTTON_PAUSE_VIDEO, isVideo);
+ ui.showButton(BUTTON_PAUSE_VIDEO, isVideo && !useExt && !useCustomVideoUi &&
+ !mEnhanceEnable);
if (isVideo) {
getUi().setVideoPaused(!VideoUtils.isTransmissionEnabled(call));
}
ui.showButton(BUTTON_DIALPAD, true);
ui.showButton(BUTTON_MERGE, showMerge);
+ ui.showButton(BUTTON_ADD_PARTICIPANT, showAddParticipant && !mEnhanceEnable);
+ ui.showButton(BUTTON_RECORD, showRecord);
+
+ /* Depending on the transfer capabilities, display the corresponding buttons */
+ if ((callTransferCapabilities & QtiImsExtUtils.QTI_IMS_CONSULTATIVE_TRANSFER) != 0) {
+ ui.showButton(BUTTON_TRANSFER_BLIND, true);
+ ui.showButton(BUTTON_TRANSFER_ASSURED, true);
+ ui.showButton(BUTTON_TRANSFER_CONSULTATIVE, true);
+ } else if ((callTransferCapabilities & QtiImsExtUtils.QTI_IMS_BLIND_TRANSFER) != 0) {
+ ui.showButton(BUTTON_TRANSFER_BLIND, true);
+ ui.showButton(BUTTON_TRANSFER_ASSURED, true);
+ ui.showButton(BUTTON_TRANSFER_CONSULTATIVE, false);
+ } else {
+ ui.showButton(BUTTON_TRANSFER_BLIND, false);
+ ui.showButton(BUTTON_TRANSFER_ASSURED, false);
+ ui.showButton(BUTTON_TRANSFER_CONSULTATIVE, false);
+ }
+ if (mEnhanceEnable) {
+ Log.v(this, "Add three new buttons");
+ ui.showButton(BUTTON_RXTX_VIDEO_CALL, showRxTx);
+ ui.showButton(BUTTON_RX_VIDEO_CALL, showRx);
+ ui.showButton(BUTTON_VO_VIDEO_CALL, showVolte);
+ }
ui.updateButtonStates();
+ ui.updateColors();
}
private boolean hasVideoCallCapabilities(Call call) {
@@ -474,6 +650,7 @@
*/
void updateButtonStates();
Context getContext();
+ public void updateColors();
}
@Override
@@ -483,4 +660,16 @@
}
getUi().setCameraSwitched(!isUsingFrontFacingCamera);
}
+
+ public void onActiveSubChanged(int subId) {
+ InCallState state = InCallPresenter.getInstance()
+ .getPotentialStateFromCallList(CallList.getInstance());
+
+ onStateChange(null, state, CallList.getInstance());
+ Log.d(this, "onActiveSubChanged");
+ CallButtonUi ui = getUi();
+ if (ui != null) {
+ ui.updateColors();
+ }
+ }
}
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 39dd5ea..1c4890b 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -20,25 +20,38 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
+import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.Trace;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
+import android.content.ContentResolver;
+import android.media.AudioManager;
+import android.provider.Settings;
import android.telecom.DisconnectCause;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
@@ -57,6 +70,8 @@
import android.widget.Toast;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
+import android.telecom.CallAudioState;
+
import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
import com.android.contacts.common.widget.FloatingActionButtonController;
import com.android.dialer.R;
@@ -112,6 +127,11 @@
*/
private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MS = 500;
+ private static final String RECORD_STATE_CHANGED =
+ "com.qualcomm.qti.phonefeature.RECORD_STATE_CHANGED";
+
+ private static final int MESSAGE_TIMER = 1;
+
private AnimatorSet mAnimatorSet;
private int mShrinkAnimationDuration;
private int mFabNormalDiameter;
@@ -143,6 +163,8 @@
private ViewGroup mPrimaryCallInfo;
private View mCallButtonsContainer;
private ImageView mPhotoSmall;
+ private ImageButton mVbButton;
+ private AudioManager mAudioManager;
// Secondary caller info
private View mSecondaryCallInfo;
@@ -166,6 +188,10 @@
// Dark number info bar
private TextView mInCallMessageLabel;
+ private InCallActivity mInCallActivity;
+ private TextView mRecordingTimeLabel;
+ private TextView mRecordingIcon;
+
private FloatingActionButtonController mFloatingActionButtonController;
private View mFloatingActionButtonContainer;
private ImageButton mFloatingActionButton;
@@ -187,11 +213,61 @@
private boolean mCallStateLabelResetPending = false;
private Handler mHandler;
+ private BroadcastReceiver recorderStateReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!RECORD_STATE_CHANGED.equals(intent.getAction())) {
+ return;
+ }
+
+ if (mInCallActivity.isCallRecording()) {
+ recorderHandler.sendEmptyMessage(MESSAGE_TIMER);
+ } else {
+ mRecordingTimeLabel.setVisibility(View.GONE);
+ mRecordingIcon.setVisibility(View.GONE);
+ }
+ }
+ };
+
+ private Handler recorderHandler = new Handler() {
+
+ @Override
+ public void handleMessage(Message msg) {
+
+ switch (msg.what) {
+ case MESSAGE_TIMER:
+ if (!mInCallActivity.isCallRecording()) {
+ break;
+ }
+
+ String recordingTime = mInCallActivity.getCallRecordingTime();
+
+ if (!TextUtils.isEmpty(recordingTime)) {
+ mRecordingTimeLabel.setVisibility(View.VISIBLE);
+ showCallRecordingElapsedTime(recordingTime);
+ mRecordingIcon.setVisibility(View.VISIBLE);
+ }
+
+ if (!recorderHandler.hasMessages(MESSAGE_TIMER)) {
+ sendEmptyMessageDelayed(MESSAGE_TIMER, 1000);
+ }
+
+ break;
+ }
+ }
+ };
+
/**
* Determines if secondary call info is populated in the secondary call info UI.
*/
private boolean mHasSecondaryCallInfo = false;
+ private static final int TTY_MODE_OFF = 0;
+ private static final int TTY_MODE_HCO = 2;
+
+ private static final String VOLUME_BOOST = "volume_boost";
+
@Override
public CallCardPresenter.CallCardUi getUi() {
return this;
@@ -219,6 +295,21 @@
if (savedInstanceState != null) {
mIsDialpadShowing = savedInstanceState.getBoolean(IS_DIALPAD_SHOWING_KEY, false);
}
+ mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(RECORD_STATE_CHANGED);
+ getActivity().registerReceiver(recorderStateReceiver, filter);
+
+ mInCallActivity = (InCallActivity) getActivity();
+ if (mInCallActivity.isCallRecording()) {
+ recorderHandler.sendEmptyMessage(MESSAGE_TIMER);
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ getActivity().unregisterReceiver(recorderStateReceiver);
}
@Override
@@ -334,6 +425,21 @@
mPrimaryName.setElegantTextHeight(false);
mCallStateLabel.setElegantTextHeight(false);
mCallSubject = (TextView) view.findViewById(R.id.callSubject);
+
+ mVbButton = (ImageButton) view.findViewById(R.id.volumeBoost);
+ if (null != mVbButton) {
+ mVbButton.setOnClickListener(mVbListener);
+ }
+ mRecordingTimeLabel = (TextView) view.findViewById(R.id.recordingTime);
+ mRecordingIcon = (TextView) view.findViewById(R.id.recordingIcon);
+ }
+
+ private void showCallRecordingElapsedTime(String time) {
+ if (mRecordingTimeLabel.getVisibility() != View.VISIBLE) {
+ AnimUtils.fadeIn(mRecordingTimeLabel, AnimUtils.DEFAULT_DURATION);
+ }
+
+ mRecordingTimeLabel.setText(time);
}
@Override
@@ -399,6 +505,8 @@
// Determine how much space there is below or to the side of the call card.
final float spaceBesideCallCard = getSpaceBesideCallCard();
+ doActionOnPredraw(visible, isLayoutRtl, videoView, spaceBesideCallCard);
+
// We need to translate the video surface, but we need to know its position after the layout
// has occurred so use a {@code ViewTreeObserver}.
final ViewTreeObserver observer = getView().getViewTreeObserver();
@@ -408,69 +516,73 @@
// We don't want to continue getting called.
getView().getViewTreeObserver().removeOnPreDrawListener(this);
- float videoViewTranslation = 0f;
-
- // Translate the call card to its pre-animation state.
- if (!mIsLandscape) {
- mPrimaryCallCardContainer.setTranslationY(visible ?
- -mPrimaryCallCardContainer.getHeight() : 0);
-
- ViewGroup.LayoutParams p = videoView.getLayoutParams();
- videoViewTranslation = p.height / 2 - spaceBesideCallCard / 2;
- }
-
- // Perform animation of video view.
- ViewPropertyAnimator videoViewAnimator = videoView.animate()
- .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
- .setDuration(mVideoAnimationDuration);
- if (mIsLandscape) {
- videoViewAnimator
- .translationX(visible ? videoViewTranslation : 0);
- } else {
- videoViewAnimator
- .translationY(visible ? videoViewTranslation : 0);
- }
- videoViewAnimator.start();
-
- // Animate the call card sliding.
- ViewPropertyAnimator callCardAnimator = mPrimaryCallCardContainer.animate()
- .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
- .setDuration(mVideoAnimationDuration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- if (!visible) {
- mPrimaryCallCardContainer.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- if (visible) {
- mPrimaryCallCardContainer.setVisibility(View.VISIBLE);
- }
- }
- });
-
- if (mIsLandscape) {
- float translationX = mPrimaryCallCardContainer.getWidth();
- translationX *= isLayoutRtl ? 1 : -1;
- callCardAnimator
- .translationX(visible ? 0 : translationX)
- .start();
- } else {
- callCardAnimator
- .translationY(visible ? 0 : -mPrimaryCallCardContainer.getHeight())
- .start();
- }
-
+ doActionOnPredraw(visible, isLayoutRtl, videoView, spaceBesideCallCard);
return true;
}
});
}
+ private void doActionOnPredraw(final boolean visible, final boolean isLayoutRtl,
+ final View videoView, final float spaceBesideCallCard) {
+ float videoViewTranslation = 0f;
+
+ // Translate the call card to its pre-animation state.
+ if (!mIsLandscape) {
+ mPrimaryCallCardContainer.setTranslationY(visible ?
+ -mPrimaryCallCardContainer.getHeight() : 0);
+
+ ViewGroup.LayoutParams p = videoView.getLayoutParams();
+ videoViewTranslation = p.height / 2 - spaceBesideCallCard / 2;
+ }
+
+ // Perform animation of video view.
+ ViewPropertyAnimator videoViewAnimator = videoView.animate()
+ .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
+ .setDuration(mVideoAnimationDuration);
+ if (mIsLandscape) {
+ videoViewAnimator
+ .translationX(visible ? videoViewTranslation : 0);
+ } else {
+ videoViewAnimator
+ .translationY(visible ? videoViewTranslation : 0);
+ }
+ videoViewAnimator.start();
+
+ // Animate the call card sliding.
+ ViewPropertyAnimator callCardAnimator = mPrimaryCallCardContainer.animate()
+ .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
+ .setDuration(mVideoAnimationDuration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (!visible) {
+ mPrimaryCallCardContainer.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (visible) {
+ mPrimaryCallCardContainer.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+
+ if (mIsLandscape) {
+ float translationX = mPrimaryCallCardContainer.getWidth();
+ translationX *= isLayoutRtl ? 1 : -1;
+ callCardAnimator
+ .translationX(visible ? 0 : translationX)
+ .start();
+ } else {
+ callCardAnimator
+ .translationY(visible ? 0 : -mPrimaryCallCardContainer.getHeight())
+ .start();
+ }
+ }
+
/**
* Determines the amount of space below the call card for portrait layouts), or beside the
* call card for landscape layouts.
@@ -638,11 +750,17 @@
public void setSecondaryInfoVisible(final boolean visible) {
boolean wasVisible = mSecondaryCallInfo.isShown();
final boolean isVisible = visible && mHasSecondaryCallInfo;
+ // If hide request is coming when InCallUI is in background, force the view to hide.
+ final boolean needForceHide = !isVisible &&
+ mSecondaryCallInfo.getVisibility() == View.VISIBLE &&
+ (!InCallPresenter.getInstance().isShowingInCallUi() ||
+ InCallPresenter.getInstance().isShowingManageConferenceUi());
Log.v(this, "setSecondaryInfoVisible: wasVisible = " + wasVisible + " isVisible = "
- + isVisible);
+ + isVisible + " isFg = " + InCallPresenter.getInstance().isShowingInCallUi()
+ + " view visibility = " + mSecondaryCallInfo.getVisibility());
// If visibility didn't change, nothing to do.
- if (wasVisible == isVisible) {
+ if (wasVisible == isVisible && !needForceHide) {
return;
}
@@ -728,6 +846,8 @@
sessionModificationState, disconnectCause, connectionLabel, isGatewayCall, isWifi,
isConference, isWorkCall);
+ updateVbByCall(state);
+
Log.v(this, "setCallState " + callStateLabel.getCallStateLabel());
Log.v(this, "AutoDismiss " + callStateLabel.isAutoDismissing());
Log.v(this, "DisconnectCause " + disconnectCause.toString());
@@ -742,9 +862,9 @@
// Check if the call subject is showing -- if it is, we want to bypass showing the call
// state.
boolean isSubjectShowing = mCallSubject.getVisibility() == View.VISIBLE;
-
+ boolean isSubChanged = (callStateIcon != mCallStateIcon.getDrawable());
if (TextUtils.equals(callStateLabel.getCallStateLabel(), mCallStateLabel.getText()) &&
- !isSubjectShowing) {
+ !isSubjectShowing && !isSubChanged) {
// Nothing to do if the labels are the same
if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
mCallStateLabel.clearAnimation();
@@ -778,6 +898,18 @@
mCallStateIcon.setAlpha(1.0f);
mCallStateIcon.setImageDrawable(callStateIcon);
+ MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors();
+ if (themeColors != null) {
+ // Change the alpha value in the 32 bit color of sim card, because the color of
+ // call background changed with the color of sim card.
+ // Set the tint mode to SCREEN to avoid the slot number in the sim icon to be
+ // covered.
+ int stateIconColor = (themeColors.mPrimaryColor & 0x00ffffff) | 0x7f000000;
+ mCallStateIcon.setImageTintMode(PorterDuff.Mode.SCREEN);
+ mCallStateIcon.setImageTintList(ColorStateList.valueOf(stateIconColor));
+ Log.d(this, "Set tint of call state icon to " + stateIconColor);
+ }
+
if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED
|| TextUtils.isEmpty(callStateLabel.getCallStateLabel())) {
mCallStateIcon.clearAnimation();
@@ -788,6 +920,9 @@
if (callStateIcon instanceof AnimationDrawable) {
((AnimationDrawable) callStateIcon).start();
}
+ if (isSubChanged) {
+ updateColors();
+ }
} else {
mCallStateIcon.clearAnimation();
@@ -796,6 +931,7 @@
mCallStateIcon.setAlpha(0.0f);
mCallStateIcon.setVisibility(View.GONE);
}
+ mCallStateIcon.requestLayout();
if (VideoUtils.isVideoCall(videoState)
|| (state == Call.State.ACTIVE && sessionModificationState
@@ -1033,9 +1169,7 @@
case Call.State.ACTIVE:
// We normally don't show a "call state label" at all in this state
// (but we can use the call state label to display the provider name).
- if ((isAccount || isWifi || isConference) && hasSuggestedLabel) {
- callStateLabel = label;
- } else if (sessionModificationState
+ if (sessionModificationState
== Call.SessionModificationState.REQUEST_REJECTED) {
callStateLabel = context.getString(R.string.card_title_video_call_rejected);
isAutoDismissing = true;
@@ -1052,6 +1186,11 @@
} else if (VideoUtils.isVideoCall(videoState)) {
callStateLabel = context.getString(R.string.card_title_video_call);
}
+
+ if ((isAccount || isWifi || isConference) && hasSuggestedLabel) {
+ label += (callStateLabel != null) ? (" " + callStateLabel) : "";
+ callStateLabel = label;
+ }
break;
case Call.State.ONHOLD:
callStateLabel = context.getString(R.string.card_title_on_hold);
@@ -1069,7 +1208,17 @@
break;
case Call.State.INCOMING:
case Call.State.CALL_WAITING:
- if (isWifi && hasSuggestedLabel) {
+ if (isConference) {
+ if (isAccount) {
+ callStateLabel = context.getString(
+ R.string.incoming_conf_via_template, label);
+ } else if (VideoUtils.isVideoCall(videoState)) {
+ callStateLabel = context.getString(
+ R.string.card_title_incoming_video_conf_call);
+ } else {
+ callStateLabel = context.getString(R.string.card_title_incoming_conf_call);
+ }
+ } else if (isWifi && hasSuggestedLabel) {
callStateLabel = label;
} else if (isAccount) {
callStateLabel = context.getString(R.string.incoming_via_template, label);
@@ -1095,6 +1244,13 @@
if (TextUtils.isEmpty(callStateLabel)) {
callStateLabel = context.getString(R.string.card_title_call_ended);
}
+ if (context.getResources().getBoolean(R.bool.def_incallui_clearcode_enabled)) {
+ String clearText = disconnectCause.getDescription() == null ? ""
+ : disconnectCause.getDescription().toString();
+ if (!TextUtils.isEmpty(clearText)) {
+ Toast.makeText(context, clearText, Toast.LENGTH_SHORT).show();
+ }
+ }
break;
case Call.State.CONFERENCED:
callStateLabel = context.getString(R.string.card_title_conf_call);
@@ -1203,6 +1359,11 @@
*/
@Override
public void showHdAudioIndicator(boolean visible) {
+ int subId = CallList.getInstance().getActiveSubId();
+ if (SubscriptionManager.getResourcesForSubId(getContext(), subId)
+ .getBoolean(R.bool.config_show_hd2)) {
+ mHdAudioIcon.setImageResource(R.drawable.ic_hd2_24dp);
+ }
mHdAudioIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
}
@@ -1331,6 +1492,7 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ updateFabPosition();
mPrimaryCallCardContainer.setTag(R.id.view_tag_callcard_actual_height,
null);
setViewStatePostAnimation(listener);
@@ -1489,4 +1651,105 @@
v.setBottom(oldBottom);
}
}
+
+ private OnClickListener mVbListener = new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ if (isVbAvailable()) {
+ // Switch Volume Boost status
+ setVolumeBoost(!isVolumeBoostOn());
+ }
+
+ updateVbButton();
+ showVbNotify();
+ }
+ };
+
+ private boolean isVbAvailable() {
+ int mode = AudioModeProvider.getInstance().getAudioMode();
+ final Context context = getContext();
+ int settingsTtyMode = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.PREFERRED_TTY_MODE,
+ TTY_MODE_OFF);
+
+ return (mode == CallAudioState.ROUTE_EARPIECE || mode == CallAudioState.ROUTE_SPEAKER
+ || settingsTtyMode == TTY_MODE_HCO);
+ }
+
+ private void updateVbButton() {
+ if (isVbAvailable()) {
+ if (isVolumeBoostOn()) {
+ mVbButton.setBackgroundResource(R.drawable.vb_active);
+ } else {
+ mVbButton.setBackgroundResource(R.drawable.vb_normal);
+ }
+ } else {
+ mVbButton.setBackgroundResource(R.drawable.vb_disable);
+ }
+ }
+
+ @Override
+ public void showVbButton(boolean show) {
+ mVbButton.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
+ private void showVbNotify() {
+ Toast vbnotify;
+ int resId = R.string.volume_boost_notify_unavailable;
+
+ if (isVbAvailable()) {
+ if (isVolumeBoostOn()) {
+ resId = R.string.volume_boost_notify_enabled;
+ } else {
+ resId = R.string.volume_boost_notify_disabled;
+ }
+ }
+
+ vbnotify = Toast.makeText(getView().getContext(), resId, Toast.LENGTH_SHORT);
+ vbnotify.setGravity(Gravity.CENTER, 0, 0);
+ vbnotify.show();
+ }
+
+ private void updateVbByCall(int state) {
+ updateVbButton();
+
+ if (Call.State.ACTIVE == state) {
+ mVbButton.setVisibility(View.VISIBLE);
+ } else if (Call.State.DISCONNECTED == state) {
+ if (!CallList.getInstance().hasLiveCall()
+ && isVolumeBoostOn()) {
+ mVbButton.setVisibility(View.GONE);
+
+ setVolumeBoost(false);
+ }
+ }
+ }
+
+ public void updateVbByAudioMode(int newMode) {
+ if (!(newMode == CallAudioState.ROUTE_EARPIECE
+ || newMode == CallAudioState.ROUTE_BLUETOOTH
+ || newMode == CallAudioState.ROUTE_WIRED_HEADSET
+ || newMode == CallAudioState.ROUTE_SPEAKER)) {
+ return;
+ }
+
+ if (mAudioManager != null && isVolumeBoostOn()) {
+ setVolumeBoost(false);
+ }
+
+ updateVbButton();
+ }
+
+ private void setVolumeBoost(boolean on){
+ if (on)
+ mAudioManager.setParameters(VOLUME_BOOST + "=on");
+ else
+ mAudioManager.setParameters(VOLUME_BOOST + "=off");
+ }
+
+ private boolean isVolumeBoostOn(){
+
+ return mAudioManager.getParameters(VOLUME_BOOST).contains("=on");
+ }
+
}
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index 10bf5e6..4317a5e 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -35,6 +35,8 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -75,6 +77,11 @@
private static final String TAG = CallCardPresenter.class.getSimpleName();
private static final long CALL_TIME_UPDATE_INTERVAL_MS = 1000;
+ private static final int IDP_NONE = 0;
+ private static final int IDP_CDMA = 1;
+ private static final int IDP_GSM = 2;
+ private static final int IDP_BOTH = 3;
+
private final EmergencyCallListener mEmergencyCallListener =
ObjectFactory.newEmergencyCallListener();
private DistanceHelper mDistanceHelper;
@@ -135,6 +142,18 @@
});
}
+ private boolean isGeocoderLocationNeeded(Call call) {
+ Log.d(this, "isGeocoderLocationNeeded getState() = " + call.getState());
+ if (call.getState() == Call.State.INCOMING ||
+ call.getState() == Call.State.CALL_WAITING ||
+ call.getState() == Call.State.DIALING ||
+ call.getState() == Call.State.CONNECTING ||
+ call.getState() == Call.State.SELECT_PHONE_ACCOUNT) {
+ return true;
+ };
+ return false;
+ }
+
public void init(Context context, Call call) {
mContext = Preconditions.checkNotNull(context);
mDistanceHelper = ObjectFactory.newDistanceHelper(mContext, this);
@@ -153,7 +172,7 @@
// start processing lookups right away.
if (!call.isConferenceCall()) {
- startContactInfoSearch(call, true, call.getState() == Call.State.INCOMING);
+ startContactInfoSearch(call, true, isGeocoderLocationNeeded(call));
} else {
updateContactEntry(null, true);
}
@@ -273,7 +292,7 @@
CallList.getInstance().addCallUpdateListener(mPrimary.getId(), this);
mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mPrimary,
- mPrimary.getState() == Call.State.INCOMING);
+ isGeocoderLocationNeeded(mPrimary));
updatePrimaryDisplayInfo();
maybeStartSearch(mPrimary, true);
maybeClearSessionModificationState(mPrimary);
@@ -302,6 +321,7 @@
// Start/stop timers.
if (isPrimaryCallActive()) {
Log.d(this, "Starting the calltime timer");
+ mPrimary.triggerCalcBaseChronometerTime();
mCallTimer.start(CALL_TIME_UPDATE_INTERVAL_MS);
} else {
Log.d(this, "Canceling the calltime timer");
@@ -360,7 +380,7 @@
* @param sessionModificationState The new session modification state.
*/
@Override
- public void onSessionModificationStateChange(int sessionModificationState) {
+ public void onSessionModificationStateChange(Call call, int sessionModificationState) {
Log.d(this, "onSessionModificationStateChange : sessionModificationState = " +
sessionModificationState);
@@ -425,11 +445,13 @@
return null;
}
- private void updatePrimaryCallState() {
+ public void updatePrimaryCallState() {
if (getUi() != null && mPrimary != null) {
boolean isWorkCall = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
|| (mPrimaryContactInfo == null ? false
: mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
+ InCallPresenter.getInstance().setThemeColors();
+ boolean isConfCall = mPrimary.isConferenceCall() || mPrimary.isIncomingConfCall();
getUi().setCallState(
mPrimary.getState(),
mPrimary.getVideoState(),
@@ -439,7 +461,7 @@
getCallStateIcon(),
getGatewayNumber(),
mPrimary.hasProperty(Details.PROPERTY_WIFI),
- mPrimary.isConferenceCall(),
+ isConfCall,
isWorkCall);
maybeShowHdAudioIcon();
@@ -540,9 +562,7 @@
ui.setPrimaryCallElapsedTime(false, 0);
mCallTimer.cancel();
} else {
- final long callStart = mPrimary.getConnectTimeMillis();
- final long duration = System.currentTimeMillis() - callStart;
- ui.setPrimaryCallElapsedTime(true, duration);
+ ui.setPrimaryCallElapsedTime(true, mPrimary.getCallDuration());
}
}
@@ -567,7 +587,7 @@
private void maybeStartSearch(Call call, boolean isPrimary) {
// no need to start search for conference calls which show generic info.
if (call != null && !call.isConferenceCall()) {
- startContactInfoSearch(call, isPrimary, call.getState() == Call.State.INCOMING);
+ startContactInfoSearch(call, isPrimary, isGeocoderLocationNeeded(call));
}
}
@@ -738,6 +758,82 @@
return retval;
}
+ private boolean checkIdpEnable(int subId) {
+ boolean ret = false;
+ final int checkIdp = mContext.getResources().getInteger(R.integer.check_idp);
+ final int phoneType = TelephonyManager.getDefault().getCurrentPhoneType(subId);
+
+ if (checkIdp == IDP_BOTH ||
+ (checkIdp == IDP_CDMA && phoneType == TelephonyManager.PHONE_TYPE_CDMA) ||
+ (checkIdp == IDP_GSM && phoneType == TelephonyManager.PHONE_TYPE_GSM)) {
+ ret = true;
+ }
+
+ Log.i(this, "checkIdp phone type:" + phoneType + " enabled:" + ret);
+ return ret;
+ }
+
+ private int getSubId() {
+ int subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+
+ PhoneAccountHandle accountHandle = mPrimary.getAccountHandle();
+ if (accountHandle != null) {
+ TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
+ PhoneAccount account = mgr.getPhoneAccount(accountHandle);
+ if (account != null) {
+ subId = TelephonyManager.getDefault().getSubIdForPhoneAccount(account);
+ Log.d(this, "get sub id from phone account = " + subId);
+ }
+ }
+
+ return subId;
+ }
+
+ private String checkIdp(String number, boolean nameIsNumber, boolean isIncoming) {
+ int subId = getSubId();
+ String checkedNumber = number;
+ String[] idp = mContext.getResources().getStringArray(R.array.international_idp);
+ String[] idpValues = mContext.getResources().
+ getStringArray(R.array.international_idp_values);
+ boolean isDataInValid = (idp.length == 0 || idpValues.length == 0 ||
+ idp.length != idpValues.length);
+
+ if (checkIdpEnable(subId) && nameIsNumber && !isDataInValid) {
+ if (!isIncoming) {
+ final int checkRoamingIdx = mContext.getResources().
+ getInteger(R.integer.check_idp_roaming_idx);
+ final boolean isNetworkRoaming =
+ TelephonyManager.getDefault().isNetworkRoaming(subId);
+ for (int i = 0; i < idp.length; i++) {
+ Log.i(this, "checkIdp idp:" + idp[i] + " idp value:" + idpValues[i] +
+ " roaming idx:" + checkRoamingIdx);
+ int indexIdp = checkedNumber.indexOf(idp[i]);
+
+ if (indexIdp != -1) {
+ if ((checkRoamingIdx == i && !isNetworkRoaming) ||
+ checkRoamingIdx != i) {
+ checkedNumber = checkedNumber.substring(0, indexIdp) + idpValues[i] +
+ checkedNumber.substring(indexIdp + idp[i].length());
+ }
+ }
+ }
+ } else {
+ final int checkReconvertIdx = mContext.getResources().
+ getInteger(R.integer.check_idp_reconvert_idx);
+ if (checkReconvertIdx >= 0 && checkReconvertIdx < idp.length &&
+ number.indexOf(idpValues[checkReconvertIdx]) == 0) {
+ checkedNumber = idp[checkReconvertIdx] +
+ number.substring(idpValues[checkReconvertIdx].length());
+ }
+ }
+ }
+
+ Log.i(this, "checkIdp number: " + number + " subid:" + subId + " isIncoming:" +
+ isIncoming + " checked number:" + checkedNumber + " inValid:" +
+ isDataInValid + " nameIsNumber" + nameIsNumber);
+ return checkedNumber;
+ }
+
private void updatePrimaryDisplayInfo() {
final CallCardUi ui = getUi();
if (ui == null) {
@@ -806,6 +902,12 @@
boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
// Call with caller that is a work contact.
boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
+ boolean isIncoming = mPrimary.getState() == Call.State.INCOMING;
+ boolean isWithPrefix = mContext.getResources().
+ getBoolean(R.bool.phone_number_with_intl_prefix);
+ if(isWithPrefix == true){
+ name = checkIdp(name, nameIsNumber, isIncoming);
+ }
ui.setPrimary(
number,
name,
@@ -902,13 +1004,27 @@
PhoneAccount account = getAccountForCall(call);
TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
if (account != null && !TextUtils.isEmpty(account.getLabel())
- && TelecomManagerCompat.getCallCapablePhoneAccounts(mgr).size() > 1) {
+ && TelecomManagerCompat.getCallCapablePhoneAccounts(mgr).size() >= 1) {
return account.getLabel().toString();
}
return null;
}
/**
+ * Return the icon to represent the call provider
+ */
+ private Drawable getCallProviderIcon(Call call) {
+ PhoneAccount account = getAccountForCall(call);
+ TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
+ if (account != null && account.getIcon()!= null &&
+ mgr.getCallCapablePhoneAccounts().size() > 1 &&
+ SubscriptionManager.from(mContext).getActiveSubscriptionInfoCount() > 1) {
+ return account.getIcon().loadDrawable(mContext);
+ }
+ return null;
+ }
+
+ /**
* Returns the label (line of text above the number/name) for any given call.
* For example, "calling via [Account/Google Voice]" for outgoing calls.
*/
@@ -943,7 +1059,7 @@
}
}
- return null;
+ return getCallProviderIcon(mPrimary);
}
private boolean hasOutgoingGatewayCall() {
@@ -1031,6 +1147,11 @@
}
ui.setCallCardVisible(!isFullscreenMode);
ui.setSecondaryInfoVisible(!isFullscreenMode);
+ final int callState = (mPrimary != null) ? mPrimary.getState() : Call.State.INVALID;
+ ui.setEndCallButtonEnabled(!isFullscreenMode &&
+ shouldShowEndCallButton(mPrimary, callState),
+ callState != Call.State.INCOMING /* animate */);
+ ui.showVbButton(!isFullscreenMode);
maybeShowManageConferenceCallButton();
}
@@ -1039,6 +1160,15 @@
// No-op - the Call Card is the origin of this event.
}
+ @Override
+ public void onIncomingVideoAvailabilityChanged(boolean isAvailable) {
+ Log.d(this, "onIncomingVideoAvailabilityChanged: available = " + isAvailable);
+ if (mPrimary == null) {
+ return;
+ }
+ updatePrimaryDisplayInfo();
+ }
+
private boolean isPrimaryCallActive() {
return mPrimary != null && mPrimary.getState() == Call.State.ACTIVE;
}
@@ -1169,5 +1299,6 @@
void animateForNewOutgoingCall();
void sendAccessibilityAnnouncement();
void showNoteSentToast();
+ void showVbButton(boolean show);
}
}
diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java
index d0f3c10..059345f 100644
--- a/InCallUI/src/com/android/incallui/CallList.java
+++ b/InCallUI/src/com/android/incallui/CallList.java
@@ -28,8 +28,12 @@
import com.android.incallui.util.TelecomCallUtil;
import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.SubscriptionManager;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -51,6 +55,7 @@
private static final int DISCONNECTED_CALL_LONG_TIMEOUT_MS = 5000;
private static final int EVENT_DISCONNECTED_TIMEOUT = 1;
+ private static final int EVENT_NOTIFY_CHANGE = 2;
private static final long BLOCK_QUERY_TIMEOUT_MS = 1000;
private static CallList sInstance = new CallList();
@@ -70,6 +75,9 @@
private final Set<Call> mPendingDisconnectCalls = Collections.newSetFromMap(
new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
private FilteredNumberAsyncQueryHandler mFilteredQueryHandler;
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private final ArrayList<ActiveSubChangeListener> mActiveSubChangeListeners =
+ Lists.newArrayList();
/**
* Static singleton accessor method.
@@ -130,6 +138,18 @@
* Called when a single call has changed.
*/
public void onIncoming(Call call, List<String> textMessages) {
+ Log.d(this, "onIncoming - " + call);
+
+ // Update active subscription from call object. it will be set by
+ // Telecomm service for incoming call and whenever active sub changes.
+ if (call.mIsActiveSub) {
+ int sub = Integer.parseInt(call.getAccountHandle().getId());
+ Log.d(this, "onIncoming - sub:" + sub + " mSubId:" + mSubId);
+ if (sub != mSubId) {
+ setActiveSubId(sub);
+ }
+ }
+
if (updateCallInMap(call)) {
Log.i(this, "onIncoming - " + call);
}
@@ -151,6 +171,19 @@
*/
public void onUpdate(Call call) {
Trace.beginSection("onUpdate");
+ PhoneAccountHandle ph = call.getAccountHandle();
+ Log.d(this, "onUpdate - " + call + " ph:" + ph);
+ try {
+ if (call.mIsActiveSub && ph != null) {
+ int sub = Integer.parseInt(ph.getId());
+ Log.d(this, "onUpdate - sub:" + sub + " mSubId:" + mSubId);
+ if(sub != mSubId) {
+ setActiveSubId(sub);
+ }
+ }
+ } catch (NumberFormatException e) {
+ Log.w(this,"Sub Id is not a number " + e);
+ }
onUpdateCall(call);
notifyGenericListeners();
Trace.endSection();
@@ -166,7 +199,7 @@
final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
if (listeners != null) {
for (CallUpdateListener listener : listeners) {
- listener.onSessionModificationStateChange(sessionModificationState);
+ listener.onSessionModificationStateChange(call, sessionModificationState);
}
}
}
@@ -347,6 +380,9 @@
result = getFirstCallWithState(Call.State.ACTIVE);
}
if (result == null) {
+ result = getFirstCallWithState(Call.State.ONHOLD);
+ }
+ if (result == null) {
result = getDisconnectingCall();
}
if (result == null) {
@@ -382,6 +418,10 @@
return mCallById.get(callId);
}
+ public boolean isDsdaEnabled() {
+ return QtiCallUtils.isDsdaEnabled();
+ }
+
public Call getCallByTelecomCall(android.telecom.Call telecomCall) {
return mCallByTelecomCall.get(telecomCall);
}
@@ -402,6 +442,13 @@
* TODO: Improve this logic to sort by call time.
*/
public Call getCallWithState(int state, int positionToFind) {
+ // if DSDA is enabled call getCallWithState with active subscription.
+ if (state != Call.State.SELECT_PHONE_ACCOUNT && getActiveSubId()
+ != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ && isDsdaEnabled()) {
+ return getCallWithState(state, positionToFind, getActiveSubId());
+ }
+
Call retval = null;
int position = 0;
for (Call call : mCallById.values()) {
@@ -608,6 +655,13 @@
Log.d(this, "EVENT_DISCONNECTED_TIMEOUT ", msg.obj);
finishDisconnectedCall((Call) msg.obj);
break;
+ case EVENT_NOTIFY_CHANGE:
+ Log.d(this, "EVENT_NOTIFY_CHANGE: ");
+ notifyGenericListeners();
+ for (ActiveSubChangeListener listener : mActiveSubChangeListeners) {
+ listener.onActiveSubChanged(getActiveSubId());
+ }
+ break;
default:
Log.wtf(this, "Message not expected: " + msg.what);
break;
@@ -663,7 +717,7 @@
*
* @param sessionModificationState The new session modification state.
*/
- public void onSessionModificationStateChange(int sessionModificationState);
+ public void onSessionModificationStateChange(Call call, int sessionModificationState);
/**
* Notifies of a change to the last forwarded number for a call.
@@ -675,4 +729,152 @@
*/
public void onChildNumberChange();
}
+
+ /**
+ * Called when active subscription changes.
+ */
+ void onActiveSubChanged(int activeSub) {
+ Log.d(this, "onActiveSubChanged = " + activeSub);
+ if (hasAnyLiveCall(activeSub)) {
+ setActiveSubId(activeSub);
+ }
+ }
+
+ int getActiveSubId() {
+ return mSubId;
+ }
+
+ /**
+ * Called to update the latest active subscription id, and also it
+ * notifies the registred clients about subscription change information.
+ */
+ void setActiveSubId(int subId) {
+ if (subId != mSubId) {
+ Log.d(this, "setActiveSubId, oldActiveSubId = " + mSubId +
+ " newActiveSubId = " + subId);
+ mSubId = subId;
+ final Message msg = mHandler.obtainMessage(EVENT_NOTIFY_CHANGE, null);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /**
+ * Returns true, if any voice call is ACTIVE on the provided subscription.
+ */
+ boolean hasAnyLiveCall(int subId) {
+ for (Call call : mCallById.values()) {
+ PhoneAccountHandle ph = call.getAccountHandle();
+ try {
+ if (!isCallDead(call) && ph != null && (Integer.parseInt(ph.getId()) == subId)) {
+ Log.d(this, "hasAnyLiveCall sub = " + subId);
+ return true;
+ }
+ } catch (NumberFormatException e) {
+ Log.w(this,"Sub Id is not a number " + e);
+ }
+ }
+ Log.d(this, "no active call ");
+ return false;
+ }
+
+ /**
+ * Returns true, if any call is ACTIVE
+ */
+ boolean hasAnyLiveCall() {
+ for (Call call : mCallById.values()) {
+ if (!isCallDead(call)) {
+ Log.d(this, "hasAnyLiveCall call = " + call);
+ return true;
+ }
+ }
+ Log.d(this, "no active call ");
+ return false;
+ }
+
+ /**
+ * This method checks whether any other subscription currently has active voice
+ * call other than current active subscription, if yes it makes that other
+ * subscription as active subscription i.e user visible subscription.
+ * @param retainLch whether to retain the LCH state of the other active sub
+ */
+ boolean switchToOtherActiveSub() {
+ int activeSub = getActiveSubId();
+ boolean subSwitched = false;
+
+ for (int i = 0; i < InCallServiceImpl.sPhoneCount; i++) {
+ int subId = QtiCallUtils.getSubId(i);
+ if ((subId != activeSub) && hasAnyLiveCall(subId)) {
+ Log.d(this, "switchToOtherActiveSub, subId = " + subId);
+ subSwitched = true;
+ QtiCallUtils.switchToActiveSub(subId);
+ setActiveSubId(subId);
+ break;
+ }
+ }
+ return subSwitched;
+ }
+
+ /**
+ * Method to check if there is any live call in a sub other than the one supplied.
+ * @param currentSub The subscription to exclude while checking for active calls.
+ */
+ boolean isAnyOtherSubActive(int currentSub) {
+ boolean result = false;
+ if(!isDsdaEnabled()) {
+ return false;
+ }
+
+ for (int phoneId = 0; phoneId < InCallServiceImpl.sPhoneCount;
+ phoneId++) {
+ int subId = QtiCallUtils.getSubId(phoneId);
+
+ if ((subId != currentSub) && hasAnyLiveCall(subId)) {
+ Log.d(this, "Live call found on another sub = " + subId);
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the [position]th call which belongs to provided subscription and
+ * found in the call map with the specified state.
+ */
+ Call getCallWithState(int state, int positionToFind, int subId) {
+ Call retval = null;
+ int position = 0;
+ for (Call call : mCallById.values()) {
+ PhoneAccountHandle ph = call.getAccountHandle();
+ try {
+ if ((call.getState() == state) && ((ph == null) ||
+ (ph != null && (ph.getId() != null) && ((ph.getId().contains("sip")
+ || ph.getId().contains("@")) || Integer.parseInt(ph.getId()) == subId)))) {
+ if (position >= positionToFind) {
+ retval = call;
+ break;
+ } else {
+ position++;
+ }
+ }
+ } catch (NumberFormatException e) {
+ Log.w(this,"Sub Id is not a number " + e);
+ }
+ }
+ return retval;
+ }
+
+ void addActiveSubChangeListener(ActiveSubChangeListener listener) {
+ Preconditions.checkNotNull(listener);
+ mActiveSubChangeListeners.add(listener);
+ }
+
+ void removeActiveSubChangeListener(ActiveSubChangeListener listener) {
+ Preconditions.checkNotNull(listener);
+ mActiveSubChangeListeners.remove(listener);
+ }
+
+ interface ActiveSubChangeListener {
+ public void onActiveSubChanged(int subId);
+ }
}
diff --git a/InCallUI/src/com/android/incallui/CallSubstateNotifier.java b/InCallUI/src/com/android/incallui/CallSubstateNotifier.java
new file mode 100644
index 0000000..ab2713c
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/CallSubstateNotifier.java
@@ -0,0 +1,158 @@
+/* Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import org.codeaurora.ims.QtiCallConstants;
+import android.os.Bundle;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.HashMap;
+import java.util.List;
+import com.google.common.base.Preconditions;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+
+/**
+ * This class listens to incoming events from the {@class InCallDetailsListener}.
+ * When call details change, this class is notified and we parse the callExtras from the details to
+ * figure out if call substate has changed and notify the {@class InCallMessageController} to
+ * display the indication on UI.
+ *
+ */
+public class CallSubstateNotifier implements InCallDetailsListener, CallList.Listener {
+
+ private final List<InCallSubstateListener> mCallSubstateListeners =
+ new CopyOnWriteArrayList<>();
+
+ private static CallSubstateNotifier sCallSubstateNotifier;
+ private final HashMap<String, Integer> mCallSubstateMap = new HashMap<>();
+
+ /**
+ * This method returns a singleton instance of {@class CallSubstateNotifier}
+ */
+ public static synchronized CallSubstateNotifier getInstance() {
+ if (sCallSubstateNotifier == null) {
+ sCallSubstateNotifier = new CallSubstateNotifier();
+ }
+ return sCallSubstateNotifier;
+ }
+
+ /**
+ * This method adds a new call substate listener. Users interested in listening to call
+ * substate changes should add a listener of type {@class InCallSubstateListener}
+ */
+ public void addListener(InCallSubstateListener listener) {
+ Preconditions.checkNotNull(listener);
+ mCallSubstateListeners.add(listener);
+ }
+
+ /**
+ * This method removes an existing call substate listener. Users listening to call
+ * substate changes when not interested any more can de-register an existing listener of type
+ * {@class InCallSubstateListener}
+ */
+ public void removeListener(InCallSubstateListener listener) {
+ if (listener != null) {
+ mCallSubstateListeners.remove(listener);
+ } else {
+ Log.e(this, "Can't remove null listener");
+ }
+ }
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private CallSubstateNotifier() {
+ }
+
+ private int getCallSubstate(Bundle callExtras) {
+ return callExtras.getInt(QtiCallConstants.CALL_SUBSTATE_EXTRA_KEY,
+ QtiCallConstants.CALL_SUBSTATE_NONE);
+ }
+
+ /**
+ * This method overrides onDetailsChanged method of {@class InCallDetailsListener}. We are
+ * notified when call details change and extract the call substate from the callExtras, detect
+ * if call substate changed and notify all registered listeners.
+ */
+ @Override
+ public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+ Log.d(this, "onDetailsChanged - call: " + call + "details: " + details);
+
+ if (call == null || details == null ||
+ !Call.State.isConnectingOrConnected(call.getState())) {
+ Log.d(this, "onDetailsChanged - Call/details is null/Call is not connected. Return");
+ return;
+ }
+
+ final Bundle callExtras = details.getExtras();
+
+ if (callExtras == null) {
+ return;
+ }
+
+ final String callId = call.getId();
+
+ final int oldCallSubstate = mCallSubstateMap.containsKey(callId) ?
+ mCallSubstateMap.get(callId) : QtiCallConstants.CALL_SUBSTATE_NONE;
+ final int newCallSubstate = getCallSubstate(callExtras);
+
+ if (oldCallSubstate == newCallSubstate) {
+ return;
+ }
+
+ mCallSubstateMap.put(callId, newCallSubstate);
+ Preconditions.checkNotNull(mCallSubstateListeners);
+ for (InCallSubstateListener listener : mCallSubstateListeners) {
+ listener.onCallSubstateChanged(call, newCallSubstate);
+ }
+ }
+
+ /**
+ * This method overrides onDisconnect method of {@interface CallList.Listener}
+ */
+ @Override
+ public void onDisconnect(final Call call) {
+ Log.d(this, "onDisconnect: call: " + call);
+ mCallSubstateMap.remove(call.getId());
+ }
+
+ @Override
+ public void onUpgradeToVideo(Call call) {
+ //NO-OP
+ }
+
+ @Override
+ public void onIncomingCall(Call call) {
+ //NO-OP
+ }
+
+ @Override
+ public void onCallListChange(CallList callList) {
+ //NO-OP
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/CallerInfo.java b/InCallUI/src/com/android/incallui/CallerInfo.java
index f270678..ccee604 100644
--- a/InCallUI/src/com/android/incallui/CallerInfo.java
+++ b/InCallUI/src/com/android/incallui/CallerInfo.java
@@ -402,6 +402,15 @@
return this;
}
+ /* package */ CallerInfo markAsEmergency(Context context, String number) {
+ number = PhoneNumberUtils.normalizeNumber(number);
+ name = context.getString(R.string.emergency_call_dialog_number_for_display);
+ phoneNumber = number;
+
+ photoResource = R.drawable.img_phone;
+ mIsEmergency = true;
+ return this;
+ }
/**
* Mark this CallerInfo as a voicemail call. The voicemail label
diff --git a/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java b/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java
index f7f0cbb..fa4d671 100644
--- a/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java
+++ b/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java
@@ -261,7 +261,14 @@
if (cw.event == EVENT_EMERGENCY_NUMBER) {
// Note we're setting the phone number here (refer to javadoc
// comments at the top of CallerInfo class).
- mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext);
+ if (mQueryContext.getResources().getBoolean(R.bool.mark_emergency_call)) {
+ Log.d(this, "Emergency Number and Mark Emergency Number enabled");
+ mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext,
+ cw.number);
+ } else {
+ Log.d(this, "Emergency Number and Mark Emergency Number disabled");
+ mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext);
+ }
} else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
mCallerInfo = new CallerInfo().markAsVoiceMail(mQueryContext);
} else {
@@ -401,7 +408,7 @@
cw.number = info.phoneNumber;
// check to see if these are recognized numbers, and use shortcuts if we can.
- if (PhoneNumberUtils.isLocalEmergencyNumber(context, info.phoneNumber)) {
+ if (QtiCallUtils.isLocalEmergencyNumber(info.phoneNumber)){
cw.event = EVENT_EMERGENCY_NUMBER;
} else if (info.isVoiceMailNumber()) {
cw.event = EVENT_VOICEMAIL_NUMBER;
diff --git a/InCallUI/src/com/android/incallui/CircularRevealFragment.java b/InCallUI/src/com/android/incallui/CircularRevealFragment.java
index 01bd253..f9816de 100644
--- a/InCallUI/src/com/android/incallui/CircularRevealFragment.java
+++ b/InCallUI/src/com/android/incallui/CircularRevealFragment.java
@@ -24,6 +24,7 @@
import android.graphics.Outline;
import android.graphics.Point;
import android.os.Bundle;
+import android.os.Handler;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
@@ -39,9 +40,16 @@
public class CircularRevealFragment extends Fragment {
static final String TAG = "CircularRevealFragment";
+ // This is used to extend the expiration time of Circular reveal
+ // animation. So over all time the handler waits is Animation
+ // duration + REVEAL_ADDITIONAL_EXPIRATION_TIME and notifies
+ // the Circular reveal completion to listeners.
+ private static final int REVEAL_ADDITIONAL_EXPIRATION_TIME = 200;
+
private Point mTouchPoint;
private OnCircularRevealCompleteListener mListener;
private boolean mAnimationStarted;
+ private boolean mAnimationFinished;
interface OnCircularRevealCompleteListener {
public void onCircularRevealComplete(FragmentManager fm);
@@ -123,6 +131,7 @@
view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
+ mAnimationFinished = false;
final ViewTreeObserver vto = view.getViewTreeObserver();
if (vto.isAlive()) {
vto.removeOnPreDrawListener(this);
@@ -132,13 +141,31 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ Log.w(TAG, " CircularRevealFragment - onAnimationEnd");
view.setClipToOutline(false);
- if (mListener != null) {
+ if (mListener != null && mAnimationFinished == false) {
mListener.onCircularRevealComplete(getFragmentManager());
+ mAnimationFinished = true;
}
}
});
animator.start();
+ // If somehow the animator is failed to run from lower layers,
+ // need to wait for certain expiration time and inform the
+ // listeners with OnCircularComplete callback so that rest of
+ // the things will not be blocked.
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Log.w(TAG, " Failed to complete animation");
+ if (mListener != null && mAnimationFinished == false) {
+ mListener.onCircularRevealComplete(getFragmentManager());
+ mAnimationFinished = true;
+ }
+ }
+ }, getResources().getInteger(R.integer.reveal_animation_duration) +
+ REVEAL_ADDITIONAL_EXPIRATION_TIME);
}
return false;
}
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java b/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
index fe941c8..ad417b8 100644
--- a/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
+++ b/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
@@ -99,10 +99,13 @@
public void onVisibilityChanged(boolean isVisible) {
mIsVisible = isVisible;
ActionBar actionBar = getActivity().getActionBar();
+ boolean isDsdaEnabled = CallList.getInstance().isDsdaEnabled();
if (isVisible) {
actionBar.setTitle(R.string.manageConferenceLabel);
actionBar.setElevation(mActionBarElevation);
- actionBar.setHideOffset(0);
+ if (!isDsdaEnabled) {
+ actionBar.setHideOffset(0);
+ }
actionBar.show();
final CallList calls = CallList.getInstance();
@@ -112,7 +115,9 @@
mConferenceParticipantList.requestFocus();
} else {
actionBar.setElevation(0);
- actionBar.setHideOffset(actionBar.getHeight());
+ if (!isDsdaEnabled) {
+ actionBar.setHideOffset(actionBar.getHeight());
+ }
}
}
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
index 6fb6e5d..c995be0 100644
--- a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
+++ b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
@@ -126,7 +126,7 @@
}
Log.d(this, "Number of calls is " + String.valueOf(calls.size()));
-
+ Log.d(this, "update calls" + calls);
// Users can split out a call from the conference call if either the active call or the
// holding call is empty. If both are filled, users can not split out another call.
final boolean hasActiveCall = (callList.getActiveCall() != null);
diff --git a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java b/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
index d68ae1f..e1968f2 100644
--- a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
+++ b/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
@@ -366,7 +366,7 @@
contactCache.nameAlternative, mContactsPreferences),
contactCache.number, contactCache.label,
contactCache.lookupKey, contactCache.displayPhotoUri, thisRowCanSeparate,
- thisRowCanDisconnect);
+ thisRowCanDisconnect, getResourceforState(call.getTrueState()));
// Tag the row in the conference participant list with the call id to make it easier to
// find calls when contact cache information is loaded.
@@ -375,6 +375,33 @@
return result;
}
+ private static int getResourceforState(int state){
+ int res;
+ switch (state){
+ case Call.State.ACTIVE:
+ res = R.string.call_state_active;
+ break;
+ case Call.State.NEW:
+ case Call.State.IDLE:
+ case Call.State.DIALING:
+ case Call.State.REDIALING:
+ res = R.string.call_state_dialing;
+ break;
+ case Call.State.ONHOLD:
+ res = R.string.call_state_holding;
+ break;
+ case Call.State.DISCONNECTING:
+ res = R.string.call_state_disconnecting;
+ break;
+ case Call.State.DISCONNECTED:
+ res = R.string.call_state_disconnected;
+ break;
+ default:
+ res = R.string.call_state_unknown;
+ }
+ return res;
+ }
+
/**
* Replaces the contact info for a participant and triggers a refresh of the UI.
*
@@ -404,7 +431,7 @@
*/
private final void setCallerInfoForRow(View view, String callerName, String preferredName,
String callerNumber, String callerNumberType, String lookupKey, Uri photoUri,
- boolean thisRowCanSeparate, boolean thisRowCanDisconnect) {
+ boolean thisRowCanSeparate, boolean thisRowCanDisconnect, int state) {
final ImageView photoView = (ImageView) view.findViewById(R.id.callerPhoto);
final TextView nameTextView = (TextView) view.findViewById(R.id.conferenceCallerName);
@@ -413,7 +440,11 @@
R.id.conferenceCallerNumberType);
final View endButton = view.findViewById(R.id.conferenceCallerDisconnect);
final View separateButton = view.findViewById(R.id.conferenceCallerSeparate);
-
+ if (mContext.getResources().getBoolean(R.bool.
+ config_conference_call_show_participant_status)){
+ final TextView stateTextView = (TextView) view.findViewById(R.id.conferenceCallerState);
+ stateTextView.setText(state);
+ }
endButton.setVisibility(thisRowCanDisconnect ? View.VISIBLE : View.GONE);
if (thisRowCanDisconnect) {
endButton.setOnClickListener(mDisconnectListener);
@@ -457,6 +488,7 @@
* @param conferenceParticipants The calls which make up the conference participants.
*/
private void updateParticipantInfo(List<Call> conferenceParticipants) {
+ Log.d(this, "updateParticipantInfo: " + conferenceParticipants);
final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
boolean newParticipantAdded = false;
HashSet<String> newCallIds = new HashSet<>(conferenceParticipants.size());
diff --git a/InCallUI/src/com/android/incallui/GlowPadAnswerFragment.java b/InCallUI/src/com/android/incallui/GlowPadAnswerFragment.java
index 62a8e78..98e797e 100644
--- a/InCallUI/src/com/android/incallui/GlowPadAnswerFragment.java
+++ b/InCallUI/src/com/android/incallui/GlowPadAnswerFragment.java
@@ -91,6 +91,8 @@
final int directionDescriptionsResourceId;
final int handleDrawableResourceId;
mGlowpad.setVideoState(videoState);
+ final boolean isEnhanceUIEnabled = getContext().getResources().getBoolean(
+ R.bool.config_enable_enhance_video_call_ui);
switch (targetSet) {
case TARGET_SET_FOR_AUDIO_WITH_SMS:
@@ -102,7 +104,12 @@
handleDrawableResourceId = R.drawable.ic_incall_audio_handle;
break;
case TARGET_SET_FOR_VIDEO_WITHOUT_SMS:
- targetResourceId = R.array.incoming_call_widget_video_without_sms_targets;
+ if (isEnhanceUIEnabled) {
+ targetResourceId =
+ R.array.enhance_incoming_call_widget_video_without_sms_targets;
+ } else {
+ targetResourceId = R.array.incoming_call_widget_video_without_sms_targets;
+ }
targetDescriptionsResourceId =
R.array.incoming_call_widget_video_without_sms_target_descriptions;
directionDescriptionsResourceId =
@@ -110,7 +117,11 @@
handleDrawableResourceId = R.drawable.ic_incall_video_handle;
break;
case TARGET_SET_FOR_VIDEO_WITH_SMS:
- targetResourceId = R.array.incoming_call_widget_video_with_sms_targets;
+ if (isEnhanceUIEnabled) {
+ targetResourceId = R.array.enhance_incoming_call_widget_video_with_sms_targets;
+ } else {
+ targetResourceId = R.array.incoming_call_widget_video_with_sms_targets;
+ }
targetDescriptionsResourceId =
R.array.incoming_call_widget_video_with_sms_target_descriptions;
directionDescriptionsResourceId =
@@ -126,6 +137,140 @@
.incoming_call_widget_video_request_target_direction_descriptions;
handleDrawableResourceId = R.drawable.ic_incall_video_handle;
break;
+ case TARGET_SET_FOR_QTI_VIDEO_WITHOUT_SMS:
+ if (isEnhanceUIEnabled) {
+ targetResourceId =
+ R.array.enhance_incoming_call_widget_video_without_sms_targets;
+ } else {
+ targetResourceId = R.array.qti_incoming_call_widget_video_without_sms_targets;
+ }
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_video_without_sms_target_descriptions;
+ directionDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_video_without_sms_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_VIDEO_WITH_SMS:
+ if (isEnhanceUIEnabled) {
+ targetResourceId = R.array.enhance_incoming_call_widget_video_with_sms_targets;
+ } else {
+ targetResourceId = R.array.qti_incoming_call_widget_video_with_sms_targets;
+ }
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_video_with_sms_target_descriptions;
+ directionDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_video_with_sms_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_WITHOUT_SMS:
+ targetResourceId =
+ R.array.qti_incoming_call_widget_tx_video_without_sms_targets;
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_tx_video_without_sms_target_descriptions;
+ directionDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_tx_video_without_sms_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_WITH_SMS:
+ targetResourceId =
+ R.array.qti_incoming_call_widget_tx_video_with_sms_targets;
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_tx_video_with_sms_target_descriptions;
+ directionDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_tx_video_with_sms_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_WITHOUT_SMS:
+ targetResourceId =
+ R.array.qti_incoming_call_widget_rx_video_without_sms_targets;
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_rx_video_without_sms_target_descriptions;
+ directionDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_rx_video_without_sms_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_WITH_SMS:
+ targetResourceId =
+ R.array.qti_incoming_call_widget_rx_video_with_sms_targets;
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_rx_video_with_sms_target_descriptions;
+ directionDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_rx_video_with_sms_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_VIDEO_ACCEPT_REJECT_REQUEST:
+ if (isEnhanceUIEnabled) {
+ targetResourceId =
+ R.array.enhance_incoming_call_widget_video_upgrade_request_targets;
+ } else {
+ targetResourceId = R.array.qti_incoming_call_widget_video_request_targets;
+ }
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_video_request_target_descriptions;
+ directionDescriptionsResourceId = R.array.
+ qti_incoming_call_widget_video_request_target_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST:
+ if (isEnhanceUIEnabled) {
+ targetResourceId = R.array.
+ enhance_incoming_call_bidirectional_video_accept_request_targets;
+ } else {
+ targetResourceId = R.array.
+ qti_incoming_call_widget_bidirectional_video_accept_reject_request_targets;
+ }
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_video_request_target_descriptions;
+ directionDescriptionsResourceId = R.array.
+ qti_incoming_call_widget_video_request_target_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST:
+ if (isEnhanceUIEnabled) {
+ targetResourceId =
+ R.array.enhance_incoming_call_video_transmit_accept_request_targets;
+ } else {
+ targetResourceId = R.array.
+ qti_incoming_call_widget_video_transmit_accept_reject_request_targets;
+ }
+ targetDescriptionsResourceId = R.array.
+ qti_incoming_call_widget_video_transmit_request_target_descriptions;
+ directionDescriptionsResourceId = R.array
+ .qti_incoming_call_widget_video_request_target_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST:
+ if (isEnhanceUIEnabled) {
+ targetResourceId = R.array.
+ enhance_incoming_call_video_receive_accept_request_targets;
+ } else {
+ targetResourceId = R.array.
+ qti_incoming_call_widget_video_receive_accept_reject_request_targets;
+ }
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_video_receive_request_target_descriptions;
+ directionDescriptionsResourceId = R.array
+ .qti_incoming_call_widget_video_request_target_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+
+ case TARGET_SET_FOR_QTI_AUDIO_WITH_SMS:
+ targetResourceId = R.array.qti_incoming_call_widget_audio_with_sms_targets;
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_audio_with_sms_target_descriptions;
+ directionDescriptionsResourceId = R.array
+ .qti_incoming_call_widget_audio_with_sms_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_audio_handle;
+ break;
+ case TARGET_SET_FOR_QTI_AUDIO_WITHOUT_SMS:
+ targetResourceId = R.array.qti_incoming_call_widget_audio_without_sms_targets;
+ targetDescriptionsResourceId =
+ R.array.qti_incoming_call_widget_audio_without_sms_target_descriptions;
+ directionDescriptionsResourceId = R.array
+ .qti_incoming_call_widget_audio_without_sms_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_audio_handle;
+ break;
+
case TARGET_SET_FOR_AUDIO_WITHOUT_SMS:
default:
targetResourceId = R.array.incoming_call_widget_audio_without_sms_targets;
diff --git a/InCallUI/src/com/android/incallui/GlowPadWrapper.java b/InCallUI/src/com/android/incallui/GlowPadWrapper.java
index 342f6b4..c88a984 100644
--- a/InCallUI/src/com/android/incallui/GlowPadWrapper.java
+++ b/InCallUI/src/com/android/incallui/GlowPadWrapper.java
@@ -115,18 +115,32 @@
if (resId == R.drawable.ic_lockscreen_answer) {
mAnswerFragment.onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext());
mTargetTriggered = true;
- } else if (resId == R.drawable.ic_lockscreen_decline) {
+ } else if (resId == R.drawable.ic_lockscreen_decline ||
+ resId == R.drawable.ic_enhance_decline_video) {
mAnswerFragment.onDecline(getContext());
mTargetTriggered = true;
} else if (resId == R.drawable.ic_lockscreen_text) {
mAnswerFragment.onText();
mTargetTriggered = true;
- } else if (resId == R.drawable.ic_videocam || resId == R.drawable.ic_lockscreen_answer_video) {
+ } else if (resId == R.drawable.ic_videocam ||
+ resId == R.drawable.ic_lockscreen_answer_video ||
+ resId == R.drawable.ic_enhance_answer_video) {
mAnswerFragment.onAnswer(mVideoState, getContext());
mTargetTriggered = true;
} else if (resId == R.drawable.ic_lockscreen_decline_video) {
mAnswerFragment.onDeclineUpgradeRequest(getContext());
mTargetTriggered = true;
+ } else if (resId == R.drawable.qti_ic_lockscreen_answer_tx_video ||
+ resId == R.drawable.ic_enhance_answer_tx_video) {
+ mAnswerFragment.onAnswer(VideoProfile.STATE_TX_ENABLED, getContext());
+ mTargetTriggered = true;
+ } else if (resId == R.drawable.qti_ic_lockscreen_answer_rx_video ||
+ resId == R.drawable.ic_enhance_answer_rx_video) {
+ mAnswerFragment.onAnswer(VideoProfile.STATE_RX_ENABLED, getContext());
+ mTargetTriggered = true;
+ } else if (resId == R.drawable.qti_ic_lockscreen_deflect) {
+ mAnswerFragment.onDeflect(getContext());
+ mTargetTriggered = true;
} else {
// Code should never reach here.
Log.e(this, "Trigger detected on unhandled resource. Skipping.");
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index eca79f8..dd2af36 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -17,6 +17,8 @@
package com.android.incallui;
import android.app.ActionBar;
+import android.app.FragmentTransaction;
+import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
@@ -30,12 +32,17 @@
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.Point;
import android.hardware.SensorManager;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
+import android.os.UserHandle;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.MenuItem;
@@ -49,6 +56,8 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
import com.android.contacts.common.activity.TransactionSafeActivity;
import com.android.contacts.common.compat.CompatUtils;
@@ -103,6 +112,16 @@
private AlertDialog mDialog;
private InCallOrientationEventListener mInCallOrientationEventListener;
+ // Add phone feature uri
+ private static final Uri URI_PHONE_FEATURE = Uri
+ .parse("content://com.qualcomm.qti.phonefeature.FEATURE_PROVIDER");
+
+ private static final String METHOD_START_CALL_RECORD = "start_call_record";
+ private static final String METHOD_STOP_CALL_RECORD = "stop_call_record";
+ private static final String METHOD_IS_CALL_RECORD_RUNNING = "is_call_record_running";
+ private static final String METHOD_IS_CALL_RECORD_AVAILABLE = "is_call_record_available";
+ private static final String METHOD_GET_CALL_RECORD_DURATION = "get_call_record_duration";
+ private static final String EXTRA_RESULT = "result";
/**
* Used to indicate whether the dialpad should be hidden or shown {@link #onResume}.
@@ -134,6 +153,13 @@
private Animation mSlideOut;
private boolean mDismissKeyguard = false;
+ private final int TAB_COUNT_ONE = 1;
+ private final int TAB_COUNT_TWO = 2;
+ private final int TAB_POSITION_FIRST = 0;
+
+ private Tab[] mDsdaTab = new Tab[TAB_COUNT_TWO];
+ private boolean[] mDsdaTabAdd = {false, false};
+
AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
@Override
public void onAnimationEnd(Animation animation) {
@@ -170,19 +196,28 @@
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
getWindow().addFlags(flags);
-
- // Setup action bar for the conference call manager.
- requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
- ActionBar actionBar = getActionBar();
- if (actionBar != null) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.hide();
+ boolean isDsdaEnabled = CallList.getInstance().isDsdaEnabled();
+ if (isDsdaEnabled) {
+ requestWindowFeature(Window.FEATURE_ACTION_BAR);
+ getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ getActionBar().setDisplayShowTitleEnabled(false);
+ getActionBar().setDisplayShowHomeEnabled(false);
+ } else {
+ requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ if (getActionBar() != null) {
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ getActionBar().setDisplayShowTitleEnabled(true);
+ getActionBar().hide();
+ }
}
// TODO(klp): Do we need to add this back when prox sensor is not available?
// lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+ // Since activity is created newly, clear full screen flag. This will ensure that
+ // the flag is in sync with actual UI when UI is recreated due to orientation change.
+ InCallPresenter.getInstance().clearFullscreen();
+
setContentView(R.layout.incall_screen);
internalResolveIntent(getIntent());
@@ -236,7 +271,10 @@
}
mInCallOrientationEventListener = new InCallOrientationEventListener(this);
- Log.d(this, "onCreate(): exit");
+ if (isDsdaEnabled ) {
+ initializeDsdaSwitchTab();
+ }
+ Log.d(this, "onCreate(): exit" + isDsdaEnabled);
}
@Override
@@ -301,6 +339,7 @@
if (mShowPostCharWaitDialogOnResume) {
showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars);
}
+ InCallPresenter.getInstance().updatePrimaryCallState();
}
// onPause is guaranteed to be called when the InCallActivity goes
@@ -331,6 +370,7 @@
@Override
protected void onDestroy() {
Log.d(this, "onDestroy()... this = " + this);
+ InCallLowBatteryListener.getInstance().onDestroyInCallActivity();
InCallPresenter.getInstance().unsetActivity(this);
InCallPresenter.getInstance().updateIsChangingConfigurations();
super.onDestroy();
@@ -387,8 +427,13 @@
return isSafeToCommitTransactions();
}
+ /* package */ boolean isManageConferenceVisible() {
+ return (mConferenceManagerFragment != null && mConferenceManagerFragment.isVisible());
+ }
+
private boolean hasPendingDialogs() {
- return mDialog != null || (mAnswerFragment != null && mAnswerFragment.hasPendingDialogs());
+ return mDialog != null || (mAnswerFragment != null && mAnswerFragment.hasPendingDialogs())
+ || InCallCsRedialHandler.getInstance().hasPendingDialogs();
}
@Override
@@ -885,9 +930,16 @@
mDialog.dismiss();
mDialog = null;
}
+ SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
+ getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT);
+ if (dialogFragment != null) {
+ dialogFragment.dismiss();
+ }
if (mAnswerFragment != null) {
mAnswerFragment.dismissPendingDialogs();
}
+ InCallCsRedialHandler.getInstance().dismissPendingDialogs();
+ InCallLowBatteryListener.getInstance().dismissPendingDialogs();
}
/**
@@ -940,6 +992,113 @@
}
}
+ private void initializeDsdaSwitchTab() {
+ int phoneCount = InCallServiceImpl.sPhoneCount;
+ ActionBar bar = getActionBar();
+ View[] mDsdaTabLayout = new View[phoneCount];
+ int[] subString = {R.string.sub_1, R.string.sub_2};
+
+ Log.d(TAG, "initializeDsdaSwitchTab" + phoneCount);
+ for (int i = 0; i < phoneCount; i++) {
+ mDsdaTabLayout[i] = getLayoutInflater()
+ .inflate(R.layout.msim_tab_sub_info, null);
+
+ ((TextView)mDsdaTabLayout[i].findViewById(R.id.tabSubText))
+ .setText(subString[i]);
+
+ mDsdaTab[i] = bar.newTab().setCustomView(mDsdaTabLayout[i])
+ .setTabListener(new TabListener(i));
+ }
+ }
+
+ public void updateDsdaTab() {
+ int phoneCount = InCallServiceImpl.sPhoneCount;
+ ActionBar bar = getActionBar();
+
+ for (int i = 0; i < phoneCount; i++) {
+ int subId = QtiCallUtils.getSubId(i);
+ if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ && CallList.getInstance().hasAnyLiveCall(subId)) {
+ if (!mDsdaTabAdd[i]) {
+ addDsdaTab(i);
+ }
+ } else {
+ removeDsdaTab(i);
+ }
+ }
+
+ updateDsdaTabSelection();
+ }
+
+ private void addDsdaTab(int phoneId) {
+ ActionBar bar = getActionBar();
+ int tabCount = bar.getTabCount();
+
+ if (tabCount < phoneId) {
+ bar.addTab(mDsdaTab[phoneId], false);
+ } else {
+ bar.addTab(mDsdaTab[phoneId], phoneId, false);
+ }
+ mDsdaTabAdd[phoneId] = true;
+ Log.d(this, "addDsdaTab, phoneId = " + phoneId + " tab count = " + tabCount);
+ }
+
+ private void removeDsdaTab(int subId) {
+ ActionBar bar = getActionBar();
+ int tabCount = bar.getTabCount();
+
+ for (int i = 0; i < tabCount; i++) {
+ if (bar.getTabAt(i).equals(mDsdaTab[subId])) {
+ bar.removeTab(mDsdaTab[subId]);
+ mDsdaTabAdd[subId] = false;
+ return;
+ }
+ }
+ Log.d(this, "removeDsdaTab, subId = " + subId + " tab count = " + tabCount);
+ }
+
+ private void updateDsdaTabSelection() {
+ ActionBar bar = getActionBar();
+ int barCount = bar.getTabCount();
+
+ if (barCount == TAB_COUNT_ONE) {
+ bar.selectTab(bar.getTabAt(TAB_POSITION_FIRST));
+ } else if (barCount == TAB_COUNT_TWO) {
+ int phoneId = QtiCallUtils.getPhoneId(CallList
+ .getInstance().getActiveSubId());
+ bar.selectTab(bar.getTabAt(phoneId));
+ }
+ }
+
+ private class TabListener implements ActionBar.TabListener {
+ int mPhoneId;
+
+ public TabListener(int phoneId) {
+ mPhoneId = phoneId;
+ }
+
+ public void onTabSelected(Tab tab, FragmentTransaction ft) {
+ ActionBar bar = getActionBar();
+ int tabCount = bar.getTabCount();
+ Log.d(this, "onTabSelected mPhoneId:" + mPhoneId);
+ //Don't setActiveSubscription if tab count is 1.This is to avoid
+ //setting active subscription automatically when call on one sub
+ //ends and it's corresponding tab is removed.For such cases active
+ //subscription will be set by InCallPresenter.attemptFinishActivity.
+ int subId = QtiCallUtils.getSubId(mPhoneId);
+ if (tabCount != TAB_COUNT_ONE && CallList.getInstance().hasAnyLiveCall(subId)
+ && (CallList.getInstance().getActiveSubId() != subId)) {
+ Log.d(this, "Switch to other active sub: " + subId);
+ QtiCallUtils.switchToActiveSub(subId);
+ }
+ }
+
+ public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+ }
+
+ public void onTabReselected(Tab tab, FragmentTransaction ft) {
+ }
+ }
public OnTouchListener getDispatchTouchEventListener() {
return mDispatchTouchEventListener;
@@ -961,4 +1120,58 @@
mInCallOrientationEventListener.disable();
}
}
+
+ public static boolean isPhoneFeatureEnabled(Context context) {
+ return (UserHandle.myUserId() == UserHandle.USER_OWNER &&
+ context.getContentResolver().acquireProvider(URI_PHONE_FEATURE) != null);
+ }
+
+ public static Bundle callBinder(Context context, String method) {
+ if (!isPhoneFeatureEnabled(context)) {
+ return null;
+ }
+ return context.getContentResolver().call(URI_PHONE_FEATURE, method, null, null);
+ }
+
+ public boolean isCallRecording() {
+ boolean isRecording = false;
+ Bundle result = callBinder(InCallActivity.this, METHOD_IS_CALL_RECORD_RUNNING);
+
+ if (result != null) {
+ isRecording = result.getBoolean(EXTRA_RESULT);
+ }
+
+ return isRecording;
+ }
+
+ public boolean isCallRecorderEnabled() {
+ boolean isCallRecorderEnabled = false;
+ Bundle result = callBinder(InCallActivity.this, METHOD_IS_CALL_RECORD_AVAILABLE);
+
+ if (result != null) {
+ isCallRecorderEnabled = result.getBoolean(EXTRA_RESULT);
+ }
+ return isCallRecorderEnabled;
+ }
+
+ public void startInCallRecorder() {
+ callBinder(InCallActivity.this, METHOD_START_CALL_RECORD);
+ }
+
+ public void stopInCallRecorder() {
+ callBinder(InCallActivity.this, METHOD_STOP_CALL_RECORD);
+ }
+
+ public String getCallRecordingTime() {
+ long time = 0;
+ Bundle result = callBinder(InCallActivity.this, METHOD_GET_CALL_RECORD_DURATION);
+
+ if (result != null) {
+ time = result.getLong(EXTRA_RESULT) / 1000;
+ }
+
+ String recordingTime = String.format("%02d:%02d", time / 60, time % 60);
+
+ return recordingTime;
+ }
}
diff --git a/InCallUI/src/com/android/incallui/InCallAudioManager.java b/InCallUI/src/com/android/incallui/InCallAudioManager.java
new file mode 100644
index 0000000..3f0644e
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallAudioManager.java
@@ -0,0 +1,202 @@
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+
+/**
+ * This class implements QTI audio routing policy for upgrade/downgrade/merge call use cases
+ * based on the audio policy below.
+ *
+ * Audio policy for any user initiated action like making a video call, accepting an upgrade
+ * request or sending an upgrade/downgrade request is as follows : If any of the above user actions
+ * indicate a transition to a video call, we route audio to SPEAKER. If any of the above user
+ * actions indicate a voice call, we route to EARPIECE. For all other non-user initiated actions
+ * like an incoming downgrade request, we don't change the audio path and respect user's choice.
+ * For merge calls, we route to SPEAKER only if one of the calls being merged is a video call.
+ *
+ * Audio path routing for outgoing calls, incoming calls, add call and call waiting are handled
+ * in Telecom layer. Same audio policy is followed for those as well. Any user initiated action
+ * indicating a origin/acceptance of video call routes audio to SPEAKER or to EARPIECE for voice
+ * calls.
+ */
+public class InCallAudioManager {
+
+ private static InCallAudioManager sInCallAudioManager;
+
+ private final static String LOG_TAG = "InCallAudioManager";
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private InCallAudioManager() {
+ }
+
+ /**
+ * This method returns a singleton instance of {@class InCallAudioManager}
+ */
+ public static synchronized InCallAudioManager getInstance() {
+ if (sInCallAudioManager == null) {
+ sInCallAudioManager = new InCallAudioManager();
+ }
+ return sInCallAudioManager;
+ }
+
+ /**
+ * Called when user sends an upgrade/downgrade request. Route audio to speaker if the user
+ * sends an upgrade request to Video (bidirectional, transmit or receive) otherwise route
+ * audio to earpiece if it's a downgrade request.
+ */
+ public void onModifyCallClicked(final Call call,final int newVideoState) {
+ Log.v(this, "onModifyCallClicked: Call = " + call + "new video state = " +
+ newVideoState);
+
+ if (!VideoProfile.isVideo(newVideoState)) {
+ enableEarpiece();
+ } else if (canEnableSpeaker(call.getVideoState(),newVideoState)) {
+ enableSpeaker();
+ }
+ }
+
+ /**
+ * Called when user accepts an upgrade request. Route audio to speaker if the user accepts an
+ * upgrade request to Video (bidirectional, transmit or receive)
+ */
+ public void onAcceptUpgradeRequest(final Call call, final int videoState) {
+ Log.v(this, "onAcceptUpgradeRequest: Call = " + call + "video state = " +
+ videoState);
+ if (canEnableSpeaker(call.getVideoState(), videoState)) {
+ enableSpeaker();
+ }
+ }
+
+ /**
+ * Called when user accepts an incoming call. Route audio to speaker if the user accepts an
+ * incoming call as Video (bidirectional, transmit or receive) or to earpiece is the user
+ * accepts the call as Voice
+ */
+ public void onAnswerIncomingCall(final Call call, final int videoState) {
+ Log.v(this, "onAnswerIncomingCall: Call = " + call + "video state = " +
+ videoState);
+ if (!VideoProfile.isVideo(videoState)) {
+ enableEarpiece();
+ } else {
+ enableSpeaker();
+ }
+ }
+
+ /**
+ * Called when user clicks on merge calls from the UI. Route audio to speaker if one of the
+ * calls being merged is a video call.
+ */
+ public void onMergeClicked() {
+ Log.v(this, "onMergeClicked");
+
+ if (VideoUtils.isVideoCall(CallList.getInstance().getBackgroundCall()) ||
+ VideoUtils.isVideoCall(CallList.getInstance().getActiveCall())) {
+ enableSpeaker();
+ }
+ }
+
+ /**
+ * Determines if the speakerphone should be automatically enabled for the call. Speakerphone
+ * should be enabled if the call is a video call and if the adb property
+ * "persist.radio.call.audio.output" is true.
+ *
+ * @param videoState The video state of the call.
+ * @return {@code true} if the speakerphone should be enabled.
+ */
+ private static boolean canEnableSpeaker(int oldVideoState, int newVideoState) {
+ return !VideoProfile.isVideo(oldVideoState) &&
+ VideoProfile.isVideo(newVideoState) && isSpeakerEnabledForVideoCalls();
+ }
+
+ /**
+ * Determines if the speakerphone should be automatically enabled for video calls.
+ *
+ * @return {@code true} if the speakerphone should automatically be enabled.
+ */
+ private static boolean isSpeakerEnabledForVideoCalls() {
+ // TODO: we can't access adb properties from InCallUI. Need to refactor this.
+ return true;
+ }
+
+ /**
+ * Routes the call to the earpiece if audio is not being already routed to Earpiece and if
+ * bluetooth or wired headset is not connected.
+ */
+ private static void enableEarpiece() {
+ final TelecomAdapter telecomAdapter = TelecomAdapter.getInstance();
+ if (telecomAdapter == null) {
+ Log.e(LOG_TAG, "enableEarpiece: TelecomAdapter is null");
+ return;
+ }
+
+ final int currentAudioMode = AudioModeProvider.getInstance().getAudioMode();
+ Log.v(LOG_TAG, "enableEarpiece: Current audio mode is - " + currentAudioMode);
+
+ /*
+ * For MO video calls, we mark that audio needs to be routed to speaker during
+ * call setup phase and audio routing then takes place when call state transitions
+ * to DIALING. If user decides to modify low battery MO video call to voice call,
+ * audio route needs to be changed to earpiece for which we need to unmark speaker
+ * audio route selection that was done during video call setup phase. To avoid below
+ * API from being a no operation, do not check for CallAudioState.ROUTE_EARPIECE
+ */
+ if (QtiCallUtils.isNotEnabled(CallAudioState.ROUTE_BLUETOOTH |
+ CallAudioState.ROUTE_WIRED_HEADSET,
+ currentAudioMode)) {
+ Log.v(LOG_TAG, "enableEarpiece: Set audio route to earpiece");
+ telecomAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+ }
+ }
+
+ /**
+ * Routes the call to the speaker if audio is not being already routed to Speaker and if
+ * bluetooth or wired headset is not connected.
+ */
+ private static void enableSpeaker() {
+ final TelecomAdapter telecomAdapter = TelecomAdapter.getInstance();
+ if (telecomAdapter == null) {
+ Log.e(LOG_TAG, "enableSpeaker: TelecomAdapter is null");
+ return;
+ }
+
+ final int currentAudioMode = AudioModeProvider.getInstance().getAudioMode();
+ Log.v(LOG_TAG, "enableSpeaker: Current audio mode is - " + currentAudioMode);
+
+ if(QtiCallUtils.isNotEnabled(CallAudioState.ROUTE_SPEAKER |
+ CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET,
+ currentAudioMode)) {
+ Log.v(LOG_TAG, "enableSpeaker: Set audio route to speaker");
+ telecomAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+ }
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/InCallCsRedialHandler.java b/InCallUI/src/com/android/incallui/InCallCsRedialHandler.java
new file mode 100644
index 0000000..017d636
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallCsRedialHandler.java
@@ -0,0 +1,254 @@
+/* Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.WindowManager;
+import android.os.Bundle;
+
+import org.codeaurora.ims.utils.QtiCallUtils;
+import org.codeaurora.ims.QtiCallConstants;
+
+/*
+ * This class handles redialing a call on CS domain when current call ends with reason
+ * cs retry required
+ */
+public class InCallCsRedialHandler implements CallList.Listener {
+
+ private static InCallCsRedialHandler sInCallCsRedialHandler;
+ private Context mContext;
+ private CallList mCallList = null;
+ private AlertDialog mAlert = null;
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private InCallCsRedialHandler() {
+ }
+
+ /**
+ * Handles set up of the {@class InCallCsRedialHandler}. Instantiates the context needed by
+ * the class and adds a listener to listen to call substate changes.
+ */
+ public void setUp(Context context) {
+ mContext = context;
+ mCallList = CallList.getInstance();
+ mCallList.addListener(this);
+ }
+
+ /**
+ * Handles tear down of the {@class InCallCsRedialHandler}. Sets the context to null and
+ * unregisters it's call substate listener.
+ */
+ public void tearDown() {
+ mContext = null;
+ if (mCallList != null) {
+ mCallList.removeListener(this);
+ mCallList = null;
+ }
+ }
+
+ /**
+ * This method overrides onIncomingCall method of {@interface CallList.Listener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onIncomingCall(Call call) {
+ // no-op
+ }
+
+ /**
+ * This method overrides onCallListChange method of {@interface CallList.Listener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onCallListChange(CallList list) {
+ // no-op
+ }
+
+ /**
+ * This method overrides onUpgradeToVideo method of {@interface CallList.Listener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onUpgradeToVideo(Call call) {
+ // no-op
+ }
+
+ /**
+ * This method overrides onDisconnect method of {@interface CallList.Listener}
+ */
+ @Override
+ public void onDisconnect(Call call) {
+ Log.i(this, "onDisconnect");
+ checkForCsRetry(call);
+ }
+
+ /**
+ * This method returns a singleton instance of {@class InCallCsRedialHandler}
+ */
+ public static synchronized InCallCsRedialHandler getInstance() {
+ if (sInCallCsRedialHandler == null) {
+ sInCallCsRedialHandler = new InCallCsRedialHandler();
+ }
+ return sInCallCsRedialHandler;
+ }
+
+ /*
+ * This method gets fail cause value corresponding to EXTRAS_KEY_CALL_FAIL_EXTRA_CODE key
+ */
+ private int getFailCauseFromExtras(Bundle extras) {
+ int failCause = QtiCallConstants.DISCONNECT_CAUSE_UNSPECIFIED;
+ if (extras != null) {
+ failCause = extras.getInt(QtiCallConstants.EXTRAS_KEY_CALL_FAIL_EXTRA_CODE,
+ QtiCallConstants.DISCONNECT_CAUSE_UNSPECIFIED);
+ }
+ return failCause;
+ }
+
+ /*
+ * This method checks to see if CS Retry is required or not and if
+ * required, the method further checks the user selection option to decide
+ * whether to CS Redial automatically or based on user confirmation
+ */
+ private void checkForCsRetry(final Call call) {
+ final int failCause = getFailCauseFromExtras(call.getExtras());
+ Log.i(this, "checkForCsRetry failCause: " + failCause);
+ if (failCause != QtiCallConstants.CALL_FAIL_EXTRA_CODE_CALL_CS_RETRY_REQUIRED) {
+ return;
+ }
+
+ if (QtiCallUtils.isCsRetryEnabledByUser(mContext)) {
+ dialCsCall(call.getNumber());
+ } else {
+ showCsRedialDialogOnDisconnect(call.getNumber());
+ }
+ }
+
+ /*
+ * This method initiates a CS call
+ */
+ private void dialCsCall(String number) {
+ Log.i(this, "dialCsCall number: " + number);
+
+ final Uri uri = Uri.fromParts("tel", number, null);
+ final Intent intent = new Intent(Intent.ACTION_CALL, uri);
+ intent.putExtra(QtiCallConstants.EXTRA_CALL_DOMAIN, QtiCallConstants.DOMAIN_CS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(this, "Activity for dialing new call isn't found.");
+ }
+ }
+
+ /*
+ * If user confirmation is required to retry the call on CS domain, this method
+ * displays a dialog seeking user confirmation
+ */
+ private void showCsRedialDialogOnDisconnect(final String dialString) {
+ final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity();
+
+ if (inCallActivity == null) {
+ Log.e(this, "showCsRedialDialogOnDisconnect inCallActivity is NULL");
+ return;
+ }
+ inCallActivity.dismissPendingDialogs();
+
+ mAlert = new AlertDialog.Builder(inCallActivity).setTitle(R.string.cs_redial_option)
+ .setMessage(R.string.cs_redial_msg)
+ .setPositiveButton(R.string.cs_redial_yes, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialCsCall(dialString);
+ }
+ })
+ .setNegativeButton(R.string.cs_redial_no, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //No implementation. Added for completeness
+ }
+ })
+ .setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(final DialogInterface dialog) {
+ Log.d(this, "showCsRedialDialogOnDisconnect calling onDialogDismissed");
+ onDialogDismissed();
+ }
+ })
+ .create();
+
+ mAlert.setCanceledOnTouchOutside(false);
+ mAlert.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ mAlert.show();
+ }
+
+ /*
+ * This method returns true if dialog is showing else false
+ */
+ private boolean isCsRetryDialogShowing() {
+ return mAlert != null && mAlert.isShowing();
+ }
+
+ /**
+ * A dialog could have prevented in-call screen from being previously finished.
+ * This function checks to see if there should be any UI left and if not attempts
+ * to tear down the UI.
+ */
+ private void onDialogDismissed() {
+ mAlert = null;
+ InCallPresenter.getInstance().onDismissDialog();
+ }
+
+ /*
+ * This method dismisses the CS retry dialog
+ */
+ public void dismissPendingDialogs() {
+ if (isCsRetryDialogShowing()) {
+ mAlert.dismiss();
+ mAlert = null;
+ }
+ }
+
+ /*
+ * This method returns true if the dialog is still visible and waiting for user confirmation
+ * else false
+ */
+ public boolean hasPendingDialogs() {
+ return mAlert != null;
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/InCallLowBatteryListener.java b/InCallUI/src/com/android/incallui/InCallLowBatteryListener.java
new file mode 100644
index 0000000..bdc46e1
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallLowBatteryListener.java
@@ -0,0 +1,519 @@
+
+/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.DialogInterface.OnKeyListener;
+import android.os.Bundle;
+import android.telecom.VideoProfile;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.android.incallui.Call.State;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+import com.android.incallui.InCallPresenter.IncomingCallListener;
+import com.android.incallui.InCallPresenter.InCallStateListener;
+import com.android.incallui.InCallPresenter.InCallUiListener;
+
+import com.google.common.base.Preconditions;
+import org.codeaurora.ims.QtiCallConstants;
+import org.codeaurora.ims.QtiImsException;
+import org.codeaurora.ims.QtiImsExtManager;
+
+
+/**
+ * This class is responsible for processing video call low battery indication.
+ * On detecting a low battery Video call, user shall be notified via a warning
+ * dialog for user to take informed decision (i.e. convert video call to voice
+ * call or hangup the video call etc). This class maintains a low battery map
+ * and entry is added to this map on detecting a low battery video call. The
+ * low battery map holds key value pairs with call object as key and a boolean
+ * as value. The boolean value determines whether low battery indication for the
+ * call can be processed or not. The map holds boolean value TRUE if low battery
+ * indication for the call can be processed and holds FALSE when the low battery
+ * indication processing is deferred or is done. For eg. the map holds FALSE
+ * value on detecting a low battery MT Video call and the handling is deferred until
+ * user decides to answer the call as Video in which case the map holds TRUE value
+ * signalling that low battery indication for the call can be processed now.
+ */
+public class InCallLowBatteryListener implements CallList.Listener, InCallDetailsListener,
+ InCallUiListener {
+
+ private static InCallLowBatteryListener sInCallLowBatteryListener;
+ private PrimaryCallTracker mPrimaryCallTracker;
+ private CallList mCallList = null;
+ private AlertDialog mAlert = null;
+ private Map<Call, Boolean> mLowBatteryMap = new ConcurrentHashMap<Call, Boolean>();
+ private final Boolean PROCESS_LOW_BATTERY = true;
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private InCallLowBatteryListener() {
+ }
+
+ /**
+ * Handles set up of the {@class InCallLowBatteryListener}.
+ */
+ public void setUp(Context context) {
+ mPrimaryCallTracker = new PrimaryCallTracker();
+ mCallList = CallList.getInstance();
+ mCallList.addListener(this);
+ InCallPresenter.getInstance().addListener(mPrimaryCallTracker);
+ InCallPresenter.getInstance().addIncomingCallListener(mPrimaryCallTracker);
+ InCallPresenter.getInstance().addDetailsListener(this);
+ InCallPresenter.getInstance().addInCallUiListener(this);
+ }
+
+ /**
+ * Handles tear down of the {@class InCallLowBatteryListener}.
+ */
+ public void tearDown() {
+ if (mCallList != null) {
+ mCallList.removeListener(this);
+ mCallList = null;
+ }
+ InCallPresenter.getInstance().removeListener(mPrimaryCallTracker);
+ InCallPresenter.getInstance().removeIncomingCallListener(mPrimaryCallTracker);
+ InCallPresenter.getInstance().removeDetailsListener(this);
+ InCallPresenter.getInstance().removeInCallUiListener(this);
+ mLowBatteryMap.clear();
+ mPrimaryCallTracker = null;
+ }
+
+ /**
+ * This method returns a singleton instance of {@class InCallLowBatteryListener}
+ */
+ public static synchronized InCallLowBatteryListener getInstance() {
+ if (sInCallLowBatteryListener == null) {
+ sInCallLowBatteryListener = new InCallLowBatteryListener();
+ }
+ return sInCallLowBatteryListener;
+ }
+
+ /**
+ * This method overrides onIncomingCall method of {@interface CallList.Listener}
+ * @param call The call that is in incoming state
+ */
+ @Override
+ public void onIncomingCall(Call call) {
+ maybeAddToLowBatteryMap(call);
+ // if low battery dialog is already visible to user, dismiss it
+ dismissPendingDialogs();
+ }
+
+ /**
+ * This method overrides onCallListChange method of {@interface CallList.Listener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onCallListChange(CallList list) {
+ // no-op
+ }
+
+ /**
+ * This method overrides onUpgradeToVideo method of {@interface CallList.Listener}
+ * @param call The call for which upgrade request is received
+ */
+ @Override
+ public void onUpgradeToVideo(Call call) {
+ //if low battery dialog is visible to user, dismiss it
+ dismissPendingDialogs();
+ }
+
+ /**
+ * This method overrides onDisconnect method of {@interface CallList.Listener}
+ * @param call The call that is disconnected
+ */
+ @Override
+ public void onDisconnect(Call call) {
+ Log.d(this, "onDisconnect call: " + call);
+ if (call == null) {
+ return;
+ }
+
+ mLowBatteryMap.remove(call);
+ }
+
+ /**
+ * This API handles InCallActivity destroy when low battery dialog is showing
+ */
+ public void onDestroyInCallActivity() {
+ if (dismissPendingDialogs()) {
+ Log.i(this, "onDestroyInCallActivity dismissed low battery dialog");
+
+ /* Activity is destroyed when low battery dialog is showing, possibly
+ by removing the activity from recent tasks list etc. Handle this by
+ dismissing the existing low battery dialog and marking the entry
+ against the call in low battery map that the low battery indication
+ needs to be reprocessed for eg. when user brings back the call to
+ foreground by pulling it from notification bar */
+ Call call = mPrimaryCallTracker.getPrimaryCall();
+ if (call == null) {
+ Log.w(this, "onDestroyInCallActivity call is null");
+ return;
+ }
+ mLowBatteryMap.replace(call, PROCESS_LOW_BATTERY);
+ }
+ }
+
+ /**
+ * This API conveys if incall experience is showing or not.
+ *
+ * @param showing TRUE if incall experience is showing else FALSE
+ */
+ @Override
+ public void onUiShowing(boolean showing) {
+ Call call = mPrimaryCallTracker.getPrimaryCall();
+ Log.d(this, "onUiShowing showing: " + showing + " call = " + call);
+ if (call == null || !showing) {
+ return;
+ }
+
+ maybeProcessLowBatteryIndication(call);
+ }
+
+ /**
+ * When call is answered, this API checks to see if UE is under low battery or not
+ * and accordingly processes the low battery video call and returns TRUE if
+ * user action to answer the call is handled by this API else FALSE.
+ *
+ * @param call The call that is being answered
+ * @param videoState The videoState type with which user answered the MT call
+ */
+ public boolean onAnswerIncomingCall(Call call, int videoState) {
+ Log.d(this, "onAnswerIncomingCall = " + call + " videoState = " + videoState);
+ if (call == null || !mPrimaryCallTracker.isPrimaryCall(call)) {
+ return false;
+ }
+
+ if(!(isLowBatteryVideoCall(call) && VideoProfile.isBidirectional(videoState))) {
+ /* As user didnt accept the call as Video, low battery
+ processing for that call isn't required any more */
+ return false;
+ }
+
+ if (!mLowBatteryMap.containsKey(call)) {
+ Log.w(this, "onAnswerIncomingCall no call in low battery map");
+ return false;
+ }
+
+ /* There can be multiple user attempts to answer the call as Video from
+ HUN (HeadsUp Notification) by pulling the call from notification bar.
+ In such cases, avoid multiple processing of low battery indication */
+ if (!isLowBatteryDialogShowing()) {
+ /* There is user action to answer the call as Video. Update the low battery map
+ to indicate that low battery indication for this call can be processed now */
+ mLowBatteryMap.replace(call, PROCESS_LOW_BATTERY);
+ maybeProcessLowBatteryIndication(call);
+ }
+ return true;
+ }
+
+ private boolean isLowBattery(android.telecom.Call.Details details) {
+ final Bundle extras = (details != null) ? details.getExtras() : null;
+ final boolean isLowBattery = (extras != null) ? extras.getBoolean(
+ QtiCallConstants.LOW_BATTERY_EXTRA_KEY, false) : false;
+ Log.d(this, "isLowBattery : " + isLowBattery);
+ return isLowBattery;
+ }
+
+ private boolean isActiveUnPausedVideoCall(Call call) {
+ return VideoUtils.isActiveVideoCall(call) && !VideoProfile.isPaused(call.getVideoState());
+ }
+
+ private boolean isLowBatteryVideoCall(Call call) {
+ return call != null && VideoUtils.isVideoCall(call) &&
+ isLowBattery(call.getTelecomCall().getDetails());
+ }
+
+ // Return TRUE if low battery indication for the call can be processed else return FALSE
+ private boolean canProcessLowBatteryIndication(Call call) {
+ /* Low Battery indication can be processed if:
+ 1. Value stored against the call in low battery map is true
+ 2. InCallActivity is created */
+ if (call == null || InCallPresenter.getInstance().getActivity() == null) {
+ return false;
+ }
+
+ // we get null value if map contains no mapping for the key
+ if (mLowBatteryMap.get(call) != null) {
+ return mLowBatteryMap.get(call);
+ }
+
+ Log.w(this, "canProcessLowBatteryIndication no mapping for call in low battery map");
+ return false;
+ }
+
+ private void maybeAddToLowBatteryMap(Call call) {
+ if (call == null) {
+ return;
+ }
+
+ if(isLowBatteryVideoCall(call)) {
+ /* For MT Video calls, do not mark right away that Low battery indication
+ can to be processed since the handling kicks-in only after user decides
+ to answer the call as Video handled via onAnswerIncomingCall API*/
+ mLowBatteryMap.putIfAbsent(call, !VideoUtils.isIncomingVideoCall(call));
+ }
+ }
+
+ /**
+ * Handles changes to the details of the call.
+ *
+ * @param call The call for which the details changed.
+ * @param details The new call details.
+ */
+ @Override
+ public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+ Log.i(this, "onDetailsChanged call = " + call + " details = " + details);
+
+ if (mPrimaryCallTracker.getPrimaryCall() == null) {
+ /* primarycall is null may signal the possibility that there is only a single call and
+ is getting disconnected. So, try to dismiss low battery alert dialogue (if any). This
+ is to handle unintentional dismiss for add VT call use-cases wherein low battery alert
+ dialog is waiting for user input and the held call is remotely disconnected */
+ Log.i(this,"onDetailsChanged: no primary call.Clear the map/dismiss low battery alert");
+ mLowBatteryMap.remove(call);
+ dismissPendingDialogs();
+ return;
+ }
+
+ if (call == null || !mPrimaryCallTracker.isPrimaryCall(call)) {
+ Log.d(this," onDetailsChanged: call is null/Details not for primary call");
+ return;
+ }
+
+ /* Low Battery handling for MT Video call kicks in only when user decides
+ to answer the call as Video call so ignore the incoming video call
+ processing here for now */
+ if (VideoUtils.isIncomingVideoCall(call)) {
+ return;
+ }
+
+ if (!VideoUtils.isVideoCall(call)) {
+ /* There can be chances that Video call gets downgraded when
+ low battery alert dialog is waiting for user confirmation.
+ Handle this by dismissing the dialog (if any) */
+ dismissPendingDialogs();
+ return;
+ }
+
+ //process MO/Active Video call low battery indication
+ maybeAddToLowBatteryMap(call);
+ maybeProcessLowBatteryIndication(call);
+ }
+
+ /**
+ * disconnects MO video call that is waiting for user confirmation on
+ * low battery dialog
+ * @param call The probable call that may need to be disconnected
+ **/
+ private void maybeDisconnectMoCall(Call call) {
+ if (call == null || !isLowBatteryVideoCall(call)) {
+ return;
+ }
+
+ if (call.getState() == Call.State.DIALING) {
+ // dismiss the low battery dialog that is waiting for user input
+ dismissPendingDialogs();
+
+ Log.d(this, "disconnect MO call this is waiting for user input");
+ TelecomAdapter.getInstance().disconnectCall(call.getId());
+ }
+ }
+
+ private void maybeProcessLowBatteryIndication(Call call) {
+ if (!isLowBatteryVideoCall(call)) {
+ return;
+ }
+
+ if (canProcessLowBatteryIndication(call)) {
+ displayLowBatteryAlert(call);
+ //mark against the call that the respective low battery indication is processed
+ mLowBatteryMap.replace(call, !PROCESS_LOW_BATTERY);
+ }
+ }
+
+ /*
+ * This method displays one of below alert dialogs when UE is in low battery
+ * For Active Video Calls:
+ * 1. hangup alert dialog in absence of voice capabilities
+ * 2. downgrade to voice call alert dialog in the presence of voice
+ * capabilities
+ * For MT Video calls wherein user decided to accept the call as Video and for MO Video Calls:
+ * 1. alert dialog asking user confirmation to convert the video call to voice call or
+ * to continue the call as video call
+ * For MO Video calls, seek user confirmation to continue the video call as is or convert the
+ * video call to voice call
+ */
+ private void displayLowBatteryAlert(final Call call) {
+ //if low battery dialog is already visible to user, dismiss it
+ dismissPendingDialogs();
+
+ final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity();
+ if (inCallActivity == null) {
+ Log.w(this, "displayLowBatteryAlert inCallActivity is NULL");
+ return;
+ }
+
+ AlertDialog.Builder alertDialog = new AlertDialog.Builder(inCallActivity);
+ alertDialog.setTitle(R.string.low_battery);
+ alertDialog.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(final DialogInterface dialog) {
+ }
+ });
+
+ if (VideoUtils.isIncomingVideoCall(call)) {
+ alertDialog.setNegativeButton(R.string.low_battery_convert, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d(this, "displayLowBatteryAlert answer as Voice Call");
+ TelecomAdapter.getInstance().answerCall(call.getId(),
+ VideoProfile.STATE_AUDIO_ONLY);
+ }
+ });
+
+ alertDialog.setMessage(R.string.low_battery_msg);
+ alertDialog.setPositiveButton(R.string.low_battery_yes, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d(this, "displayLowBatteryAlert answer as Video Call");
+ TelecomAdapter.getInstance().answerCall(call.getId(),
+ VideoProfile.STATE_BIDIRECTIONAL);
+ }
+ });
+ } else if (VideoUtils.isOutgoingVideoCall(call)) {
+ alertDialog.setNegativeButton(R.string.low_battery_convert, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d(this, "displayLowBatteryAlert place Voice Call");
+ //Change the audio route to earpiece
+ InCallAudioManager.getInstance().onModifyCallClicked(call,
+ VideoProfile.STATE_AUDIO_ONLY);
+ try {
+ QtiImsExtManager.getInstance().resumePendingCall(
+ VideoProfile.STATE_AUDIO_ONLY);
+ } catch (QtiImsException e) {
+ Log.e(this, "resumePendingCall exception " + e);
+ }
+ }
+ });
+
+ alertDialog.setMessage(R.string.low_battery_msg);
+ alertDialog.setPositiveButton(R.string.low_battery_yes, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d(this, "displayLowBatteryAlert place Video Call");
+ try {
+ QtiImsExtManager.getInstance().resumePendingCall(
+ VideoProfile.STATE_BIDIRECTIONAL);
+ } catch (QtiImsException e) {
+ Log.e(this, "resumePendingCall exception " + e);
+ }
+ }
+ });
+ } else if (isActiveUnPausedVideoCall(call)) {
+ if (QtiCallUtils.hasVoiceCapabilities(call)) {
+ //active video call can be downgraded to voice
+ alertDialog.setMessage(R.string.low_battery_msg);
+ alertDialog.setPositiveButton(R.string.low_battery_yes, null);
+ alertDialog.setNegativeButton(R.string.low_battery_convert, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d(this, "displayLowBatteryAlert downgrading to voice call");
+ QtiCallUtils.downgradeToVoiceCall(call);
+ }
+ });
+ } else {
+ /* video call doesn't have downgrade capabilities, so alert the user
+ with a hangup dialog*/
+ alertDialog.setMessage(R.string.low_battery_hangup_msg);
+ alertDialog.setNegativeButton(R.string.low_battery_no, null);
+ alertDialog.setPositiveButton(R.string.low_battery_yes, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d(this, "displayLowBatteryAlert hanging up the call: " + call);
+ call.setState(Call.State.DISCONNECTING);
+ CallList.getInstance().onUpdate(call);
+ TelecomAdapter.getInstance().disconnectCall(call.getId());
+ }
+ });
+ }
+ }
+
+ mAlert = alertDialog.create();
+ mAlert.setOnKeyListener(new OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ Log.d(this, "on Alert displayLowBattery keyCode = " + keyCode);
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // On Back key press, disconnect MO low battery video call
+ // that is waiting for user input
+ maybeDisconnectMoCall(call);
+ return true;
+ }
+ return false;
+ }
+ });
+ mAlert.setCanceledOnTouchOutside(false);
+ mAlert.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ mAlert.show();
+ }
+
+ /*
+ * This method returns true if dialog is showing else false
+ */
+ private boolean isLowBatteryDialogShowing() {
+ return mAlert != null && mAlert.isShowing();
+ }
+
+ /*
+ * This method dismisses the low battery dialog and
+ * returns true if dialog is dimissed else false
+ */
+ public boolean dismissPendingDialogs() {
+ if (isLowBatteryDialogShowing()) {
+ mAlert.dismiss();
+ mAlert = null;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/InCallMessageController.java b/InCallUI/src/com/android/incallui/InCallMessageController.java
new file mode 100644
index 0000000..aa5a57e
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallMessageController.java
@@ -0,0 +1,521 @@
+/* Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkState;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+import com.android.incallui.InCallVideoCallCallbackNotifier.VideoEventListener;
+import com.android.incallui.InCallVideoCallCallbackNotifier.SessionModificationListener;
+import org.codeaurora.ims.QtiCallConstants;
+import org.codeaurora.ims.QtiCarrierConfigs;
+import org.codeaurora.ims.QtiVideoCallDataUsage;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
+/**
+ * This class listens to incoming events for the listener classes it implements. It should
+ * handle all UI notification to be shown to the user for any indication that is required to be
+ * shown like call substate indication, video quality indication, etc.
+ * For e.g., this class implements {@class InCallSubstateListener} and when call substate changes,
+ * {@class CallSubstateNotifier} notifies it through the onCallSubstateChanged callback.
+ */
+public class InCallMessageController implements InCallSubstateListener, VideoEventListener,
+ CallList.Listener, SessionModificationListener, InCallSessionModificationCauseListener,
+ InCallDetailsListener {
+
+ private INetworkStatsService mStatsService;
+ private IConnectivityManager mConnManager;
+ private long previousLteUsage;
+ private long previousWlanUsage;
+
+ private static InCallMessageController sInCallMessageController;
+
+ private PrimaryCallTracker mPrimaryCallTracker;
+
+ private Context mContext;
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private InCallMessageController() {
+ }
+
+ /**
+ * Handles set up of the {@class InCallMessageController}. Instantiates the context needed by
+ * the class and adds a listener to listen to call substate changes, video event changes,
+ * session modification cause changes, call state changes.
+ */
+ public void setUp(Context context) {
+ mContext = context;
+ mPrimaryCallTracker = new PrimaryCallTracker();
+ CallSubstateNotifier.getInstance().addListener(this);
+ InCallVideoCallCallbackNotifier.getInstance().addVideoEventListener(this);
+ CallList.getInstance().addListener(this);
+ SessionModificationCauseNotifier.getInstance().addListener(this);
+ InCallPresenter.getInstance().addListener(mPrimaryCallTracker);
+ InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(this);
+ InCallPresenter.getInstance().addDetailsListener(this);
+ mStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ mConnManager = IConnectivityManager.Stub.asInterface(
+ ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ }
+
+ /**
+ * Handles tear down of the {@class InCallMessageController}. Sets the context to null and
+ * unregisters it's call substate, video event, session modification cause, call state
+ * listeners.
+ */
+ public void tearDown() {
+ mContext = null;
+ CallSubstateNotifier.getInstance().removeListener(this);
+ InCallVideoCallCallbackNotifier.getInstance().removeVideoEventListener(this);
+ CallList.getInstance().removeListener(this);
+ SessionModificationCauseNotifier.getInstance().removeListener(this);
+ InCallPresenter.getInstance().removeListener(mPrimaryCallTracker);
+ InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this);
+ InCallPresenter.getInstance().removeDetailsListener(this);
+ mPrimaryCallTracker = null;
+ mStatsService = null;
+ mConnManager = null;
+ previousLteUsage = 0;
+ previousWlanUsage = 0;
+ }
+
+ /**
+ * This method returns a singleton instance of {@class InCallMessageController}
+ */
+ public static synchronized InCallMessageController getInstance() {
+ if (sInCallMessageController == null) {
+ sInCallMessageController = new InCallMessageController();
+ }
+ return sInCallMessageController;
+ }
+
+ /**
+ * This method overrides onCallSubstateChanged method of {@interface InCallSubstateListener}
+ * We are notified when call substate changes and display a toast message on the UI.
+ */
+ @Override
+ public void onCallSubstateChanged(final Call call, final int callSubstate) {
+ Log.d(this, "onCallSubstateChanged - Call : " + call + " call substate changed to " +
+ callSubstate);
+
+ if (mContext == null || !mPrimaryCallTracker.isPrimaryCall(call)) {
+ Log.e(this, "onCallSubstateChanged - Context is null/not primary call.");
+ return;
+ }
+
+ String callSubstateChangedText = "";
+
+ if (QtiCallUtils.isEnabled(
+ QtiCallConstants.CALL_SUBSTATE_AUDIO_CONNECTED_SUSPENDED, callSubstate)) {
+ callSubstateChangedText +=
+ mContext.getResources().getString(
+ R.string.call_substate_connected_suspended_audio);
+ }
+
+ if (QtiCallUtils.isEnabled(
+ QtiCallConstants.CALL_SUBSTATE_VIDEO_CONNECTED_SUSPENDED, callSubstate)) {
+ callSubstateChangedText +=
+ mContext.getResources().getString(
+ R.string.call_substate_connected_suspended_video);
+ }
+
+ if (QtiCallUtils.isEnabled(QtiCallConstants.CALL_SUBSTATE_AVP_RETRY, callSubstate)) {
+ callSubstateChangedText +=
+ mContext.getResources().getString(R.string.call_substate_avp_retry);
+ }
+
+ if (QtiCallUtils.isNotEnabled(QtiCallConstants.CALL_SUBSTATE_ALL, callSubstate)) {
+ callSubstateChangedText =
+ mContext.getResources().getString(R.string.call_substate_call_resumed);
+ }
+
+ if (!callSubstateChangedText.isEmpty()) {
+ String callSubstateLabelText = mContext.getResources().getString(
+ R.string.call_substate_label);
+ QtiCallUtils.displayToast(mContext, callSubstateLabelText + callSubstateChangedText);
+ }
+ }
+
+ /**
+ * This method overrides onVideoQualityChanged method of {@interface VideoEventListener}
+ * We are notified when video quality of the call changed and display a message on the UI.
+ */
+ @Override
+ public void onVideoQualityChanged(final Call call, final int videoQuality) {
+ Log.d(this, "onVideoQualityChanged: - Call : " + call + " Video quality changed to " +
+ videoQuality);
+
+ if (mContext == null || !mPrimaryCallTracker.isPrimaryCall(call)) {
+ Log.e(this, "onVideoQualityChanged - Context is null/not primary call.");
+ return;
+ }
+ if (QtiImsExtUtils.isCarrierConfigEnabled(mContext,
+ QtiCarrierConfigs.SHOW_VIDEO_QUALITY_TOAST)) {
+ final Resources resources = mContext.getResources();
+ final String videoQualityChangedText = resources.getString(
+ R.string.video_quality_changed) + resources.getString(
+ QtiCallUtils.getVideoQualityResourceId(videoQuality));
+ QtiCallUtils.displayToast(mContext, videoQualityChangedText);
+ }
+ }
+
+ /**
+ * This method overrides onCallSessionEvent method of {@interface VideoEventListener}
+ * We are notified when a new call session event is sent and display a message on the UI.
+ */
+ @Override
+ public void onCallSessionEvent(final int event) {
+ Log.d(this, "onCallSessionEvent: event = " + event);
+
+ if (mContext == null) {
+ Log.e(this, "onCallSessionEvent - Context is null.");
+ return;
+ }
+ if (QtiImsExtUtils.isCarrierConfigEnabled(mContext,
+ QtiCarrierConfigs.SHOW_CALL_SESSION_EVENT_TOAST)) {
+ QtiCallUtils.displayToast(mContext, QtiCallUtils.getCallSessionResourceId(event));
+ }
+ }
+
+ /**
+ * Handles changes to the details of the call.
+ *
+ * @param call The call for which the details changed.
+ * @param details The new call details.
+ */
+ @Override
+ public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+ Log.d(this, " onDetailsChanged call=" + call + " details=" + details);
+
+ if (mContext == null || details == null) {
+ Log.e(this," onDetailsChanged: context/details is null");
+ return;
+ }
+
+ final Bundle extras = details.getExtras();
+ final QtiVideoCallDataUsage dataUsage = (extras != null) ?
+ (QtiVideoCallDataUsage)extras.getParcelable(
+ QtiCallConstants.VIDEO_CALL_DATA_USAGE_KEY) : null;
+ if (dataUsage == null) {
+ return;
+ }
+
+ long lteUsage = dataUsage.getLteDataUsage();
+ long wlanUsage = dataUsage.getWlanDataUsage();
+ Log.i(this, "onDetailsChanged LTE data value = " + lteUsage + " WiFi data value = " +
+ wlanUsage);
+
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_video_call_datausage_enable)) {
+ recordDataUsage(lteUsage, wlanUsage);
+ }
+
+
+ if (QtiImsExtUtils.isCarrierConfigEnabled(mContext,
+ QtiCarrierConfigs.SHOW_DATA_USAGE_TOAST)) {
+ final String dataUsageChangedText = mContext.getResources().getString(
+ R.string.wlan_data_usage_label) + wlanUsage;
+ QtiCallUtils.displayToast(mContext, dataUsageChangedText);
+ }
+ }
+
+ /**
+ * This method overrides onCallDataUsageChange method of {@interface VideoEventListener}
+ * We are notified when data usage is changed and display a message on the UI.
+ */
+ @Override
+ public void onCallDataUsageChange(final long dataUsage) {
+ Log.d(this, "onCallDataUsageChange: dataUsage = " + dataUsage);
+ if (QtiImsExtUtils.isCarrierConfigEnabled(mContext,
+ QtiCarrierConfigs.SHOW_DATA_USAGE_TOAST)) {
+ final String dataUsageChangedText = mContext.getResources().getString(
+ R.string.lte_data_usage_label) + dataUsage;
+ QtiCallUtils.displayToast(mContext, dataUsageChangedText);
+ }
+ }
+
+ /**
+ * This method overrides onPeerPauseStateChanged method of {@interface VideoEventListener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onPeerPauseStateChanged(final Call call, final boolean paused) {
+ //no-op
+ }
+
+ /**
+ * This method overrides onIncomingCall method of {@interface CallList.Listener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onIncomingCall(Call call) {
+ // no-op
+ }
+
+ /**
+ * This method overrides onCallListChange method of {@interface CallList.Listener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onCallListChange(CallList list) {
+ // no-op
+ }
+
+ /**
+ * This method overrides onUpgradeToVideo method of {@interface CallList.Listener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onUpgradeToVideo(Call call) {
+ // no-op
+ }
+
+ /**
+ * This method overrides onDisconnect method of {@interface CallList.Listener}
+ */
+ @Override
+ public void onDisconnect(final Call call) {
+ Bundle extras = call.getExtras();
+ if (extras == null) {
+ Log.w(this, "onDisconnect: null Extras");
+ return;
+ }
+ final int errorCode = extras.getInt(QtiCallConstants.EXTRAS_KEY_CALL_FAIL_EXTRA_CODE,
+ QtiCallConstants.DISCONNECT_CAUSE_UNSPECIFIED);
+ Log.d(this, "onDisconnect: code = " + errorCode);
+ showCallDisconnectInfo(errorCode);
+ }
+
+ /**
+ * This method displays specific disconnect information to user
+ */
+ private void showCallDisconnectInfo(int errorCode) {
+ if(errorCode == QtiCallConstants.DISCONNECT_CAUSE_UNSPECIFIED) {
+ return;
+ }
+
+ switch (errorCode) {
+ case QtiCallConstants.CALL_FAIL_EXTRA_CODE_LTE_3G_HA_FAILED:
+ QtiCallUtils.displayToast(mContext, R.string.call_failed_ho_not_feasible);
+ break;
+ case QtiCallConstants.CALL_FAIL_EXTRA_CODE_LOCAL_LOW_BATTERY:
+ QtiCallUtils.displayToast(mContext, R.string.call_failed_due_to_low_battery);
+ break;
+ case QtiCallConstants.CALL_FAIL_EXTRA_CODE_LOCAL_VALIDATE_NUMBER:
+ QtiCallUtils.displayToast(mContext, R.string.call_failed_due_to_validate_number);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Handles any session modifictaion cause changes in the call.
+ *
+ * @param call The call for which orientation mode changed.
+ * @param sessionModificationCause The new session modifictaion cause
+ */
+ @Override
+ public void onSessionModificationCauseChanged(Call call, int sessionModificationCause) {
+ Log.d(this, "onSessionModificationCauseChanged: Call : " + call +
+ " Call modified due to " + sessionModificationCause);
+
+ if (mContext == null || !mPrimaryCallTracker.isPrimaryCall(call)) {
+ Log.e(this,
+ "onSessionModificationCauseChanged- Context is null/not primary call.");
+ return;
+ }
+
+ QtiCallUtils.displayToast(mContext,
+ getSessionModificationCauseResourceId(sessionModificationCause));
+ }
+
+ /**
+ * This method returns the string resource id (i.e. display string) that corresponds to the
+ * session modification cause code.
+ */
+ private static int getSessionModificationCauseResourceId(int cause) {
+ switch(cause) {
+ case QtiCallConstants.CAUSE_CODE_UNSPECIFIED:
+ return R.string.session_modify_cause_unspecified;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_UPGRADE_LOCAL_REQ:
+ return R.string.session_modify_cause_upgrade_local_request;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_UPGRADE_REMOTE_REQ:
+ return R.string.session_modify_cause_upgrade_remote_request;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_LOCAL_REQ:
+ return R.string.session_modify_cause_downgrade_local_request;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_REMOTE_REQ:
+ return R.string.session_modify_cause_downgrade_remote_request;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_RTP_TIMEOUT:
+ return R.string.session_modify_cause_downgrade_rtp_timeout;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_QOS:
+ return R.string.session_modify_cause_downgrade_qos;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_PACKET_LOSS:
+ return R.string.session_modify_cause_downgrade_packet_loss;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_LOW_THRPUT:
+ return R.string.session_modify_cause_downgrade_low_thrput;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_THERM_MITIGATION:
+ return R.string.session_modify_cause_downgrade_thermal_mitigation;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_LIPSYNC:
+ return R.string.session_modify_cause_downgrade_lipsync;
+ case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_GENERIC_ERROR:
+ default:
+ return R.string.session_modify_cause_downgrade_generic_error;
+ }
+ }
+
+ @Override
+ public void onUpgradeToVideoRequest(Call call, int videoState) {
+ //no-op
+ }
+
+ @Override
+ public void onUpgradeToVideoFail(int error, Call call) {
+ Log.d(this, "onUpgradeToVideoFail: error = " + error);
+ showUpgradeFailInfo(error);
+ }
+
+ private void showUpgradeFailInfo(int errorCode) {
+ switch (errorCode) {
+ case QtiCallConstants.SESSION_MODIFY_REQUEST_FAILED_LOW_BATTERY:
+ QtiCallUtils.displayToast(mContext,
+ R.string.modify_call_failed_due_to_low_battery);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * This method is used to ignore the repeatly calling onDetailsChanged
+ */
+ private boolean hasDataUsageChanged(long lteUsage, long wlanUsage) {
+ boolean hasChanged = false;
+ if (previousLteUsage != lteUsage) {
+ hasChanged = true;
+ previousLteUsage = lteUsage;
+ }
+ if (previousWlanUsage != wlanUsage) {
+ hasChanged = true;
+ previousWlanUsage = wlanUsage;
+ }
+ return hasChanged;
+ }
+
+ private void recordDataUsage(long lteUsage, long wlanUsage) {
+ String wifiIface;
+ String imsIface;
+ if(!hasDataUsageChanged(lteUsage, wlanUsage)) {
+ return;
+ }
+
+ if (wlanUsage != 0) {
+ wifiIface = getWifiIface();
+ if (wifiIface != null)
+ recordUsage(wifiIface, ConnectivityManager.TYPE_WIFI, wlanUsage, 0);
+ }
+
+ if (lteUsage != 0 ) {
+ imsIface = getImsIface();
+ if (imsIface != null)
+ recordUsage(imsIface, ConnectivityManager.TYPE_MOBILE, lteUsage, 0);
+ }
+ }
+
+ private void recordUsage(String ifaces, int ifaceType, long rx, long tx) {
+ if (ifaces == null) {
+ Log.d(this, "recordDataUseage ifaces is null");
+ return;
+ }
+ Log.d(this,"recordDataUseage ifaces ="+ ifaces + " ifaceType=" + ifaceType +
+ "rx = " + rx + " tx =" + tx);
+
+ try {
+ mStatsService.recordVideoCallData(ifaces, ifaceType, rx, tx);
+ } catch (RuntimeException e) {
+ Log.e(this, "recordDataUseage RuntimeException" + e);
+ } catch (RemoteException e) {
+ Log.e(this, "recordDataUseage RemoteException" + e);
+ }
+ }
+
+ private String getImsIface() {
+ final NetworkState[] states;
+ try {
+ states = mConnManager.getAllNetworkState();
+ } catch (RemoteException e) {
+ Log.e(this, "getVoiceCallIfaces RemoteException" + e);
+ return null;
+ }
+
+ if (states != null) {
+ for (NetworkState state : states) {
+ if (state.networkInfo.isConnected() && state.networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(
+ state.networkInfo.getType());
+ final String baseIface = state.linkProperties.getInterfaceName();
+ if (isMobile)
+ return baseIface;
+ }
+ }
+ }
+ return null;
+ }
+
+ private String getWifiIface() {
+ final LinkProperties wifiLinkProperties;
+ try {
+ wifiLinkProperties =
+ mConnManager.getLinkPropertiesForType(ConnectivityManager.TYPE_WIFI);
+ if (wifiLinkProperties != null) {
+ return wifiLinkProperties.getInterfaceName();
+ }
+ } catch (RemoteException e) {
+ Log.e(this, "get wifi Iface RemoteException" + e);
+ }
+ return null;
+ }
+
+
+}
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index c3ca6de..0acc35e 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -20,13 +20,16 @@
import android.app.ActivityManager.TaskDescription;
import android.app.FragmentManager;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Handler;
+import android.os.PowerManager;
import android.provider.CallLog;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
@@ -123,6 +126,8 @@
private InCallCameraManager mInCallCameraManager = null;
private AnswerPresenter mAnswerPresenter = new AnswerPresenter();
private FilteredNumberAsyncQueryHandler mFilteredQueryHandler;
+ private PowerManager mPowerManager;
+ private PowerManager.WakeLock mWakeLock = null;
/**
* Whether or not we are currently bound and waiting for Telecom to send us a new call.
@@ -331,8 +336,16 @@
mProximitySensor = proximitySensor;
addListener(mProximitySensor);
+ // dismiss any pending dialogues related to earlier call, which
+ // are no longer relevant now.
+ if (isActivityStarted()) {
+ mInCallActivity.dismissPendingDialogs();
+ }
addIncomingCallListener(mAnswerPresenter);
addInCallUiListener(mAnswerPresenter);
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK |
+ PowerManager.ACQUIRE_CAUSES_WAKEUP, "InCallPresenter");
mCallList = callList;
mExternalCallList = externalCallList;
@@ -345,7 +358,10 @@
// will kick off an update and the whole process can start.
mCallList.addListener(this);
+ InCallCsRedialHandler.getInstance().setUp(mContext);
+ InCallUiStateNotifier.getInstance().setUp(mContext);
VideoPauseController.getInstance().setUp(this);
+ InCallLowBatteryListener.getInstance().setUp(mContext);
InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(this);
mFilteredQueryHandler = new FilteredNumberAsyncQueryHandler(context.getContentResolver());
@@ -353,6 +369,14 @@
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
mCallList.setFilteredNumberQueryHandler(mFilteredQueryHandler);
+ InCallMessageController.getInstance().setUp(mContext);
+ OrientationModeHandler.getInstance().setUp();
+ addDetailsListener(CallSubstateNotifier.getInstance());
+ addDetailsListener(SessionModificationCauseNotifier.getInstance());
+ CallList.getInstance().addListener(CallSubstateNotifier.getInstance());
+ CallList.getInstance().addListener(SessionModificationCauseNotifier.getInstance());
+
+ InCallZoomController.getInstance().setUp(mContext);
Log.d(this, "Finished InCallPresenter.setUp");
}
@@ -373,12 +397,30 @@
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
VideoPauseController.getInstance().tearDown();
+ InCallUiStateNotifier.getInstance().tearDown();
+ InCallLowBatteryListener.getInstance().tearDown();
InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this);
+ InCallMessageController.getInstance().tearDown();
+ OrientationModeHandler.getInstance().tearDown();
+ removeDetailsListener(CallSubstateNotifier.getInstance());
+
+ InCallZoomController.getInstance().tearDown();
+ removeDetailsListener(SessionModificationCauseNotifier.getInstance());
+ CallList.getInstance().removeListener(CallSubstateNotifier.getInstance());
+ CallList.getInstance().removeListener(SessionModificationCauseNotifier.getInstance());
}
private void attemptFinishActivity() {
final boolean doFinish = (mInCallActivity != null && isActivityStarted());
Log.i(this, "Hide in call UI: " + doFinish);
+
+ if ((mCallList != null)
+ && (CallList.getInstance().isDsdaEnabled())
+ && !(mCallList.hasAnyLiveCall(mCallList.getActiveSubId()))) {
+ Log.d(this, "Switch active sub");
+ if (mCallList.switchToOtherActiveSub()) return;
+ }
+
if (doFinish) {
mInCallActivity.setExcludeFromRecents(true);
mInCallActivity.finish();
@@ -661,6 +703,9 @@
callList.getOutgoingCall() != null;
mInCallActivity.dismissKeyguard(hasCall);
}
+ if (CallList.getInstance().isDsdaEnabled() && (mInCallActivity != null)) {
+ mInCallActivity.updateDsdaTab();
+ }
}
/**
@@ -679,6 +724,11 @@
for (IncomingCallListener listener : mIncomingCallListeners) {
listener.onIncomingCall(oldState, mInCallState, call);
}
+
+ if (CallList.getInstance().isDsdaEnabled() && (mInCallActivity != null)) {
+ mInCallActivity.updateDsdaTab();
+ }
+ wakeUpScreen();
}
@Override
@@ -703,6 +753,7 @@
if (call.isEmergencyCall()) {
FilteredNumbersUtil.recordLastEmergencyCallTime(mContext);
}
+ wakeUpScreen();
}
@Override
@@ -713,9 +764,15 @@
return;
}
+ wakeUpScreen();
call.setRequestedVideoState(videoState);
}
+ @Override
+ public void onUpgradeToVideoFail(int error, Call call) {
+ //NO-OP
+ }
+
/**
* Given the call list, return the state in which the in-call screen should be.
*/
@@ -903,7 +960,7 @@
/**
* Answers any incoming call.
*/
- public void answerIncomingCall(Context context, int videoState) {
+ public void answerIncomingCall(Context context) {
// By the time we receive this intent, we could be shut down and call list
// could be null. Bail in those cases.
if (mCallList == null) {
@@ -913,7 +970,29 @@
Call call = mCallList.getIncomingCall();
if (call != null) {
+ answerIncomingCall(context, call.getVideoState());
+ }
+ }
+
+ /**
+ * Answers any incoming call.
+ */
+ public void answerIncomingCall(Context context, int videoState) {
+ // By the time we receive this intent, we could be shut down and call list
+ // could be null. Bail in those cases.
+ if (mCallList == null) {
+ StatusBarNotifier.clearAllCallNotifications(context);
+ return;
+ }
+
+ Call call = mCallList.getIncomingCall();
+ if (call != null && !InCallLowBatteryListener.getInstance().
+ onAnswerIncomingCall(call, videoState)) {
TelecomAdapter.getInstance().answerCall(call.getId(), videoState);
+ InCallAudioManager.getInstance().onAnswerIncomingCall(call, videoState);
+ }
+
+ if (call != null) {
showInCall(false, false/* newOutgoingCall */);
}
}
@@ -935,6 +1014,19 @@
}
}
+ public void acceptUpgradeRequest(Context context) {
+ if (mCallList == null) {
+ StatusBarNotifier.clearAllCallNotifications(context);
+ Log.e(this, " acceptUpgradeRequest mCallList is empty so returning");
+ return;
+ }
+
+ Call call = mCallList.getVideoUpgradeRequestCall();
+ if (call != null) {
+ acceptUpgradeRequest(call.getRequestedVideoState(), context);
+ }
+ }
+
public void acceptUpgradeRequest(int videoState, Context context) {
Log.d(this, " acceptUpgradeRequest videoState " + videoState);
// Bail if we have been shut down and the call list is null.
@@ -949,6 +1041,7 @@
VideoProfile videoProfile = new VideoProfile(videoState);
call.getVideoCall().sendSessionModifyResponse(videoProfile);
call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
+ InCallAudioManager.getInstance().onAcceptUpgradeRequest(call, videoState);
}
}
@@ -986,6 +1079,14 @@
}
/**
+ * Returns true if the manage conference ui is in foreground.
+ */
+ public boolean isShowingManageConferenceUi() {
+ return (isActivityStarted() && mInCallActivity.isVisible()
+ && mInCallActivity.isManageConferenceVisible());
+ }
+
+ /**
* Returns true if the activity has been created and is running.
* Returns true as long as activity is not destroyed or finishing. This ensures that we return
* true even if the activity is paused (not in foreground).
@@ -1072,20 +1173,26 @@
/*package*/
void onActivityStarted() {
Log.d(this, "onActivityStarted");
- notifyVideoPauseController(true);
+ notifyInCallUiStateNotifier(true);
+ if (mStatusBarNotifier != null) {
+ mStatusBarNotifier.updateCallStatusBar(mCallList);
+ }
}
/*package*/
void onActivityStopped() {
Log.d(this, "onActivityStopped");
- notifyVideoPauseController(false);
+ notifyInCallUiStateNotifier(false);
+ if (mStatusBarNotifier != null ) {
+ mStatusBarNotifier.updateCallStatusBar(mCallList);
+ }
}
- private void notifyVideoPauseController(boolean showing) {
- Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" +
+ private void notifyInCallUiStateNotifier(boolean showing) {
+ Log.d(this, "notifyInCallUiStateNotifier: mIsChangingConfigurations=" +
mIsChangingConfigurations);
if (!mIsChangingConfigurations) {
- VideoPauseController.getInstance().onUiShowing(showing);
+ InCallUiStateNotifier.getInstance().onUiShowing(showing);
}
}
@@ -1270,6 +1377,18 @@
}
/**
+ * Update color of sim card icon
+ */
+ public void updatePrimaryCallState() {
+ for (InCallEventListener listener : mInCallEventListeners) {
+ if (listener instanceof CallCardPresenter) {
+ listener.updatePrimaryCallState();
+ break;
+ }
+ }
+ }
+
+ /**
* Called by the {@link CallCardPresenter} to inform of a change in visibility of the secondary
* caller info bar.
*
@@ -1283,6 +1402,15 @@
}
}
+ /**
+ * Called by the {@link VideoCallPresenter} to inform of a change in availability of
+ * incoming video stream.
+ */
+ public void notifyIncomingVideoAvailabilityChanged(boolean isAvailable) {
+ for (InCallEventListener listener : mInCallEventListeners) {
+ listener.onIncomingVideoAvailabilityChanged(isAvailable);
+ }
+ }
/**
* For some disconnected causes, we show a dialog. This calls into the activity to show
@@ -1307,9 +1435,24 @@
// TODO: Consider a proper state machine implementation
+ //If the call is auto answered bring up the InCallActivity
+ boolean isAutoAnswer = false;
+
+ if ((mCallList.getDisconnectedCall() == null) &&
+ (mCallList.getDisconnectingCall() == null)) {
+ isAutoAnswer = (mInCallState == InCallState.INCOMING) &&
+ (newState == InCallState.INCALL) &&
+ (mInCallActivity == null);
+ }
+
+ Log.d(this, "startOrFinishUi: " + isAutoAnswer);
+
+ boolean isAnyOtherSubActive = InCallState.INCOMING == newState &&
+ mCallList.isAnyOtherSubActive(mCallList.getActiveSubId());
+
// If the state isn't changing we have already done any starting/stopping of activities in
// a previous pass...so lets cut out early
- if (newState == mInCallState) {
+ if ((newState == mInCallState) && !(mInCallActivity == null && isAnyOtherSubActive)) {
return newState;
}
@@ -1373,6 +1516,13 @@
showCallUi |= InCallState.PENDING_OUTGOING == newState && mainUiNotVisible
&& isCallWithNoValidAccounts(mCallList.getPendingOutgoingCall());
+ // Handle transition from InCallState.WAITING_FOR_ACCOUNT to InCallState.INCALL and
+ // and there is a call alive, this case can come for DSDA and hence we should show
+ // UI in such case.
+ showCallUi |= (newState == InCallState.INCALL) &&
+ (mInCallState == InCallState.WAITING_FOR_ACCOUNT) && (mCallList.hasLiveCall() ||
+ (mCallList.getBackgroundCall() != null));
+
// The only time that we have an instance of mInCallActivity and it isn't started is
// when it is being destroyed. In that case, lets avoid bringing up another instance of
// the activity. When it is finally destroyed, we double check if we should bring it back
@@ -1383,7 +1533,7 @@
return mInCallState;
}
- if (showCallUi || showAccountPicker) {
+ if (showCallUi || showAccountPicker || isAutoAnswer) {
Log.i(this, "Start in call UI");
showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
} else if (startIncomingCallSequence) {
@@ -1392,6 +1542,7 @@
// We're about the bring up the in-call UI for an incoming call. If we still have
// dialogs up, we need to clear them out before showing incoming screen.
if (isActivityStarted()) {
+ mInCallActivity.showCallCardFragment(true);
mInCallActivity.dismissPendingDialogs();
}
if (!startUi(newState)) {
@@ -1467,6 +1618,7 @@
}
private boolean startUi(InCallState inCallState) {
+ final Call incomingCall = mCallList.getIncomingCall();
boolean isCallWaiting = mCallList.getActiveCall() != null &&
mCallList.getIncomingCall() != null;
@@ -1477,7 +1629,13 @@
// There should be no jank from this since the screen is already off and will remain so
// until our new activity is up.
- if (isCallWaiting) {
+ // In addition to call waiting scenario, we need to force finish() in case of DSDA when
+ // we get an incoming call on one sub and there is a live call in other sub and screen
+ // is off.
+ boolean anyOtherSubActive = (incomingCall != null &&
+ mCallList.isAnyOtherSubActive(mCallList.getActiveSubId()));
+ Log.d(this, "Start UI " + " anyOtherSubActive:" + anyOtherSubActive);
+ if (isCallWaiting || anyOtherSubActive) {
if (mProximitySensor.isScreenReallyOff() && isActivityStarted()) {
Log.i(this, "Restarting InCallActivity to turn screen on for call waiting");
mInCallActivity.finish();
@@ -1522,6 +1680,9 @@
}
mProximitySensor = null;
+ mWakeLock = null;
+ mPowerManager = null;
+
mAudioModeProvider = null;
if (mStatusBarNotifier != null) {
@@ -1532,6 +1693,8 @@
}
mStatusBarNotifier = null;
+ InCallCsRedialHandler.getInstance().tearDown();
+
if (mCallList != null) {
mCallList.removeListener(this);
}
@@ -1607,6 +1770,56 @@
return intent;
}
+ public void sendAddParticipantIntent() {
+ Intent intent = new Intent(Intent.ACTION_DIAL);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // when we request the dialer come up, we also want to inform
+ // it that we're going through the "add participant" option from the
+ // InCallScreen.
+ intent.putExtra(TelecomAdapter.ADD_CALL_MODE_KEY, true);
+ intent.putExtra(TelecomAdapter.ADD_PARTICIPANT_KEY, true);
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ // This is rather rare but possible.
+ // Note: this method is used even when the phone is encrypted. At
+ // that moment
+ // the system may not find any Activity which can accept this Intent
+ Log.e(this, "Activity for adding calls isn't found.");
+ }
+ }
+
+ public void sendAddMultiParticipantsIntent() {
+ Intent intent = new Intent("android.intent.action.ADDPARTICIPANT");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("add_participant", true);
+
+ Call call = mCallList.getActiveOrBackgroundCall();
+ List<String> childCallIdList = call.getChildCallIds();
+ if (childCallIdList != null) {
+ StringBuffer sb = new StringBuffer();
+ for (int k=0; k<childCallIdList.size(); k++) {
+ String tmp = childCallIdList.get(k);
+ String number = CallList.getInstance()
+ .getCallById(tmp).getNumber();
+ if (number.contains(";")) {
+ String[] temp = number.split(";");
+ number = temp[0];
+ }
+ sb.append(number).append(";");
+ }
+ Log.d(this, "sendAddMultiParticipantsIntent, numbers " + sb.toString());
+ intent.putExtra("current_participant_list", sb.toString());
+ } else {
+ Log.e(this, "sendAddMultiParticipantsIntent, childCallIdList null.");
+ }
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(this, "Activity for adding calls isn't found.");
+ }
+ }
+
/**
* Retrieves the current in-call camera manager instance, creating if necessary.
*
@@ -1650,39 +1863,46 @@
* Configures the in-call UI activity so it can change orientations or not. Enables the
* orientation event listener if allowOrientationChange is true, disables it if false.
*
- * @param allowOrientationChange {@code True} if the in-call UI can change between portrait
- * and landscape. {@Code False} if the in-call UI should be locked in portrait.
+ * @param orientation {@link ActivityInfo#screenOrientation} Actual orientation value to set
+ * @return returns whether the new orientation mode was set successfully or not.
*/
- public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
+ public boolean setInCallAllowsOrientationChange(int orientation) {
if (mInCallActivity == null) {
Log.e(this, "InCallActivity is null. Can't set requested orientation.");
- return;
+ return false;
}
- if (!allowOrientationChange) {
- mInCallActivity.setRequestedOrientation(
- InCallOrientationEventListener.NO_SENSOR_SCREEN_ORIENTATION);
- } else {
- // Using SCREEN_ORIENTATION_FULL_SENSOR allows for reverse-portrait orientation, where
- // SCREEN_ORIENTATION_SENSOR does not.
- mInCallActivity.setRequestedOrientation(
- InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
- }
- mInCallActivity.enableInCallOrientationEventListener(allowOrientationChange);
+ mInCallActivity.setRequestedOrientation(orientation);
+ mInCallActivity.enableInCallOrientationEventListener(
+ orientation == InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
+ return true;
}
- public void enableScreenTimeout(boolean enable) {
- Log.v(this, "enableScreenTimeout: value=" + enable);
- if (mInCallActivity == null) {
- Log.e(this, "enableScreenTimeout: InCallActivity is null.");
- return;
- }
+ /* returns TRUE if screen is turned ON else false */
+ private boolean isScreenInteractive() {
+ return mPowerManager.isInteractive();
+ }
- final Window window = mInCallActivity.getWindow();
- if (enable) {
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- } else {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ private void wakeUpScreen() {
+ if (!isScreenInteractive()) {
+ acquireWakeLock();
+ releaseWakeLock();
+ }
+ }
+
+ private void acquireWakeLock() {
+ Log.v(this, "acquireWakeLock");
+
+ if (mWakeLock != null) {
+ mWakeLock.acquire();
+ }
+ }
+
+ private void releaseWakeLock() {
+ Log.v(this, "releaseWakeLock");
+
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
}
}
@@ -1907,6 +2127,8 @@
public interface InCallEventListener {
public void onFullscreenModeChanged(boolean isFullscreenMode);
public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height);
+ public void updatePrimaryCallState();
+ public void onIncomingVideoAvailabilityChanged(boolean isAvailable);
}
public interface InCallUiListener {
diff --git a/InCallUI/src/com/android/incallui/InCallServiceImpl.java b/InCallUI/src/com/android/incallui/InCallServiceImpl.java
index 1414bc5..8f011e3 100644
--- a/InCallUI/src/com/android/incallui/InCallServiceImpl.java
+++ b/InCallUI/src/com/android/incallui/InCallServiceImpl.java
@@ -22,6 +22,7 @@
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.telecom.InCallService;
+import android.telephony.TelephonyManager;
/**
* Used to receive updates about calls from the Telecom component. This service is bound to
@@ -31,6 +32,8 @@
*/
public class InCallServiceImpl extends InCallService {
+ static int sPhoneCount = TelephonyManager.getDefault().getPhoneCount();
+
@Override
public void onCallAudioStateChanged(CallAudioState audioState) {
AudioModeProvider.getInstance().onAudioStateChanged(audioState.isMuted(),
diff --git a/InCallUI/src/com/android/incallui/InCallSessionModificationCauseListener.java b/InCallUI/src/com/android/incallui/InCallSessionModificationCauseListener.java
new file mode 100644
index 0000000..e6ad761
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallSessionModificationCauseListener.java
@@ -0,0 +1,37 @@
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+/**
+ * This interface will be implemented by classes that wish to listen to session modification cause
+ * updates.
+ */
+public interface InCallSessionModificationCauseListener {
+ public void onSessionModificationCauseChanged(Call call, int sessionModificationCause);
+}
diff --git a/InCallUI/src/com/android/incallui/InCallSubstateListener.java b/InCallUI/src/com/android/incallui/InCallSubstateListener.java
new file mode 100644
index 0000000..20449c4
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallSubstateListener.java
@@ -0,0 +1,36 @@
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+/**
+ * This interface will be implemented by classes that wish to listen to call substate changes.
+ */
+public interface InCallSubstateListener {
+ public void onCallSubstateChanged(final Call call, final int callSubstate);
+}
diff --git a/InCallUI/src/com/android/incallui/InCallUiStateNotifier.java b/InCallUI/src/com/android/incallui/InCallUiStateNotifier.java
new file mode 100644
index 0000000..299c13b
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallUiStateNotifier.java
@@ -0,0 +1,229 @@
+/* Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.List;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * This class listens to below events and notifes whether InCallUI is visible to the user or not.
+ * a. InCallActivity's lifecycle events (onStop/onStart)
+ * b. Display state change events (DISPLAY_ON/DISPLAY_OFF)
+ */
+public class InCallUiStateNotifier implements DisplayManager.DisplayListener {
+
+ private List<InCallUiStateNotifierListener> mInCallUiStateNotifierListeners =
+ new CopyOnWriteArrayList<>();
+ private static InCallUiStateNotifier sInCallUiStateNotifier;
+ private DisplayManager mDisplayManager;
+ private Context mContext;
+
+ /**
+ * Tracks whether the application is in the background. {@code True} if the application is in
+ * the background, {@code false} otherwise.
+ */
+ private boolean mIsInBackground;
+
+ /**
+ * Tracks whether display is ON/OFF. {@code True} if display is ON, {@code false} otherwise.
+ */
+ private boolean mIsDisplayOn;
+
+ /**
+ * Handles set up of the {@class InCallUiStateNotifier}. Instantiates the context needed by
+ * the class and adds a listener to listen to display state changes.
+ */
+ public void setUp(Context context) {
+ mContext = context;
+ mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(this, null);
+ mIsDisplayOn = isDisplayOn(
+ mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState());
+ Log.d(this, "setUp mIsDisplayOn: " + mIsDisplayOn);
+ }
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private InCallUiStateNotifier() {
+ }
+
+ /**
+ * This method returns a singleton instance of {@class InCallUiStateNotifier}
+ */
+ public static synchronized InCallUiStateNotifier getInstance() {
+ if (sInCallUiStateNotifier == null) {
+ sInCallUiStateNotifier = new InCallUiStateNotifier();
+ }
+ return sInCallUiStateNotifier;
+ }
+
+ /**
+ * Adds a new {@link InCallUiStateNotifierListener}.
+ *
+ * @param listener The listener.
+ */
+ public void addListener(InCallUiStateNotifierListener listener) {
+ Preconditions.checkNotNull(listener);
+ mInCallUiStateNotifierListeners.add(listener);
+ }
+
+ /**
+ * Remove a {@link InCallUiStateNotifierListener}.
+ *
+ * @param listener The listener.
+ */
+ public void removeListener(InCallUiStateNotifierListener listener) {
+ if (listener != null) {
+ mInCallUiStateNotifierListeners.remove(listener);
+ } else {
+ Log.e(this, "Can't remove null listener");
+ }
+ }
+
+ /**
+ * Notfies when visibility of InCallUI is changed. For eg.
+ * when UE moves in/out of the foreground, display either turns ON/OFF
+ * @param showing true if InCallUI is visible, false otherwise.
+ */
+ private void notifyOnUiShowing(boolean showing) {
+ Preconditions.checkNotNull(mInCallUiStateNotifierListeners);
+ for (InCallUiStateNotifierListener listener : mInCallUiStateNotifierListeners) {
+ listener.onUiShowing(showing);
+ }
+ }
+
+ /**
+ * Handles tear down of the {@class InCallUiStateNotifier}. Sets the context to null and
+ * unregisters it's display listener.
+ */
+ public void tearDown() {
+ mDisplayManager.unregisterDisplayListener(this);
+ mDisplayManager = null;
+ mContext = null;
+ mInCallUiStateNotifierListeners.clear();
+ }
+
+ /**
+ * checks to see whether InCallUI experience is visible to the user or not.
+ * returns true if InCallUI experience is visible to the user else false.
+ */
+ private boolean isUiShowing() {
+ /* Not in background and display is ON does mean that InCallUI is visible/showing.
+ Return true in such cases else false */
+ return !mIsInBackground && mIsDisplayOn;
+ }
+
+ /**
+ * Checks whether the display is ON.
+ *
+ * @param displayState The display's current state.
+ */
+ public static boolean isDisplayOn(int displayState) {
+ return displayState == Display.STATE_ON ||
+ displayState == Display.STATE_DOZE ||
+ displayState == Display.STATE_DOZE_SUSPEND;
+ }
+
+ /**
+ * Called when UE goes in/out of the foreground.
+ * @param showing true if UE is in the foreground, false otherwise.
+ */
+ public void onUiShowing(boolean showing) {
+
+ //Check UI's old state before updating corresponding state variable(s)
+ final boolean wasShowing = isUiShowing();
+
+ mIsInBackground = !showing;
+
+ //Check UI's new state after updating corresponding state variable(s)
+ final boolean isShowing = isUiShowing();
+
+ Log.d(this, "onUiShowing wasShowing: " + wasShowing + " isShowing: " + isShowing);
+ //notify if there is a change in UI state
+ if (wasShowing != isShowing) {
+ notifyOnUiShowing(showing);
+ }
+ }
+
+ /**
+ * This method overrides onDisplayRemoved method of {@interface DisplayManager.DisplayListener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ /**
+ * This method overrides onDisplayAdded method of {@interface DisplayManager.DisplayListener}
+ * Added for completeness. No implementation yet.
+ */
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ /**
+ * This method overrides onDisplayAdded method of {@interface DisplayManager.DisplayListener}
+ * The method gets invoked whenever the properties of a logical display have changed.
+ */
+ @Override
+ public void onDisplayChanged(int displayId) {
+ /* Ignore display changed indications if they are received for displays
+ * other than default display
+ */
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ Log.w(this, "onDisplayChanged Ignoring...");
+ return;
+ }
+
+ final int displayState = mDisplayManager.getDisplay(displayId).getState();
+ Log.d(this, "onDisplayChanged displayState: " + displayState +
+ " displayId: " + displayId);
+
+ //Check UI's old state before updating corresponding state variable(s)
+ final boolean wasShowing = isUiShowing();
+
+ mIsDisplayOn = isDisplayOn(displayState);
+
+ //Check UI's new state after updating corresponding state variable(s)
+ final boolean isShowing = isUiShowing();
+
+ Log.d(this, "onDisplayChanged wasShowing: " + wasShowing + " isShowing: " + isShowing);
+ //notify if there is a change in UI state
+ if (wasShowing != isShowing) {
+ notifyOnUiShowing(mIsDisplayOn);
+ }
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/InCallUiStateNotifierListener.java b/InCallUI/src/com/android/incallui/InCallUiStateNotifierListener.java
new file mode 100644
index 0000000..772f2da
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallUiStateNotifierListener.java
@@ -0,0 +1,38 @@
+/* Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+/**
+ * Listener interface for any class that wants to be notified when
+ * visibilitiy of InCallUI is changed. For eg. when UE moves in/out of the foreground,
+ * display either turns ON/OFF
+ */
+public interface InCallUiStateNotifierListener {
+ public void onUiShowing(boolean showing);
+}
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java b/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java
index 99e6d51..e62e4d9 100644
--- a/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java
+++ b/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java
@@ -55,10 +55,11 @@
boolean wasVideoCall = VideoUtils.isVideoCall(previousVideoState);
boolean isVideoCall = VideoUtils.isVideoCall(newVideoState);
- // Check for upgrades to video.
- if (!wasVideoCall && isVideoCall && previousVideoState != newVideoState) {
+ if (wasVideoCall && !isVideoCall) {
+ Log.v(this, " onSessionModifyRequestReceived Call downgraded to " + newVideoState);
+ } else if (previousVideoState != newVideoState) {
InCallVideoCallCallbackNotifier.getInstance().upgradeToVideoRequest(mCall,
- newVideoState);
+ newVideoState);
}
}
@@ -91,6 +92,7 @@
Call.SessionModificationState.REQUEST_FAILED);
}
}
+ InCallVideoCallCallbackNotifier.getInstance().upgradeToVideoFail(status, mCall);
}
// Finally clear the outstanding request.
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallCallbackNotifier.java b/InCallUI/src/com/android/incallui/InCallVideoCallCallbackNotifier.java
index bb75292..1a24b16 100644
--- a/InCallUI/src/com/android/incallui/InCallVideoCallCallbackNotifier.java
+++ b/InCallUI/src/com/android/incallui/InCallVideoCallCallbackNotifier.java
@@ -46,6 +46,14 @@
private final Set<SurfaceChangeListener> mSurfaceChangeListeners = Collections.newSetFromMap(
new ConcurrentHashMap<SurfaceChangeListener, Boolean>(8, 0.9f, 1));
+ /* Invalid call session event */
+ public static final int CALL_SESSION_INVALID_EVENT = -1;
+
+ /** Cache the call session event for cases where the call is in background and listeners
+ * are unregistered.
+ */
+ private int mCallSessionEvent = CALL_SESSION_INVALID_EVENT;
+
/**
* Static singleton accessor method.
*/
@@ -91,6 +99,22 @@
}
/**
+ * Adds a new {@link VideoEventListener} and notifies the entity that registered
+ * if flag notify is true.
+ *
+ * @param listener The listener.
+ * @param notify true or false
+ */
+ public void addVideoEventListener(VideoEventListener listener, boolean notify) {
+ addVideoEventListener(listener);
+
+ // Notify registered listeners of cached call session event if it's a valid value
+ if (notify && mCallSessionEvent != CALL_SESSION_INVALID_EVENT) {
+ callSessionEvent(mCallSessionEvent);
+ }
+ }
+
+ /**
* Remove a {@link VideoEventListener}.
*
* @param listener The listener.
@@ -135,13 +159,25 @@
}
/**
+ * Inform listeners of an unsuccessful response to a video request for a call.
+ *
+ * @param call The call.
+ */
+ public void upgradeToVideoFail(int status, Call call) {
+ for (SessionModificationListener listener : mSessionModificationListeners) {
+ listener.onUpgradeToVideoFail(status, call);
+ }
+ }
+
+ /**
* Inform listeners of a call session event.
*
* @param event The call session event.
*/
public void callSessionEvent(int event) {
+ mCallSessionEvent = event;
for (VideoEventListener listener : mVideoEventListeners) {
- listener.onCallSessionEvent(event);
+ listener.onCallSessionEvent(mCallSessionEvent);
}
}
@@ -217,6 +253,15 @@
* @param videoState The requested video state.
*/
public void onUpgradeToVideoRequest(Call call, int videoState);
+
+ /**
+ * Called when a request to a peer to upgrade an audio-only call to a video call is
+ * NOT successful. This can be if the peer chooses rejects the the video call, or if the
+ * peer does not support video calling, or if there is some error in sending the request.
+ *
+ * @param call The call the request was successful for.
+ */
+ public void onUpgradeToVideoFail(int status, Call call);
}
/**
diff --git a/InCallUI/src/com/android/incallui/InCallZoomController.java b/InCallUI/src/com/android/incallui/InCallZoomController.java
new file mode 100644
index 0000000..b44e99e
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallZoomController.java
@@ -0,0 +1,250 @@
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.content.Context;
+import android.view.View;
+import android.telecom.InCallService.VideoCall;
+import android.app.AlertDialog;
+import android.view.LayoutInflater;
+import android.view.Window;
+import android.view.WindowManager;
+import org.codeaurora.ims.QtiCallConstants;
+import android.hardware.Camera;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraManager;
+import java.lang.Integer;
+import java.util.Objects;
+
+import com.android.incallui.ZoomControl.OnZoomChangedListener;
+
+
+/**
+ * This class implements the zoom listener for zoom control and shows the dialog and zoom controls
+ * on the InCall screen and maintains state info about the camera zoom index.
+ */
+public class InCallZoomController implements InCallPresenter.IncomingCallListener {
+
+ private static InCallZoomController sInCallZoomController;
+
+ private AlertDialog mAlertDialog;
+
+ private InCallPresenter mInCallPresenter;
+
+ private Context mContext;
+
+ private String mCameraId;
+
+ CameraManager mCameraManager;
+
+ /**
+ * This class implements the zoom listener for zoom control
+ */
+ private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
+ private VideoCall mVideoCall;
+
+ public ZoomChangeListener(VideoCall videoCall) {
+ mVideoCall = videoCall;
+ }
+
+ @Override
+ public void onZoomValueChanged(int index) {
+ Log.v("this", "onZoomValueChanged: index = " + index);
+ mZoomIndex = index;
+ mVideoCall.setZoom(mZoomIndex);
+ }
+ }
+
+ /**
+ * Default zoom value for camera
+ */
+ private static final int DEFAULT_CAMERA_ZOOM_VALUE = 0;
+
+ /**
+ * Transparency value for alert dialog
+ */
+ private static final float DIALOG_ALPHA_INDEX = 0.6f;
+
+ /**
+ * Static variable for storing zoom index value to maintain state
+ */
+ private int mZoomIndex = DEFAULT_CAMERA_ZOOM_VALUE;
+
+ /**
+ * This method returns a singleton instance of {@class InCallZoomController}
+ */
+ public static synchronized InCallZoomController getInstance() {
+ if (sInCallZoomController == null) {
+ sInCallZoomController = new InCallZoomController();
+ }
+ return sInCallZoomController;
+ }
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private InCallZoomController() {
+ }
+
+ /**
+ * Set up function called to add listener for camera selection changes
+ */
+ public void setUp(Context context) {
+ mContext = context;
+ mInCallPresenter = InCallPresenter.getInstance();
+ mInCallPresenter.addIncomingCallListener(this);
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ }
+
+ /**
+ * Tear down function to reset all variables and remove camera selection listener
+ */
+ public void tearDown() {
+ mAlertDialog = null;
+ mContext = null;
+ mCameraId = null;
+ mZoomIndex = DEFAULT_CAMERA_ZOOM_VALUE;
+ mInCallPresenter.removeIncomingCallListener(this);
+ mInCallPresenter = null;
+ mCameraManager = null;
+ }
+
+ /**
+ * Sets the layout params for the alert dialog - transparency and clearing flag to dim
+ * background UI
+ */
+ private static void setLayoutParams(AlertDialog alertDialog) {
+ if (alertDialog == null) {
+ return;
+ }
+ final Window window = alertDialog.getWindow();
+ WindowManager.LayoutParams windowLayoutParams = window.getAttributes();
+ windowLayoutParams.alpha = DIALOG_ALPHA_INDEX;
+ window.setAttributes(windowLayoutParams);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ }
+
+ /**
+ * Called when preview surface is clicked on the InCallUI screen. Notification comes from
+ * {@class VideocallPresenter}. Create the alert dialog and the zoom control,
+ * set layout params attributes, set zoom params if zoom is supported and video call is valid
+ */
+ public void onPreviewSurfaceClicked(VideoCall videoCall) {
+ Log.d(this, "onPreviewSurfaceClicked: VideoCall - " + videoCall);
+
+ if(videoCall == null || !isCameraZoomSupported()) {
+ Log.e(this, "onPreviewSurfaceClicked: VideoCall is null or Zoom not supported ");
+ return;
+ }
+
+ try {
+ final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(
+ mInCallPresenter.getActivity(), AlertDialog.THEME_HOLO_DARK);
+ final View zoomControlView = mInCallPresenter.getActivity().getLayoutInflater().
+ inflate(R.layout.qti_video_call_zoom_control, null);
+ final ZoomControlBar zoomControl = (ZoomControlBar) zoomControlView.findViewById(
+ R.id.zoom_control);
+ dialogBuilder.setView(zoomControlView);
+ mAlertDialog = dialogBuilder.create();
+ mAlertDialog.setCanceledOnTouchOutside(true);
+ setLayoutParams(mAlertDialog);
+ zoomControl.setOnZoomChangeListener(new ZoomChangeListener(videoCall));
+ initZoomControl(zoomControl, mZoomIndex);
+ mAlertDialog.show();
+ } catch (Exception e) {
+ Log.e(this, "onPreviewSurfaceClicked: Exception " + e);
+ return;
+ }
+ }
+
+ private static void initZoomControl(ZoomControlBar zoomControl, int zoomIndex) {
+ zoomControl.setZoomMax(QtiCallConstants.CAMERA_MAX_ZOOM);
+ zoomControl.setZoomIndex(zoomIndex);
+ zoomControl.setEnabled(true);
+ }
+
+ /**
+ * Queries the camera characteristics to figure out if zoom is supported or not
+ */
+ private boolean isCameraZoomSupported() {
+ try {
+ final InCallCameraManager inCallCameraManager = mInCallPresenter.
+ getInCallCameraManager();
+ final float CAMERA_ZOOM_NOT_SUPPORTED = 1.0f;
+
+ CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(
+ inCallCameraManager.getActiveCameraId());
+ return (characteristics != null) && (characteristics.get(
+ CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)
+ > CAMERA_ZOOM_NOT_SUPPORTED);
+ } catch (Exception e) {
+ Log.e(this, "isCameraZoomSupported: Failed to retrieve Max Zoom, " + e);
+ return false;
+ }
+ }
+
+ /**
+ * Called from the {@class VideoCallPresenter} when camera is enabled or disabled
+ * Reset the zoom index and dismiss the alert if camera id changes
+ */
+ public void onCameraEnabled(String cameraId) {
+ Log.d(this, "onCameraEnabled: - cameraId -" + cameraId);
+ if (!Objects.equals(mCameraId, cameraId)) {
+ mCameraId = cameraId;
+ mZoomIndex = DEFAULT_CAMERA_ZOOM_VALUE;
+ dismissAlertDialog();
+ }
+ }
+
+ private void dismissAlertDialog() {
+ try {
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ mAlertDialog = null;
+ }
+ } catch (Exception e) {
+ // Since exceptions caused in zoom control dialog should not crash the phone process,
+ // we intentionally capture the exception and ignore.
+ Log.e(this, "dismissAlertDialog: Exception: " + e);
+ }
+ }
+
+ /**
+ * Called when there is a new incoming call.
+ * Dismiss the alert.
+ */
+ @Override
+ public void onIncomingCall(InCallPresenter.InCallState oldState,
+ InCallPresenter.InCallState newState, Call call) {
+ Log.v(this, "onIncomingCall - Call " + call + "oldState " + oldState + "newState " +
+ newState);
+ dismissAlertDialog();
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java b/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java
index 27f7115..94a852b 100644
--- a/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java
+++ b/InCallUI/src/com/android/incallui/NotificationBroadcastReceiver.java
@@ -45,6 +45,8 @@
"com.android.incallui.ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST";
public static final String ACTION_DECLINE_VIDEO_UPGRADE_REQUEST =
"com.android.incallui.ACTION_DECLINE_VIDEO_UPGRADE_REQUEST";
+ public static final String ADD_CALL_MODE_KEY = "add_call_mode";
+ public static final String ADD_PARTICIPANT_KEY = "add_participant";
public static final String ACTION_PULL_EXTERNAL_CALL =
"com.android.incallui.ACTION_PULL_EXTERNAL_CALL";
public static final String EXTRA_NOTIFICATION_ID =
@@ -57,8 +59,7 @@
// TODO: Commands of this nature should exist in the CallList.
if (action.equals(ACTION_ANSWER_VIDEO_INCOMING_CALL)) {
- InCallPresenter.getInstance().answerIncomingCall(
- context, VideoProfile.STATE_BIDIRECTIONAL);
+ InCallPresenter.getInstance().answerIncomingCall(context);
} else if (action.equals(ACTION_ANSWER_VOICE_INCOMING_CALL)) {
InCallPresenter.getInstance().answerIncomingCall(
context, VideoProfile.STATE_AUDIO_ONLY);
@@ -67,9 +68,7 @@
} else if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) {
InCallPresenter.getInstance().hangUpOngoingCall(context);
} else if (action.equals(ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST)) {
- //TODO: Change calltype after adding support for TX and RX
- InCallPresenter.getInstance().acceptUpgradeRequest(
- VideoProfile.STATE_BIDIRECTIONAL, context);
+ InCallPresenter.getInstance().acceptUpgradeRequest(context);
} else if (action.equals(ACTION_DECLINE_VIDEO_UPGRADE_REQUEST)) {
InCallPresenter.getInstance().declineUpgradeRequest(context);
} else if (action.equals(ACTION_PULL_EXTERNAL_CALL)) {
diff --git a/InCallUI/src/com/android/incallui/OrientationModeHandler.java b/InCallUI/src/com/android/incallui/OrientationModeHandler.java
new file mode 100644
index 0000000..edf05e3
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/OrientationModeHandler.java
@@ -0,0 +1,187 @@
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+import com.android.incallui.InCallPresenter.InCallUiListener;
+import org.codeaurora.ims.QtiCallConstants;
+
+/**
+ * This class listens to incoming events from the {@class InCallDetailsListener}.
+ * When call details change, this class is notified and we parse the extras from the details to
+ * figure out if orientation mode has changed and if changed, we call setRequestedOrientation
+ * on the activity to set the orientation mode for the device.
+ *
+ */
+public class OrientationModeHandler implements InCallDetailsListener, InCallUiListener {
+
+ private static OrientationModeHandler sOrientationModeHandler;
+
+ private PrimaryCallTracker mPrimaryCallTracker;
+
+ private int mOrientationMode = QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED;
+
+ /**
+ * Returns a singleton instance of {@class OrientationModeHandler}
+ */
+ public static synchronized OrientationModeHandler getInstance() {
+ if (sOrientationModeHandler == null) {
+ sOrientationModeHandler = new OrientationModeHandler();
+ }
+ return sOrientationModeHandler;
+ }
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private OrientationModeHandler() {
+ }
+
+ /**
+ * Handles set up of the {@class OrientationModeHandler}. Registers primary call tracker to
+ * listen to call state changes and registers this class to listen to call details changes.
+ */
+ public void setUp() {
+ mPrimaryCallTracker = new PrimaryCallTracker();
+ InCallPresenter.getInstance().addListener(mPrimaryCallTracker);
+ InCallPresenter.getInstance().addDetailsListener(this);
+ InCallPresenter.getInstance().addInCallUiListener(this);
+ }
+
+ /**
+ * Handles tear down of the {@class OrientationModeHandler}. Unregisters primary call tracker
+ * from listening to call state changes and unregisters this class from listening to call
+ * details changes.
+ */
+ public void tearDown() {
+ InCallPresenter.getInstance().removeListener(mPrimaryCallTracker);
+ InCallPresenter.getInstance().removeDetailsListener(this);
+ InCallPresenter.getInstance().removeInCallUiListener(this);
+ mOrientationMode = QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED;
+ mPrimaryCallTracker = null;
+ }
+
+ /**
+ * Overrides onDetailsChanged method of {@class InCallDetailsListener}. We are
+ * notified when call details change and extract the orientation mode from the
+ * extras, detect if the mode has changed and set the orientation mode for the device.
+ */
+ @Override
+ public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+ Log.d(this, "onDetailsChanged: - call: " + call + "details: " + details);
+ mayBeUpdateOrientationMode(call, details);
+ }
+
+ /**
+ * This API conveys if incall experience is showing or not.
+ *
+ * @param showing TRUE if incall experience is showing else FALSE
+ */
+ @Override
+ public void onUiShowing(boolean showing) {
+ Call call = mPrimaryCallTracker.getPrimaryCall();
+ Log.d(this, "onUiShowing showing: " + showing + " call = " + call);
+
+ if (!showing || call == null) {
+ return;
+ }
+
+ mayBeUpdateOrientationMode(call, call.getTelecomCall().getDetails());
+ }
+
+ private void mayBeUpdateOrientationMode(Call call, android.telecom.Call.Details details) {
+ final Bundle extras = (call != null && details != null) ? details.getExtras() : null;
+ final int orientationMode = (extras != null) ? extras.getInt(
+ QtiCallConstants.ORIENTATION_MODE_EXTRA_KEY,
+ QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED) :
+ QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED;
+
+ Log.d(this, "mayBeUpdateOrientationMode : orientationMode: " + orientationMode +
+ " mOrientationMode : " + mOrientationMode);
+ if (InCallPresenter.getInstance().getActivity() == null) {
+ Log.w(this, "mayBeUpdateOrientationMode : InCallActivity is null");
+ return;
+ }
+
+ if (orientationMode != mOrientationMode && orientationMode !=
+ QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED) {
+ mOrientationMode = orientationMode;
+ onOrientationModeChanged(call, mOrientationMode);
+ }
+ }
+
+ /**
+ * Handles any orientation mode changes in the call.
+ *
+ * @param call The call for which orientation mode changed.
+ * @param orientationMode The new orientation mode of the device
+ */
+ private void onOrientationModeChanged(Call call, int orientationMode) {
+ Log.d(this, "onOrientationModeChanged: Call : " + call + " orientation mode = " +
+ orientationMode);
+
+ if (!mPrimaryCallTracker.isPrimaryCall(call)) {
+ Log.e(this, "Can't set requested orientation on a non-primary call");
+ return;
+ }
+
+ InCallPresenter.getInstance().setInCallAllowsOrientationChange(
+ QtiCallUtils.toUiOrientationMode(orientationMode));
+ }
+
+ /**
+ * Returns the current orientation mode based on the receipt of DISPLAY_MODE_EVT from lower
+ * layers and whether the call is a video call or not. If we have a video call and we
+ * did receive a valid orientation mode, return the corresponding
+ * {@link ActivityInfo#ScreenOrientation} else return
+ * ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR. If we are in a voice call, return
+ * ActivityInfo.SCREEN_ORIENTATION_NOSENSOR.
+ *
+ * @param call The current call.
+ */
+ public int getOrientation(Call call) {
+ if (VideoUtils.isVideoCall(call)) {
+ return (mOrientationMode == QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED) ?
+ ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR :
+ QtiCallUtils.toUiOrientationMode(mOrientationMode);
+ } else {
+ return ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ }
+ }
+
+ /**
+ * Returns the current orientation mode.
+ * @see #getOrientation(Call)
+ */
+ public int getCurrentOrientation() {
+ return QtiCallUtils.toUiOrientationMode(mOrientationMode);
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/PictureModeHelper.java b/InCallUI/src/com/android/incallui/PictureModeHelper.java
new file mode 100644
index 0000000..d73ef3b
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/PictureModeHelper.java
@@ -0,0 +1,335 @@
+/**
+ * Copyright (c) 2016 The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.CheckBox;
+import android.widget.ListView;
+
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+import com.android.incallui.InCallPresenter.InCallStateListener;
+
+import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.List;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * This class displays the picture mode alert dialog and registers listener who wish to listen to
+ * user selection for the preview video and the incoming video.
+ */
+public class PictureModeHelper extends AlertDialog implements InCallDetailsListener,
+ InCallStateListener, CallList.Listener {
+
+ private AlertDialog mAlertDialog;
+
+ /**
+ * Indicates whether we should display camera preview video view
+ */
+ private static boolean mShowPreviewVideoView = true;
+
+ /**
+ * Indicates whether we should display incoming video view
+ */
+ private static boolean mShowIncomingVideoView = true;
+
+ public PictureModeHelper(Context context) {
+ super(context);
+ }
+
+ public void setUp(VideoCallPresenter videoCallPresenter) {
+ InCallPresenter.getInstance().addDetailsListener(this);
+ InCallPresenter.getInstance().addListener(this);
+ CallList.getInstance().addListener(this);
+ addListener(videoCallPresenter);
+ }
+
+ public void tearDown(VideoCallPresenter videoCallPresenter) {
+ InCallPresenter.getInstance().removeDetailsListener(this);
+ InCallPresenter.getInstance().removeListener(this);
+ CallList.getInstance().removeListener(this);
+ removeListener(videoCallPresenter);
+ mAlertDialog = null;
+ }
+
+ /**
+ * Displays the alert dialog
+ */
+ public void show() {
+ if (mAlertDialog != null) {
+ mAlertDialog.show();
+ }
+ }
+
+ /**
+ * Listener interface to implement if any class is interested in listening to preview
+ * video or incoming video selection changed
+ */
+ public interface Listener {
+ public void onPreviewVideoSelectionChanged();
+ public void onIncomingVideoSelectionChanged();
+ }
+
+ private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
+
+ /**
+ * This method adds a new Listener. Users interested in listening to preview video selection
+ * and incoming video selection changes must register to this class
+ * @param Listener listener - the listener to be registered
+ */
+ public void addListener(Listener listener) {
+ Preconditions.checkNotNull(listener);
+ mListeners.add(listener);
+ }
+
+ /**
+ * This method unregisters the listener listening to preview video selection and incoming
+ * video selection
+ * @param Listener listener - the listener to be un-registered
+ */
+ public void removeListener(Listener listener) {
+ Preconditions.checkNotNull(listener);
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Creates and displays the picture mode alert dialog to enable the user to switch
+ * between picture modes - Picture in picture, Incoming mode or Camera preview mode
+ * Once users makes their choice, they can save or cancel. Saving will apply the
+ * new picture mode to the video call by notifying video call presenter of the change.
+ * Cancel will dismiss the alert dialog without making any changes. Alert dialog is
+ * cancelable so pressing home/back key will dismiss the dialog.
+ * @param Context context - The application context.
+ */
+ public void create(Context context) {
+ final ArrayList<CharSequence> items = new ArrayList<CharSequence>();
+ final Resources res = context.getResources();
+
+ final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity();
+ if (inCallActivity == null) {
+ return;
+ }
+
+ final View checkboxView = inCallActivity.getLayoutInflater().
+ inflate(R.layout.qti_video_call_picture_mode_menu, null);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.video_call_picture_mode_menu_title);
+ builder.setView(checkboxView);
+ builder.setCancelable(true);
+
+ CheckBox previewVideo = (CheckBox) checkboxView.findViewById(R.id.preview_video);
+ CheckBox incomingVideo = (CheckBox) checkboxView.findViewById(R.id.incoming_video);
+
+ if (previewVideo == null || incomingVideo == null) {
+ return;
+ }
+
+ // Intialize the checkboxes with the proper checked values
+ previewVideo.setChecked(mShowPreviewVideoView);
+ incomingVideo.setChecked(mShowIncomingVideoView);
+
+ // Ensure that at least one of the check boxes is enabled. Disable the other checkbox
+ // if checkbox is un-checked and vice versa. Say for e.g: if preview video was unchecked,
+ // disble incoming video and make it unclickable
+ enable(previewVideo, mShowIncomingVideoView);
+ enable(incomingVideo, mShowPreviewVideoView);
+
+ previewVideo.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ enable(incomingVideo, ((CheckBox) view).isChecked());
+ }
+ });
+
+ incomingVideo.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ enable(previewVideo, ((CheckBox) view).isChecked());
+ }
+ });
+
+ builder.setPositiveButton(res.getText(R.string.video_call_picture_mode_save_option),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int item) {
+ mShowPreviewVideoView = previewVideo.isChecked();
+ mShowIncomingVideoView = incomingVideo.isChecked();
+ notifyOnSelectionChanged();
+ }
+ });
+
+ builder.setNegativeButton(res.getText(R.string.video_call_picture_mode_cancel_option),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int item) {
+ }
+ });
+
+ mAlertDialog = builder.create();
+ setDismissListener();
+ }
+
+ private void setDismissListener() {
+ mAlertDialog.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mAlertDialog = null;
+ }
+ });
+ }
+
+ /**
+ * This method enables or disables the checkbox passed in based on whether the flag enable
+ * is set to true or false. Also toggle the checkbox being clickable.
+ * @param CheckBox checkBox - the check Box to enable/disable
+ * @param boolean enable - Flag to enable/disable checkbox (true/false)
+ */
+ private void enable(CheckBox checkBox, boolean enable) {
+ checkBox.setEnabled(enable);
+ checkBox.setClickable(enable);
+ }
+
+ /**
+ * Determines if we can show the preview video view
+ */
+ public boolean canShowPreviewVideoView() {
+ return mShowPreviewVideoView;
+ }
+
+ /**
+ * Determines if we can show the incoming video view
+ */
+ public boolean canShowIncomingVideoView() {
+ return mShowIncomingVideoView;
+ }
+
+ /**
+ * Determines whether we are in Picture in Picture mode
+ */
+ public boolean isPipMode() {
+ return canShowPreviewVideoView() && canShowIncomingVideoView();
+ }
+
+ /**
+ * Notifies all registered classes of preview video or incoming video selection changed
+ */
+ public void notifyOnSelectionChanged() {
+ Preconditions.checkNotNull(mListeners);
+ for (Listener listener : mListeners) {
+ listener.onPreviewVideoSelectionChanged();
+ listener.onIncomingVideoSelectionChanged();
+ }
+ }
+
+ /**
+ * Listens to call details changed and dismisses the dialog if call has been downgraded to
+ * voice
+ * @param Call call - The call for which details changed
+ * @param android.telecom.Call.Details details - The changed details
+ */
+ @Override
+ public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+ if (call == null) {
+ return;
+ }
+ if (!VideoUtils.isVideoCall(call) && mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ }
+ }
+
+ /**
+ * Handles call state changes
+ *
+ * @param InCallPresenter.InCallState oldState - The old call state
+ * @param InCallPresenter.InCallState newState - The new call state
+ * @param CallList callList - The call list.
+ */
+ @Override
+ public void onStateChange(InCallPresenter.InCallState oldState,
+ InCallPresenter.InCallState newState, CallList callList) {
+ Log.d(this, "onStateChange oldState" + oldState + " newState=" + newState);
+
+ if (newState == InCallPresenter.InCallState.NO_CALLS) {
+ // Set both display preview video and incoming video to true as default display mode is
+ // to show picture in picture.
+ mShowPreviewVideoView = true;
+ mShowIncomingVideoView = true;
+ }
+ }
+
+ /**
+ * Overrides onIncomingCall method of {@interface CallList.Listener}
+ * @param Call call - The incoming call
+ */
+ @Override
+ public void onIncomingCall(Call call) {
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ }
+ }
+
+ /**
+ * Overrides onCallListChange method of {@interface CallList.Listener}
+ * Added for completeness
+ */
+ @Override
+ public void onCallListChange(CallList list) {
+ // no-op
+ }
+
+ /**
+ * Overrides onUpgradeToVideo method of {@interface CallList.Listener}
+ * @param Call call - The call to be upgraded
+ */
+ @Override
+ public void onUpgradeToVideo(Call call) {
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ }
+ }
+
+ /**
+ * Overrides onDisconnect method of {@interface CallList.Listener}
+ * @param Call call - The call to be disconnected
+ */
+ @Override
+ public void onDisconnect(Call call) {
+ // no-op
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/PrimaryCallTracker.java b/InCallUI/src/com/android/incallui/PrimaryCallTracker.java
new file mode 100644
index 0000000..e309ede
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/PrimaryCallTracker.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * 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
+ */
+
+package com.android.incallui;
+
+import com.android.incallui.InCallPresenter.InCallStateListener;
+import com.android.incallui.InCallPresenter.IncomingCallListener;
+import java.util.Objects;
+
+/**
+ * Listens to call state changes from {@class InCallStateListener} and keeps track of the current
+ * primary call.
+ */
+public class PrimaryCallTracker implements InCallStateListener, IncomingCallListener {
+
+ private Call mPrimaryCall;
+
+ public PrimaryCallTracker() {
+ }
+
+ @Override
+ public void onIncomingCall(InCallPresenter.InCallState oldState,
+ InCallPresenter.InCallState newState, Call call) {
+ // same logic should happen as with onStateChange()
+ onStateChange(oldState, InCallPresenter.InCallState.INCOMING, CallList.getInstance());
+ }
+
+ /**
+ * Handles state changes (including incoming calls)
+ *
+ * @param newState The in call state.
+ * @param callList The call list.
+ */
+ @Override
+ public void onStateChange(InCallPresenter.InCallState oldState,
+ InCallPresenter.InCallState newState, CallList callList) {
+ Log.d(this, "onStateChange: oldState" + oldState + " newState=" + newState +
+ "callList =" + callList);
+
+ // Determine the primary active call.
+ Call primaryCall = null;
+
+ if (newState == InCallPresenter.InCallState.INCOMING) {
+ primaryCall = callList.getIncomingCall();
+ } else if (newState == InCallPresenter.InCallState.OUTGOING) {
+ primaryCall = callList.getOutgoingCall();
+ } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
+ primaryCall = callList.getPendingOutgoingCall();
+ } else if (newState == InCallPresenter.InCallState.INCALL) {
+ primaryCall = callList.getActiveOrBackgroundCall();
+ }
+
+ if (!Objects.equals(mPrimaryCall, primaryCall)) {
+ mPrimaryCall = primaryCall;
+ }
+ }
+
+ /**
+ * Returns the current primary call.
+ */
+ public Call getPrimaryCall() {
+ return mPrimaryCall;
+ }
+
+ /**
+ * Checks if the current call passed in is a primary call. Returns true if it is, false
+ * otherwise
+ *
+ * @param Call The call to be compared with the primary call.
+ */
+ public boolean isPrimaryCall(final Call call) {
+ return Objects.equals(mPrimaryCall, call);
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/ProximitySensor.java b/InCallUI/src/com/android/incallui/ProximitySensor.java
index 3c9fd93..fbd24e5 100644
--- a/InCallUI/src/com/android/incallui/ProximitySensor.java
+++ b/InCallUI/src/com/android/incallui/ProximitySensor.java
@@ -25,6 +25,7 @@
import android.os.PowerManager;
import android.telecom.CallAudioState;
import android.view.Display;
+import android.provider.Settings;
import com.android.incallui.AudioModeProvider.AudioModeListener;
import com.android.incallui.InCallPresenter.InCallState;
@@ -42,6 +43,7 @@
public class ProximitySensor implements AccelerometerListener.OrientationListener,
InCallStateListener, AudioModeListener {
private static final String TAG = ProximitySensor.class.getSimpleName();
+ private static final String PROXIMITY_SENSOR = "proximity_sensor";
private final PowerManager mPowerManager;
private final PowerManager.WakeLock mProximityWakeLock;
@@ -52,6 +54,7 @@
private boolean mUiShowing = false;
private boolean mIsPhoneOffhook = false;
private boolean mDialpadVisible;
+ private Context mContext;
// True if the keyboard is currently *not* hidden
// Gets updated whenever there is a Configuration change
@@ -59,6 +62,7 @@
public ProximitySensor(Context context, AudioModeProvider audioModeProvider,
AccelerometerListener accelerometerListener) {
+ mContext = context;
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
mProximityWakeLock = mPowerManager.newWakeLock(
@@ -232,6 +236,8 @@
|| CallAudioState.ROUTE_SPEAKER == audioMode
|| CallAudioState.ROUTE_BLUETOOTH == audioMode
|| mIsHardKeyboardOpen);
+ screenOnImmediately |= Settings.System.getInt(mContext.getContentResolver(),
+ PROXIMITY_SENSOR, 1) == 0;
// We do not keep the screen off when the user is outside in-call screen and we are
// horizontal, but we do not force it on when we become horizontal until the
diff --git a/InCallUI/src/com/android/incallui/QtiCallUtils.java b/InCallUI/src/com/android/incallui/QtiCallUtils.java
new file mode 100644
index 0000000..6a4383a
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/QtiCallUtils.java
@@ -0,0 +1,545 @@
+/**
+ * Copyright (c) 2015, 2016 The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telecom.VideoProfile;
+import android.telecom.Connection.VideoProvider;
+import android.widget.Toast;
+import android.content.Context;
+import android.content.res.Resources;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.content.pm.ActivityInfo;
+import android.telecom.InCallService.VideoCall;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import java.lang.reflect.*;
+import java.util.ArrayList;
+
+import org.codeaurora.internal.IExtTelephony;
+import org.codeaurora.ims.QtiCallConstants;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
+/**
+ * This class contains Qti specific utiltity functions.
+ */
+public class QtiCallUtils {
+
+ private static String LOG_TAG = "QtiCallUtils";
+
+ /**
+ * Private constructor for QtiCallUtils as we don't want to instantiate this class
+ */
+ private QtiCallUtils() {
+ }
+
+ /**
+ * This utility method checks to see if bits in the mask are enabled in the value
+ * Returns true if all bits in {@code mask} are set in {@code value}
+ */
+ public static boolean isEnabled(final int mask, final int value) {
+ return (mask & value) == mask;
+ }
+
+ /**
+ * This utility method checks to see if none of the bits in the mask are enabled in the value
+ * Returns true if none of the bits in {@code mask} are set in {@code value}
+ */
+ public static boolean isNotEnabled(final int mask, final int value) {
+ return (mask & value) == 0;
+ }
+
+ /**
+ * Method to get the video quality display string resource id given the video quality
+ */
+ public static int getVideoQualityResourceId(int videoQuality) {
+ switch (videoQuality) {
+ case VideoProfile.QUALITY_HIGH:
+ return R.string.video_quality_high;
+ case VideoProfile.QUALITY_MEDIUM:
+ return R.string.video_quality_medium;
+ case VideoProfile.QUALITY_LOW:
+ return R.string.video_quality_low;
+ default:
+ return R.string.video_quality_unknown;
+ }
+ }
+
+ /**
+ * Returns the call session resource id given the call session event
+ */
+ public static int getCallSessionResourceId(int event) {
+ switch (event) {
+ case VideoProvider.SESSION_EVENT_RX_PAUSE:
+ return R.string.player_stopped;
+ case VideoProvider.SESSION_EVENT_RX_RESUME:
+ return R.string.player_started;
+ case VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
+ return R.string.camera_not_ready;
+ case VideoProvider.SESSION_EVENT_CAMERA_READY:
+ return R.string.camera_ready;
+ default:
+ return R.string.unknown_call_session_event;
+ }
+ }
+
+ /**
+ * Displays the message as a Toast on the UI
+ */
+ public static void displayToast(Context context, String msg) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+ }
+
+ /**
+ * Displays the string corresponding to the resourceId as a Toast on the UI
+ */
+ public static void displayToast(Context context, int resourceId) {
+ displayToast(context, context.getResources().getString(resourceId));
+ }
+
+ /**
+ * The function is called when Modify Call button gets pressed. The function creates and
+ * displays modify call options.
+ */
+ public static void displayModifyCallOptions(final Call call, final Context context) {
+ if (call == null) {
+ Log.d(LOG_TAG, "Can't display modify call options. Call is null");
+ return;
+ }
+
+ if (isTtyEnabled(context)) {
+ Log.w(LOG_TAG, "Call session modification is allowed only when TTY is off.");
+ displayToast(context, R.string.video_call_not_allowed_if_tty_enabled);
+ return;
+ }
+
+ final ArrayList<CharSequence> items = new ArrayList<CharSequence>();
+ final ArrayList<Integer> itemToCallType = new ArrayList<Integer>();
+ final Resources res = context.getResources();
+
+ // Prepare the string array and mapping.
+ if (hasVoiceCapabilities(call)) {
+ items.add(res.getText(R.string.modify_call_option_voice));
+ itemToCallType.add(VideoProfile.STATE_AUDIO_ONLY);
+ }
+
+ if (hasReceiveVideoCapabilities(call)) {
+ items.add(res.getText(R.string.modify_call_option_vt_rx));
+ itemToCallType.add(VideoProfile.STATE_RX_ENABLED);
+ }
+
+ if (hasTransmitVideoCapabilities(call)) {
+ items.add(res.getText(R.string.modify_call_option_vt_tx));
+ itemToCallType.add(VideoProfile.STATE_TX_ENABLED);
+ }
+
+ if (hasReceiveVideoCapabilities(call) && hasTransmitVideoCapabilities(call)) {
+ items.add(res.getText(R.string.modify_call_option_vt));
+ itemToCallType.add(VideoProfile.STATE_BIDIRECTIONAL);
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.modify_call_option_title);
+ final AlertDialog alert;
+
+ DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int item) {
+ Toast.makeText(context, items.get(item), Toast.LENGTH_SHORT).show();
+ final int selCallType = itemToCallType.get(item);
+ Log.v(this, "Videocall: ModifyCall: upgrade/downgrade to "
+ + callTypeToString(selCallType));
+ VideoProfile videoProfile = new VideoProfile(selCallType);
+ changeToVideoClicked(call, videoProfile, context);
+ dialog.dismiss();
+ }
+ };
+ final int currUnpausedVideoState = VideoUtils.getUnPausedVideoState(call.getVideoState());
+ final int index = itemToCallType.indexOf(currUnpausedVideoState);
+ builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), index, listener);
+ alert = builder.create();
+ alert.show();
+ }
+
+ public static void changeToVideoCall(Call call, VideoProfile videoProfile, Context context) {
+ changeToVideoClicked(call, videoProfile, context);
+ }
+ /**
+ * Converts the call type to string
+ */
+ public static String callTypeToString(int callType) {
+ switch (callType) {
+ case VideoProfile.STATE_BIDIRECTIONAL:
+ return "VT";
+ case VideoProfile.STATE_TX_ENABLED:
+ return "VT_TX";
+ case VideoProfile.STATE_RX_ENABLED:
+ return "VT_RX";
+ }
+ return "";
+ }
+
+ /**
+ * Sends a session modify request to the telephony framework
+ */
+ private static void changeToVideoClicked(Call call, VideoProfile videoProfile) {
+ changeToVideoClicked(call, videoProfile, null);
+ }
+
+ private static void changeToVideoClicked(Call call, VideoProfile videoProfile, Context ctx) {
+ VideoCall videoCall = call.getVideoCall();
+ if (videoCall == null) {
+ return;
+ }
+ videoCall.sendSessionModifyRequest(videoProfile);
+ if (shallShowPreviewWhileWaiting(ctx)) {
+ call.setRequestedVideoState(videoProfile.getVideoState());
+ }
+ call.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE);
+ InCallAudioManager.getInstance().onModifyCallClicked(call, videoProfile.getVideoState());
+ }
+
+ /**
+ * Checks the boolean flag in config file to figure out if we are going to use Qti extension or
+ * not
+ */
+ public static boolean useExt(Context context) {
+ if (context == null) {
+ Log.w(context, "Context is null...");
+ }
+ return context != null && QtiImsExtUtils.useExt(context);
+ }
+
+ /**
+ * Checks the boolean flag in config file to figure out if custom video ui is required or
+ * not
+ */
+ public static boolean useCustomVideoUi(Context context) {
+ if (context == null) {
+ Log.w(context, "Context is null...");
+ }
+ return context != null && QtiImsExtUtils.useCustomVideoUi(context);
+ }
+
+ /**
+ * Checks the boolean flag in config file to figure out if it support preview before the accept
+ * video call or not
+ */
+ public static boolean shallShowPreviewWhileWaiting(Context context) {
+ if (context == null) {
+ Log.w(context, "Context is null...");
+ return false;
+ }
+ return context.getResources().getBoolean(
+ R.bool.config_enable_modify_call_preview);
+ }
+
+ /**
+ * Returns user options for accepting an incoming video call based on Qti extension flag
+ */
+ public static int getIncomingCallAnswerOptions(Context context, int videoState,
+ boolean withSms) {
+ if (!useExt(context)) {
+ return withSms ? AnswerFragment.TARGET_SET_FOR_VIDEO_WITH_SMS :
+ AnswerFragment.TARGET_SET_FOR_VIDEO_WITHOUT_SMS;
+ } else if (VideoProfile.isBidirectional(videoState)) {
+ return withSms ? AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_WITH_SMS :
+ AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_WITHOUT_SMS;
+ } else if (VideoProfile.isTransmissionEnabled(videoState)) {
+ return withSms ?
+ AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_WITH_SMS :
+ AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_WITHOUT_SMS;
+ } else {
+ return withSms ?
+ AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_WITH_SMS :
+ AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_WITHOUT_SMS;
+ }
+ }
+
+ /**
+ * Returns the session modification user options based on session modify request video states
+ * (current video state and modify request video state)
+ */
+ public static int getSessionModificationOptions(Context context, int currentVideoState,
+ int modifyToVideoState) {
+ if (!useExt(context)) {
+ return AnswerFragment.TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST;
+ }
+
+ if (showVideoUpgradeOptions(currentVideoState, modifyToVideoState)) {
+ return AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_ACCEPT_REJECT_REQUEST;
+ } else if (isEnabled(VideoProfile.STATE_BIDIRECTIONAL, modifyToVideoState)) {
+ return AnswerFragment.TARGET_SET_FOR_QTI_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST;
+ } else if (isEnabled(VideoProfile.STATE_TX_ENABLED, modifyToVideoState)) {
+ return AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST;
+ } else if (isEnabled(VideoProfile.STATE_RX_ENABLED, modifyToVideoState)) {
+ return AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST;
+ }
+ return AnswerFragment.TARGET_SET_FOR_QTI_VIDEO_ACCEPT_REJECT_REQUEST;
+ }
+
+ /**
+ * Returns true if we are upgrading from Voice to Bidirectional video, false otherwise
+ */
+ private static boolean showVideoUpgradeOptions(int currentVideoState, int modifyToVideoState) {
+ return currentVideoState == VideoProfile.STATE_AUDIO_ONLY &&
+ isEnabled(VideoProfile.STATE_BIDIRECTIONAL, modifyToVideoState);
+ }
+
+ /**
+ * Returns IExtTelephony handle
+ */
+ public static IExtTelephony getIExtTelephony() {
+ IExtTelephony mExtTelephony = null;
+ try {
+ Class c = Class.forName("android.os.ServiceManager");
+ Method m = c.getMethod("getService",new Class[]{String.class});
+
+ mExtTelephony =
+ IExtTelephony.Stub.asInterface((IBinder)m.invoke(null, "extphone"));
+ } catch (ClassNotFoundException e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ } catch (IllegalAccessException e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ } catch (InvocationTargetException e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ } catch (SecurityException e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ } catch (NoSuchMethodException e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ }
+ return mExtTelephony;
+ }
+
+ /**
+ * returns true if it is emrgency number else false
+ */
+ public static boolean isEmergencyNumber(String number) {
+ boolean isEmergencyNumber = false;
+
+ try {
+ isEmergencyNumber = getIExtTelephony().isEmergencyNumber(number);
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Exception : " + ex);
+ } catch (NullPointerException ex) {
+ Log.e(LOG_TAG, "Exception : " + ex);
+ }
+ return isEmergencyNumber;
+ }
+
+ /**
+ * returns true if it is local emrgency number else false
+ */
+ public static boolean isLocalEmergencyNumber(String number) {
+ boolean isEmergencyNumber = false;
+
+ try {
+ isEmergencyNumber = getIExtTelephony().isLocalEmergencyNumber(number);
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Exception : " + ex);
+ } catch (NullPointerException ex) {
+ Log.e(LOG_TAG, "Exception : " + ex);
+ }
+ return isEmergencyNumber;
+ }
+
+ /**
+ * Returns true if TTY mode is enabled, false otherwise
+ */
+ private static boolean isTtyEnabled(final Context context) {
+ if (context == null) {
+ Log.w(context, "Context is null...");
+ return false;
+ }
+
+ final int TTY_MODE_OFF = 0;
+ final String PREFERRED_TTY_MODE = "preferred_tty_mode";
+ return (android.provider.Settings.Secure.getInt(context.getContentResolver(),
+ PREFERRED_TTY_MODE, TTY_MODE_OFF) != TTY_MODE_OFF);
+ }
+
+ static int getPhoneId(int subId) {
+ try {
+ Class c = Class.forName("android.telephony.SubscriptionManager");
+ Method m = c.getMethod("getPhoneId",new Class[]{int.class});
+ int phoneId = (Integer)m.invoke(null, subId);
+ if (phoneId >= InCallServiceImpl.sPhoneCount || phoneId < 0) {
+ phoneId = 0;
+ }
+ Log.d (LOG_TAG, "phoneid:" + phoneId);
+ return phoneId;
+ } catch (Exception e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ }
+ return 0;
+ }
+
+ static int getSubId(int phoneId) {
+ try {
+ Class c = Class.forName("android.telephony.SubscriptionManager");
+ Method m = c.getMethod("getSubId",new Class[]{int.class});
+ int subId[] = (int[])m.invoke(null, phoneId);
+ Log.d (LOG_TAG, "getSubId:" + subId[0]);
+ if (subId != null && subId.length > 0) {
+ return subId[0];
+ } else {
+ Log.e(LOG_TAG, "subId not valid: " + subId);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ static void switchToActiveSub(int subId) {
+ try {
+ IExtTelephony mExtTelephony = getIExtTelephony();
+ Log.d(LOG_TAG, "switchToActiveSub, mExtTelephony:" + mExtTelephony);
+ mExtTelephony.switchToActiveSub(subId);
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Exception : " + ex);
+ } catch (NullPointerException ex) {
+ Log.e(LOG_TAG, "Exception : " + ex);
+ }
+ }
+
+ static int getPhoneCount(Context context) {
+ TelephonyManager tm = null;
+ try {
+ Class c = Class.forName("android.telephony.TelephonyManager");
+ Method m = c.getMethod("from",new Class[]{Context.class});
+ tm = (TelephonyManager)m.invoke(null, context);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, " ex: " + e);
+ }
+ if (tm != null) {
+ return tm.getPhoneCount();
+ } else {
+ Log.e(LOG_TAG, "tm is null" );
+ return 1;
+ }
+ }
+
+ static Boolean dsdaEnabled = null;
+ static boolean isDsdaEnabled() {
+ try {
+ if (dsdaEnabled == null) {
+ IExtTelephony mExtTelephony = getIExtTelephony();
+ Log.d(LOG_TAG, "isDsdaEnabled, mExtTelephony:" + mExtTelephony);
+ dsdaEnabled = mExtTelephony.isDsdaEnabled();
+ return dsdaEnabled;
+ }
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Exception : " + ex);
+ } catch (NullPointerException ex) {
+ Log.e(LOG_TAG, "Exception : " + ex);
+ }
+ return (dsdaEnabled == null) ? false : dsdaEnabled;
+ }
+
+ public static void downgradeToVoiceCall(final Call call) {
+ final VideoProfile videoProfile = new VideoProfile(VideoProfile.STATE_AUDIO_ONLY);
+ changeToVideoClicked(call, videoProfile);
+ }
+
+ /**
+ * This method converts the QtiCallConstants' Orientation modes to the ActivityInfo
+ * screen orientation mode.
+ */
+ public static int toUiOrientationMode(int orientationMode) {
+ switch(orientationMode) {
+ case QtiCallConstants.ORIENTATION_MODE_LANDSCAPE:
+ return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ case QtiCallConstants.ORIENTATION_MODE_PORTRAIT:
+ return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ case QtiCallConstants.ORIENTATION_MODE_DYNAMIC:
+ return ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
+ default:
+ return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+ }
+
+ public static int toVideoIcon(int videoState) {
+ if (VideoProfile.isBidirectional(videoState)) {
+ return R.drawable.ic_videocam;
+ } else if (VideoProfile.isTransmissionEnabled(videoState)) {
+ return R.drawable.ic_tx_videocam;
+ } else {
+ return R.drawable.ic_rx_videocam;
+ }
+ }
+
+ /**
+ * Returns true if the CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO is set to false.
+ * Note that - CAPABILITY_SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL and
+ * CAPABILITY_SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE maps to
+ * CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO
+ */
+ public static boolean hasVoiceCapabilities(Call call) {
+ return call != null &&
+ !call.can(android.telecom.Call.Details.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO);
+ }
+
+ /**
+ * Returns true if local has the VT Transmit and if remote capability has VT Receive set i.e.
+ * Local can transmit and remote can receive
+ */
+ public static boolean hasTransmitVideoCapabilities(Call call) {
+ return call != null &&
+ call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX)
+ && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX);
+ }
+
+ /**
+ * Returns true if local has the VT Receive and if remote capability has VT Transmit set i.e.
+ * Remote can transmit and local can receive
+ */
+ public static boolean hasReceiveVideoCapabilities(Call call) {
+ return call != null &&
+ call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX)
+ && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX);
+ }
+
+ /**
+ * Returns true if both voice and video capabilities (see above) are set
+ */
+ public static boolean hasVoiceOrVideoCapabilities(Call call) {
+ return hasVoiceCapabilities(call) || hasTransmitVideoCapabilities(call)
+ || hasReceiveVideoCapabilities(call);
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/SessionModificationCauseNotifier.java b/InCallUI/src/com/android/incallui/SessionModificationCauseNotifier.java
new file mode 100644
index 0000000..670b307
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/SessionModificationCauseNotifier.java
@@ -0,0 +1,161 @@
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.os.Bundle;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+import com.google.common.base.Preconditions;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.codeaurora.ims.QtiCallConstants;
+
+/**
+ * This class listens to incoming events from the {@class InCallDetailsListener}.
+ * When call details change, this class is notified and we parse the extras from the details to
+ * figure out if session modification cause has been sent when a call upgrades/downgrades and
+ * notify the {@class InCallMessageController} to display the indication on UI.
+ */
+public class SessionModificationCauseNotifier implements InCallDetailsListener, CallList.Listener {
+
+ private final List<InCallSessionModificationCauseListener> mSessionModificationCauseListeners
+ = new CopyOnWriteArrayList<>();
+
+ private static SessionModificationCauseNotifier sSessionModificationCauseNotifier;
+ private final HashMap<String, Integer> mSessionModificationCauseMap = new HashMap<>();
+
+ /**
+ * Returns a singleton instance of {@class SessionModificationCauseNotifier}
+ */
+ public static synchronized SessionModificationCauseNotifier getInstance() {
+ if (sSessionModificationCauseNotifier == null) {
+ sSessionModificationCauseNotifier = new SessionModificationCauseNotifier();
+ }
+ return sSessionModificationCauseNotifier;
+ }
+
+ /**
+ * Adds a new session modification cause listener. Users interested in this cause
+ * should add a listener of type {@class InCallSessionModificationCauseListener}
+ */
+ public void addListener(InCallSessionModificationCauseListener listener) {
+ Preconditions.checkNotNull(listener);
+ mSessionModificationCauseListeners.add(listener);
+ }
+
+ /**
+ * Removes an existing session modification cause listener. Users listening to any cause
+ * changes when not interested any more can de-register an existing listener of type
+ * {@class InCallSessionModificationCauseListener}
+ */
+ public void removeListener(InCallSessionModificationCauseListener listener) {
+ if (listener != null) {
+ mSessionModificationCauseListeners.remove(listener);
+ } else {
+ Log.e(this, "Can't remove null listener");
+ }
+ }
+
+ /**
+ * Private constructor. Must use getInstance() to get this singleton.
+ */
+ private SessionModificationCauseNotifier() {
+ }
+
+ private int getSessionModificationCause(Bundle callExtras) {
+ return callExtras.getInt(QtiCallConstants.SESSION_MODIFICATION_CAUSE_EXTRA_KEY,
+ QtiCallConstants.CAUSE_CODE_UNSPECIFIED);
+ }
+ /**
+ * Overrides onDetailsChanged method of {@class InCallDetailsListener}. We are
+ * notified when call details change and extract the session modification cause from the
+ * extras, detect if the cause has changed and notify all registered listeners.
+ */
+ @Override
+ public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+ Log.d(this, "onDetailsChanged: - call: " + call + "details: " + details);
+
+ if (call == null || details == null ||
+ !Call.State.isConnectingOrConnected(call.getState())) {
+ Log.d(this, "onDetailsChanged - Call/details is null/Call is not connected. Return");
+ return;
+ }
+
+ final Bundle callExtras = details.getExtras();
+
+ if (callExtras == null) {
+ return;
+ }
+
+ final String callId = call.getId();
+
+ final int oldSessionModificationCause = mSessionModificationCauseMap.containsKey(callId) ?
+ mSessionModificationCauseMap.get(callId) : QtiCallConstants.CAUSE_CODE_UNSPECIFIED;
+ final int newSessionModificationCause = getSessionModificationCause(callExtras);
+
+ if (oldSessionModificationCause == newSessionModificationCause) {
+ return;
+ }
+
+ mSessionModificationCauseMap.put(callId, newSessionModificationCause);
+ // Notify all listeners only when there is a valid value
+ if (newSessionModificationCause != QtiCallConstants.CAUSE_CODE_UNSPECIFIED) {
+ Preconditions.checkNotNull(mSessionModificationCauseListeners);
+ for (InCallSessionModificationCauseListener listener :
+ mSessionModificationCauseListeners) {
+ listener.onSessionModificationCauseChanged(call, newSessionModificationCause);
+ }
+ }
+ }
+
+ /**
+ * This method overrides onDisconnect method of {@interface CallList.Listener}
+ */
+ @Override
+ public void onDisconnect(final Call call) {
+ Log.d(this, "onDisconnect: call: " + call);
+ mSessionModificationCauseMap.remove(call.getId());
+ }
+
+ @Override
+ public void onUpgradeToVideo(Call call) {
+ //NO-OP
+ }
+
+ @Override
+ public void onIncomingCall(Call call) {
+ //NO-OP
+ }
+
+ @Override
+ public void onCallListChange(CallList callList) {
+ //NO-OP
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
index 0662cca..1efa3cc 100644
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
@@ -38,15 +38,21 @@
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.support.annotation.Nullable;
+import android.os.SystemClock;
import android.telecom.Call.Details;
import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.ContactsUtils.UserType;
+import com.android.contacts.common.GeoUtil;
import com.android.contacts.common.preference.ContactsPreferences;
import com.android.contacts.common.testing.NeededForTesting;
import com.android.contacts.common.util.BitmapUtil;
@@ -61,6 +67,7 @@
import com.android.incallui.ringtone.ToneGeneratorFactory;
import java.util.Objects;
+import org.codeaurora.ims.QtiCallConstants;
/**
* This class adds Notifications to the status bar for the in-call experience.
@@ -82,9 +89,11 @@
@Nullable private ContactsPreferences mContactsPreferences;
private final ContactInfoCache mContactInfoCache;
private final NotificationManager mNotificationManager;
+ private final TelephonyManager mTelephonyManager;
private final DialerRingtoneManager mDialerRingtoneManager;
private int mCurrentNotification = NOTIFICATION_NONE;
private int mCallState = Call.State.INVALID;
+ private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
private int mSavedIcon = 0;
private String mSavedContent = null;
private Bitmap mSavedLargeIcon;
@@ -92,12 +101,19 @@
private String mCallId = null;
private InCallState mInCallState;
private Uri mRingtone;
+ private static final String EXTRA_KEY_SHOW = "showCallStatusBar";
+ private static final String EXTRA_KEY_CALL_STATE = "callState";
+ private static final String EXTRA_KEY_CHRONOMETER_TIME = "baseChronometerMillis";
+ private static final String EXTRA_KEY_AUDIO_MODE = "audioMode";
+ private static final String EXTRA_KEY_MUTE = "mute";
public StatusBarNotifier(Context context, ContactInfoCache contactInfoCache) {
Preconditions.checkNotNull(context);
mContext = context;
mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
mContactInfoCache = contactInfoCache;
+ mTelephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mNotificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mDialerRingtoneManager = new DialerRingtoneManager(
@@ -116,6 +132,34 @@
updateNotification(newState, callList);
}
+ private int getVoWiFiQualityIcon(int voWifiCallQuality) {
+ switch (voWifiCallQuality) {
+ case QtiCallConstants.VOWIFI_QUALITY_EXCELLENT:
+ return R.drawable.vowifi_in_call_good;
+
+ case QtiCallConstants.VOWIFI_QUALITY_FAIR:
+ return R.drawable.vowifi_in_call_fair;
+
+ case QtiCallConstants.VOWIFI_QUALITY_POOR:
+ return R.drawable.vowifi_in_call_poor;
+ }
+ return QtiCallConstants.VOWIFI_QUALITY_NONE;
+ }
+
+ private String getVoWiFiQualityText(int voWifiCallQuality) {
+ switch (voWifiCallQuality) {
+ case QtiCallConstants.VOWIFI_QUALITY_EXCELLENT:
+ return mContext.getResources().getString(R.string.vowifi_call_quality_good);
+
+ case QtiCallConstants.VOWIFI_QUALITY_FAIR:
+ return mContext.getResources().getString(R.string.vowifi_call_quality_fair);
+
+ case QtiCallConstants.VOWIFI_QUALITY_POOR:
+ return mContext.getResources().getString(R.string.vowifi_call_quality_poor);
+ }
+ return null;
+ }
+
/**
* Updates the phone app's status bar notification *and* launches the
* incoming call UI in response to a new incoming call.
@@ -187,11 +231,48 @@
} else {
cancelNotification();
}
+ if (!state.isIncoming()) {
+ updateCallStatusBar(call);
+ }
+ }
+
+ public void updateCallStatusBar() {
+ CallList callList = InCallPresenter.getInstance().getCallList();
+ updateCallStatusBar(callList);
+ }
+
+ public void updateCallStatusBar(CallList callList) {
+ updateCallStatusBar(getCallToShow(callList));
+ }
+
+ private void updateCallStatusBar(Call call) {
+ Intent intent = new Intent(
+ "com.android.incallui.UPDATE_CALL_STATUS_BAR");
+ boolean isShowingInCallUi = InCallPresenter.getInstance().isShowingInCallUi();
+ boolean show = (call != null) && !isShowingInCallUi;
+ intent.putExtra(EXTRA_KEY_SHOW, show);
+ if (call != null) {
+ int state = call.getState();
+ intent.putExtra(EXTRA_KEY_CALL_STATE, state);
+ if (state == Call.State.ACTIVE) {
+ long chronometerTime = call.getConnectTimeMillis() - System.currentTimeMillis()
+ + SystemClock.elapsedRealtime();
+ intent.putExtra(EXTRA_KEY_CHRONOMETER_TIME, chronometerTime);
+ }
+ AudioModeProvider audioModeProvider = AudioModeProvider.getInstance();
+ intent.putExtra(EXTRA_KEY_AUDIO_MODE, audioModeProvider.getAudioMode());
+ intent.putExtra(EXTRA_KEY_MUTE, audioModeProvider.getMute());
+ }
+ mContext.sendBroadcast(intent);
}
private void showNotification(final Call call) {
- final boolean isIncoming = (call.getState() == Call.State.INCOMING ||
- call.getState() == Call.State.CALL_WAITING);
+ final boolean isGeocoderLocationNeeded = (call.getState() == Call.State.INCOMING ||
+ call.getState() == Call.State.CALL_WAITING ||
+ call.getState() == Call.State.DIALING ||
+ call.getState() == Call.State.CONNECTING ||
+ call.getState() == Call.State.SELECT_PHONE_ACCOUNT);
+ Log.d(this, "showNotification isGeocoderLocationNeeded = " + isGeocoderLocationNeeded);
if (!TextUtils.isEmpty(mCallId)) {
CallList.getInstance().removeCallUpdateListener(mCallId, this);
}
@@ -203,7 +284,7 @@
// This callback will always get called immediately and synchronously with whatever data
// it has available, and may make a subsequent call later (same thread) if it had to
// call into the contacts provider for more data.
- mContactInfoCache.findInfo(call, isIncoming, new ContactInfoCacheCallback() {
+ mContactInfoCache.findInfo(call, isGeocoderLocationNeeded, new ContactInfoCacheCallback() {
@Override
public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
Call call = CallList.getInstance().getCallById(callId);
@@ -241,24 +322,34 @@
final int callState = call.getState();
// Check if data has changed; if nothing is different, don't issue another notification.
- final int iconResId = getIconToDisplay(call);
+ final int iconResId;
Bitmap largeIcon = getLargeIconToDisplay(contactInfo, call);
- final String content =
+ String content =
getContentString(call, contactInfo.userType);
+ int wifiQualityValue = call.getWifiQuality();
+ if (wifiQualityValue != QtiCallConstants.VOWIFI_QUALITY_NONE) {
+ iconResId = getVoWiFiQualityIcon(wifiQualityValue);
+ content += " " + getVoWiFiQualityText(wifiQualityValue);
+ } else {
+ iconResId = getIconToDisplay(call);
+ }
final String contentTitle = getContentTitle(contactInfo, call);
final boolean isVideoUpgradeRequest = call.getSessionModificationState()
== Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
+ final Call pendingAccountSelectionCall = CallList.getInstance()
+ .getWaitingForAccountCall();
final int notificationType;
- if (callState == Call.State.INCOMING || callState == Call.State.CALL_WAITING
- || isVideoUpgradeRequest) {
+ if ((callState == Call.State.INCOMING || callState == Call.State.CALL_WAITING
+ || isVideoUpgradeRequest) && (!InCallPresenter.getInstance().isShowingInCallUi()
+ || pendingAccountSelectionCall != null)) {
notificationType = NOTIFICATION_INCOMING_CALL;
} else {
notificationType = NOTIFICATION_IN_CALL;
}
if (!checkForChangeAndSaveData(iconResId, content, largeIcon, contentTitle, callState,
- notificationType, contactInfo.contactRingtoneUri)) {
+ call.getVideoState(), notificationType, contactInfo.contactRingtoneUri)) {
return;
}
@@ -266,6 +357,16 @@
largeIcon = getRoundedIcon(largeIcon);
}
+ //set the content
+ boolean isMultiSimDevice = mTelephonyManager.isMultiSimEnabled();
+ if (isMultiSimDevice) {
+ SubscriptionInfo info =
+ SubscriptionManager.from(mContext).getActiveSubscriptionInfo(call.getSubId());
+ if (info != null) {
+ content += " (" + info.getDisplayName() + ")";
+ }
+ }
+
/*
* This builder is used for the notification shown when the device is locked and the user
* has set their notification settings to 'hide sensitive content'
@@ -291,7 +392,8 @@
// Set the intent as a full screen intent as well if a call is incoming
if (notificationType == NOTIFICATION_INCOMING_CALL
- && !InCallPresenter.getInstance().isShowingInCallUi()) {
+ && (!InCallPresenter.getInstance().isShowingInCallUi() ||
+ (pendingAccountSelectionCall != null))) {
configureFullScreenIntent(builder, inCallPendingIntent, call);
// Set the notification category for incoming calls
builder.setCategory(Notification.CATEGORY_CALL);
@@ -357,7 +459,7 @@
addDismissAction(builder);
if (call.isVideoCall(mContext)) {
addVoiceAction(builder);
- addVideoCallAction(builder);
+ addVideoCallAction(builder, call.getVideoState());
} else {
addAnswerAction(builder);
}
@@ -384,7 +486,7 @@
* we do not issue a new notification for the exact same data.
*/
private boolean checkForChangeAndSaveData(int icon, String content, Bitmap largeIcon,
- String contentTitle, int state, int notificationType, Uri ringtone) {
+ String contentTitle, int state, int videoState, int notificationType, Uri ringtone) {
// The two are different:
// if new title is not null, it should be different from saved version OR
@@ -395,8 +497,9 @@
// any change means we are definitely updating
boolean retval = (mSavedIcon != icon) || !Objects.equals(mSavedContent, content)
- || (mCallState != state) || (mSavedLargeIcon != largeIcon)
- || contentTitleChanged || !Objects.equals(mRingtone, ringtone);
+ || (mCallState != state) || (mVideoState != videoState)
+ || (mSavedLargeIcon != largeIcon) || contentTitleChanged
+ || !Objects.equals(mRingtone, ringtone);
// If we aren't showing a notification right now or the notification type is changing,
// definitely do an update.
@@ -410,6 +513,7 @@
mSavedIcon = icon;
mSavedContent = content;
mCallState = state;
+ mVideoState = videoState;
mSavedLargeIcon = largeIcon;
mSavedContentTitle = contentTitle;
mRingtone = ringtone;
@@ -426,17 +530,22 @@
*/
@NeededForTesting
String getContentTitle(ContactCacheEntry contactInfo, Call call) {
- if (call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)) {
+ if (call.isConferenceCall() || call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)) {
return mContext.getResources().getString(R.string.card_title_conf_call);
}
-
- String preferredName = ContactDisplayUtils.getPreferredDisplayName(contactInfo.namePrimary,
- contactInfo.nameAlternative, mContactsPreferences);
- if (TextUtils.isEmpty(preferredName)) {
- return TextUtils.isEmpty(contactInfo.number) ? null : BidiFormatter.getInstance()
- .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
+ if (TextUtils.isEmpty(contactInfo.namePrimary)) {
+ String contactNumberDisplayed = TextUtils.isEmpty(contactInfo.number) ?
+ "" : contactInfo.number.toString();
+ String location_info = GeoUtil.getGeocodedLocationFor(mContext, contactNumberDisplayed);
+ if (!TextUtils.isEmpty(location_info)){
+ contactNumberDisplayed = contactNumberDisplayed + " " + location_info;
+ }
+ return TextUtils.isEmpty(contactNumberDisplayed) ? null
+ : BidiFormatter.getInstance().unicodeWrap(
+ contactNumberDisplayed, TextDirectionHeuristics.LTR);
}
- return preferredName;
+
+ return contactInfo.namePrimary;
}
private void addPersonReference(Notification.Builder builder, ContactCacheEntry contactInfo,
@@ -490,13 +599,25 @@
// different calls. So if both lines are in use, display info
// from the foreground call. And if there's a ringing call,
// display that regardless of the state of the other calls.
+ int resId;
+ boolean supportsVoicePrivacy = call.hasProperty(Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY);
if (call.getState() == Call.State.ONHOLD) {
- return R.drawable.ic_phone_paused_white_24dp;
+ if (supportsVoicePrivacy) {
+ resId = R.drawable.stat_sys_vp_phone_call_on_hold;
+ } else {
+ resId = R.drawable.ic_phone_paused_white_24dp;
+ }
} else if (call.getSessionModificationState()
== Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- return R.drawable.ic_videocam;
+ resId = R.drawable.ic_videocam;
+ } else {
+ if (supportsVoicePrivacy) {
+ resId = R.drawable.stat_sys_vp_phone_call;
+ } else {
+ resId = R.drawable.ic_call_white_24dp;
+ }
}
- return R.drawable.ic_call_white_24dp;
+ return resId;
}
/**
@@ -522,7 +643,9 @@
}
if (isIncomingOrWaiting) {
- if (call.hasProperty(Details.PROPERTY_WIFI)) {
+ if (call.isIncomingConfCall()) {
+ resId = R.string.notification_incoming_conf_call;
+ } else if (call.hasProperty(Details.PROPERTY_WIFI)) {
resId = R.string.notification_incoming_call_wifi;
} else {
resId = R.string.notification_incoming_call;
@@ -609,12 +732,12 @@
hangupPendingIntent);
}
- private void addVideoCallAction(Notification.Builder builder) {
+ private void addVideoCallAction(Notification.Builder builder, int videoState) {
Log.i(this, "Will show \"video\" action in the incoming call Notification");
PendingIntent answerVideoPendingIntent = createNotificationPendingIntent(
mContext, ACTION_ANSWER_VIDEO_INCOMING_CALL);
- builder.addAction(R.drawable.ic_videocam,
+ builder.addAction(QtiCallUtils.toVideoIcon(videoState),
mContext.getText(R.string.notification_action_answer_video),
answerVideoPendingIntent);
}
@@ -684,12 +807,18 @@
// If a call is onhold during an incoming call, the call actually comes in as
// INCOMING. For that case *and* traditional call-waiting, we want to
// cancel the notification.
+
+ // For DSDA, we want to cancel the notification if we get an incoming call on
+ // one sub and there is a live call on another sub.
+ CallList callList = CallList.getInstance();
boolean isCallWaiting = (call.getState() == Call.State.CALL_WAITING ||
(call.getState() == Call.State.INCOMING &&
- CallList.getInstance().getBackgroundCall() != null));
+ (callList.getBackgroundCall() != null ||
+ callList.isAnyOtherSubActive(callList.getActiveSubId()))));
if (isCallWaiting) {
- Log.i(this, "updateInCallNotification: call-waiting! force relaunch...");
+ Log.i(this, "configureFullScreenIntent: call-waiting or dsda incoming call!"
+ + " force relaunch. Active sub:" + callList.getActiveSubId());
// Cancel the IN_CALL_NOTIFICATION immediately before
// (re)posting it; this seems to force the
// NotificationManager to launch the fullScreenIntent.
@@ -746,7 +875,7 @@
* @param sessionModificationState The new session modification state.
*/
@Override
- public void onSessionModificationStateChange(int sessionModificationState) {
+ public void onSessionModificationStateChange(Call call, int sessionModificationState) {
if (sessionModificationState == Call.SessionModificationState.NO_REQUEST) {
if (mCallId != null) {
CallList.getInstance().removeCallUpdateListener(mCallId, this);
diff --git a/InCallUI/src/com/android/incallui/TelecomAdapter.java b/InCallUI/src/com/android/incallui/TelecomAdapter.java
index f172270..4873f96 100644
--- a/InCallUI/src/com/android/incallui/TelecomAdapter.java
+++ b/InCallUI/src/com/android/incallui/TelecomAdapter.java
@@ -27,7 +27,8 @@
import java.util.List;
final class TelecomAdapter implements InCallServiceListener {
- private static final String ADD_CALL_MODE_KEY = "add_call_mode";
+ public static final String ADD_CALL_MODE_KEY = "add_call_mode";
+ public static final String ADD_PARTICIPANT_KEY = "add_participant";
private static TelecomAdapter sInstance;
private InCallService mInCallService;
diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java
index 6a46a42..3a708ab 100644
--- a/InCallUI/src/com/android/incallui/VideoCallFragment.java
+++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java
@@ -16,11 +16,13 @@
package com.android.incallui;
+import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.os.Bundle;
import android.view.Display;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
@@ -28,6 +30,8 @@
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -115,7 +119,7 @@
* changes.
*/
private static class VideoCallSurface implements TextureView.SurfaceTextureListener,
- View.OnClickListener, View.OnAttachStateChangeListener {
+ View.OnClickListener, View.OnAttachStateChangeListener, View.OnLongClickListener {
private int mSurfaceId;
private VideoCallPresenter mPresenter;
private TextureView mTextureView;
@@ -174,6 +178,7 @@
mTextureView = view;
mTextureView.setSurfaceTextureListener(this);
mTextureView.setOnClickListener(this);
+ mTextureView.setOnLongClickListener(this);
final boolean areSameSurfaces =
Objects.equal(mSavedSurfaceTexture, mTextureView.getSurfaceTexture());
@@ -412,6 +417,18 @@
}
/**
+ * Handles a user long pressing on the surface, which is the trigger to show the
+ * picture mode pop up alert dialog
+ *
+ * @param View The view receiving the long press.
+ */
+ @Override
+ public boolean onLongClick(View v) {
+ Log.d(this, "onLongClick:");
+ return mPresenter.onLongClick();
+ }
+
+ /**
* Returns the dimensions of the surface.
*
* @return The dimensions of the surface.
@@ -563,6 +580,8 @@
if (mPreviewPhoto != null) {
mPreviewPhoto.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
}
+
+ enableScreenTimeout(false);
}
/**
@@ -570,6 +589,7 @@
*/
@Override
public void hideVideoUi() {
+ enableScreenTimeout(true);
inflateVideoUi(false);
}
@@ -693,9 +713,15 @@
preview.setLayoutParams(params);
if (mPreviewVideoContainer != null) {
- ViewGroup.LayoutParams containerParams = mPreviewVideoContainer.getLayoutParams();
+ FrameLayout.LayoutParams containerParams = (FrameLayout.LayoutParams)
+ mPreviewVideoContainer.getLayoutParams();
containerParams.width = width;
containerParams.height = height;
+ if (getPresenter().isCameraPreviewMode()) {
+ containerParams.gravity = Gravity.CENTER;
+ } else {
+ containerParams.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ }
mPreviewVideoContainer.setLayoutParams(containerParams);
}
@@ -809,6 +835,13 @@
return sPreviewSurface.getSurfaceDimensions();
}
+ @Override
+ public void showOutgoingVideoView(boolean show) {
+ if (mPreviewVideoContainer != null) {
+ mPreviewVideoContainer.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
/**
* Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary,
* and creates {@link VideoCallSurface} instances to track the surfaces.
@@ -828,7 +861,12 @@
Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse);
//If peer adjusted screen size is not available, set screen size to default display size
- Point screenSize = sDisplaySize == null ? getScreenSize() : sDisplaySize;
+ Point screenSize = getScreenSize();
+ if (sDisplaySize != null) {
+ screenSize = VideoCallPresenter.resizeForAspectRatio(screenSize,
+ sDisplaySize.x, sDisplaySize.y);
+ }
+
setSurfaceSizeAndTranslation(displaySurface, screenSize);
if (!sVideoSurfacesInUse) {
@@ -898,4 +936,23 @@
centerDisplayView(textureView);
}
}
+
+ private void enableScreenTimeout(boolean enable) {
+ Log.v(this, "enableScreenTimeout: value=" + enable);
+ final Activity activity = getActivity();
+ if (activity == null) {
+ Log.e(this, "enableScreenTimeout: Activity is null.");
+ return;
+ }
+ final Window window = activity.getWindow();
+ if (window == null) {
+ Log.e(this, "enableScreenTimeout: window is null");
+ return;
+ }
+ if (enable) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
}
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
index 06e3e44..58405a8 100644
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
@@ -23,7 +23,9 @@
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemProperties;
import android.provider.ContactsContract;
+import android.content.pm.ActivityInfo;
import android.telecom.Connection;
import android.telecom.InCallService.VideoCall;
import android.telecom.VideoProfile;
@@ -31,6 +33,8 @@
import android.view.Surface;
import android.widget.ImageView;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.compat.CompatUtils;
import com.android.dialer.R;
@@ -68,7 +72,8 @@
public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi> implements
IncomingCallListener, InCallOrientationListener, InCallStateListener,
InCallDetailsListener, SurfaceChangeListener, VideoEventListener,
- InCallPresenter.InCallEventListener {
+ InCallPresenter.InCallEventListener, InCallUiStateNotifierListener,
+ CallList.CallUpdateListener, PictureModeHelper.Listener {
public static final String TAG = "VideoCallPresenter";
public static final boolean DEBUG = false;
@@ -193,11 +198,49 @@
private int mAutoFullscreenTimeoutMillis = 0;
/**
+ *Caches information about whether InCall UI is in the background or foreground
+ */
+ private boolean mIsInBackground;
+ /**
* Determines if the countdown is currently running to automatically enter full screen video
* mode.
*/
private boolean mAutoFullScreenPending = false;
+ // Stores the current orientation mode from primary call
+ private int mActivityOrientationMode = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+ /**
+ * Determines if the incoming video is available. If the call session resume event has been
+ * received (i.e PLAYER_START has been received from lower layers), incoming video is
+ * available. If the call session pause event has been received (i.e PLAYER_STOP has been
+ * received from lower layers), incoming video is not available.
+ */
+ private static boolean mIsIncomingVideoAvailable = false;
+
+ /**
+ * Property when set will disable PIP mode.
+ * Default value is 0 (disable). To enable, set to 1 (enable)
+ */
+ private static final String PROP_DISABLE_VIDEOCALL_PIP_MODE =
+ "persist.disable.pip.mode";
+
+ /**
+ * Property set to specify the camera preview size when the picture mode is selected as
+ * camera preview mode only. Format is widthxheight (e.g 320x240)
+ */
+ private static final String PROP_CAMERA_PREVIEW_SIZE =
+ "persist.camera.preview.size";
+
+ private static final String CAMERA_PREVIEW_SIZE_DELIM = "x";
+
+ /**
+ * Cache the aspect ratio of the preview window.
+ */
+ private float mPreviewAspectRatio = 1.0f;
+
+ private PictureModeHelper mPictureModeHelper;
+
/**
* Initializes the presenter.
*
@@ -205,6 +248,7 @@
*/
public void init(Context context) {
mContext = context;
+ mPictureModeHelper = new PictureModeHelper(mContext);
mMinimumVideoDimension = mContext.getResources().getDimension(
R.dimen.video_preview_small_dimension);
mHandler = new Handler(Looper.getMainLooper());
@@ -238,16 +282,19 @@
// To get updates of video call details changes
InCallPresenter.getInstance().addDetailsListener(this);
InCallPresenter.getInstance().addInCallEventListener(this);
+ mPictureModeHelper.setUp(this);
// Register for surface and video events from {@link InCallVideoCallListener}s.
InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
- InCallVideoCallCallbackNotifier.getInstance().addVideoEventListener(this);
+ InCallUiStateNotifier.getInstance().addListener(this);
mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY;
mCurrentCallState = Call.State.INVALID;
final InCallPresenter.InCallState inCallState =
InCallPresenter.getInstance().getInCallState();
onStateChange(inCallState, inCallState, CallList.getInstance());
+ InCallVideoCallCallbackNotifier.getInstance().addVideoEventListener(this,
+ VideoUtils.isVideoCall(mCurrentVideoState));
}
/**
@@ -274,6 +321,12 @@
InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this);
InCallVideoCallCallbackNotifier.getInstance().removeVideoEventListener(this);
+ InCallUiStateNotifier.getInstance().removeListener(this);
+ if(mPrimaryCall != null) {
+ CallList.getInstance().removeCallUpdateListener(mPrimaryCall.getId(), this);
+ }
+ mPictureModeHelper.tearDown(this);
+ cancelAutoFullScreen();
}
/**
@@ -299,11 +352,17 @@
if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface());
- } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()){
- enableCamera(mVideoCall, true);
+ } else {
+ maybeEnableCamera();
}
} else if (surface == VideoCallFragment.SURFACE_DISPLAY) {
mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface());
+
+ // Show/hide the incoming video once surface is created based on
+ // whether PLAYER_START event has been received or not. Since we
+ // start with showing incoming video by default for surface creation,
+ // we need to make sure we hide it once surface is available.
+ showVideoUi(mCurrentVideoState, mCurrentCallState, isConfCall());
}
}
@@ -370,15 +429,35 @@
}
/**
- * Handles clicks on the video surfaces by toggling full screen state.
- * Informs the {@link InCallPresenter} of the change so that it can inform the
- * {@link CallCardPresenter} of the change.
+ * Handles clicks on the video surfaces by toggling full screen state if surface is
+ * SURFACE_DISPLAY. Call onPreviewSurfaceClicked of InCallZoomController if preview surface
+ * is clicked. Informs the {@link InCallPresenter} of the change for Display surface so that
+ * it can inform the {@link CallCardPresenter} of the change.
*
* @param surfaceId The video surface receiving the click.
*/
public void onSurfaceClick(int surfaceId) {
- boolean isFullscreen = InCallPresenter.getInstance().toggleFullscreenMode();
- Log.v(this, "toggleFullScreen = " + isFullscreen);
+ if (!mIsVideoMode) {
+ Log.d(this, "onSurfaceClick: Not in video mode ignoring.");
+ return;
+ }
+ switch (surfaceId) {
+ case VideoCallFragment.SURFACE_DISPLAY:
+ boolean isFullscreen = InCallPresenter.getInstance().toggleFullscreenMode();
+ Log.d(this, "toggleFullScreen = " + isFullscreen + "surfaceId =" + surfaceId);
+ break;
+ case VideoCallFragment.SURFACE_PREVIEW:
+ if (mPictureModeHelper.canShowPreviewVideoView() &&
+ mPictureModeHelper.canShowIncomingVideoView()) {
+ InCallZoomController.getInstance().onPreviewSurfaceClicked(mVideoCall);
+ } else {
+ isFullscreen = InCallPresenter.getInstance().toggleFullscreenMode();
+ Log.d(this, "toggleFullScreen = " + isFullscreen + "surfaceId =" + surfaceId);
+ }
+ break;
+ default:
+ break;
+ }
}
/**
@@ -411,7 +490,6 @@
if (isVideoMode()) {
exitVideoMode();
}
-
cleanupSurfaces();
}
@@ -471,6 +549,10 @@
cancelAutoFullScreen();
}
+ @Override
+ public void updatePrimaryCallState() {
+ }
+
/**
* Handles changes to the visibility of the secondary caller info bar.
*
@@ -486,9 +568,14 @@
getUi().adjustPreviewLocation(isVisible /* shiftUp */, height);
}
+ @Override
+ public void onIncomingVideoAvailabilityChanged(boolean isAvailable) {
+ //NO OP
+ }
+
private void checkForVideoStateChange(Call call) {
- final boolean isVideoCall = VideoUtils.isVideoCall(call);
- final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState();
+ boolean isVideoCall = VideoUtils.isVideoCall(call);
+ boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState();
Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall
+ " hasVideoStateChanged=" + hasVideoStateChanged + " isVideoMode="
@@ -496,6 +583,11 @@
VideoProfile.videoStateToString(mCurrentVideoState) + " newVideoState: "
+ VideoProfile.videoStateToString(call.getVideoState()));
+ if (isModifyCallPreview(mContext, call)) {
+ isVideoCall |= VideoUtils.isVideoCall(call.getRequestedVideoState());
+ hasVideoStateChanged |= mCurrentVideoState != call.getRequestedVideoState();
+ }
+
if (!hasVideoStateChanged) {
return;
}
@@ -535,7 +627,7 @@
}
// Make sure we hide or show the video UI if needed.
- showVideoUi(call.getVideoState(), call.getState());
+ showVideoUi(call.getVideoState(), call.getState(), call.isConferenceCall());
}
private void cleanupSurfaces() {
@@ -554,6 +646,7 @@
Log.d(this, "onPrimaryCallChanged: isVideoCall=" + isVideoCall + " isVideoMode="
+ isVideoMode);
+ listenToCallUpdates(newPrimaryCall);
if (!isVideoCall && isVideoMode) {
// Terminate video mode if new primary call is not a video call
// and we are currently in video mode.
@@ -562,6 +655,7 @@
} else if (isVideoCall) {
Log.d(this, "onPrimaryCallChanged: Entering video mode...");
+ checkForOrientationAllowedChange(newPrimaryCall);
updateCameraSelection(newPrimaryCall);
adjustVideoMode(newPrimaryCall);
}
@@ -619,8 +713,13 @@
}
private void checkForOrientationAllowedChange(Call call) {
- InCallPresenter.getInstance().setInCallAllowsOrientationChange(
- VideoUtils.isVideoCall(call));
+ final int newMode = OrientationModeHandler.getInstance().getOrientation(call);
+ if (newMode != mActivityOrientationMode && InCallPresenter.
+ getInstance().setInCallAllowsOrientationChange(newMode)) {
+ Log.d(this, "checkForOrientationAllowedChange: currMode = " +
+ mActivityOrientationMode + " newMode = " + newMode);
+ mActivityOrientationMode = newMode;
+ }
}
/**
@@ -664,9 +763,9 @@
}
}
- private static boolean isCameraRequired(int videoState) {
- return VideoProfile.isBidirectional(videoState)
- || VideoProfile.isTransmissionEnabled(videoState);
+ private boolean isCameraRequired(int videoState) {
+ return ((VideoProfile.isBidirectional(videoState) ||
+ VideoProfile.isTransmissionEnabled(videoState)) && !mIsInBackground);
}
private boolean isCameraRequired() {
@@ -689,8 +788,13 @@
Log.e(this, "Error VideoCallUi is null so returning");
return;
}
+ if (isModifyCallPreview(mContext, call)) {
+ Log.d(this, "modifying video state = " + newVideoState +
+ " to video state: " + call.getRequestedVideoState());
+ newVideoState = call.getRequestedVideoState();
+ }
- showVideoUi(newVideoState, call.getState());
+ showVideoUi(newVideoState, call.getState(), call.isConferenceCall());
// Communicate the current camera to telephony and make a request for the camera
// capabilities.
@@ -728,9 +832,11 @@
mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
videoCall.requestCameraCapabilities();
+ InCallZoomController.getInstance().onCameraEnabled(cameraManager.getActiveCameraId());
} else {
mPreviewSurfaceState = PreviewSurfaceState.NONE;
videoCall.setCamera(null);
+ InCallZoomController.getInstance().onCameraEnabled(null);
}
}
@@ -740,7 +846,7 @@
private void exitVideoMode() {
Log.d(this, "exitVideoMode");
- showVideoUi(VideoProfile.STATE_AUDIO_ONLY, Call.State.ACTIVE);
+ showVideoUi(VideoProfile.STATE_AUDIO_ONLY, Call.State.ACTIVE, false);
enableCamera(mVideoCall, false);
InCallPresenter.getInstance().setFullScreen(false);
@@ -750,39 +856,57 @@
/**
* Based on the current video state and call state, show or hide the incoming and
* outgoing video surfaces. The outgoing video surface is shown any time video is transmitting.
- * The incoming video surface is shown whenever the video is un-paused and active.
+ * The incoming video surface is shown whenever the video is un-paused and active and incoming
+ * video is available. If display surface has not been created and video reception is enabled,
+ * we override the value returned by showIncomingVideo and show the incoming video so surface
+ * creation is enabled
*
* @param videoState The video state.
* @param callState The call state.
*/
- private void showVideoUi(int videoState, int callState) {
+ private void showVideoUi(int videoState, int callState, boolean isConf) {
VideoCallUi ui = getUi();
if (ui == null) {
Log.e(this, "showVideoUi, VideoCallUi is null returning");
return;
}
- boolean showIncomingVideo = showIncomingVideo(videoState, callState);
- boolean showOutgoingVideo = showOutgoingVideo(videoState);
+
+ final boolean isDisplaySurfaceCreated = ui.isDisplayVideoSurfaceCreated();
+ final boolean isVideoReceptionEnabled = VideoProfile.isReceptionEnabled(videoState);
+ boolean showIncomingVideo = (showIncomingVideo(videoState, callState) &&
+ mPictureModeHelper.canShowIncomingVideoView()) ||
+ (!isDisplaySurfaceCreated && isVideoReceptionEnabled);
+ boolean showOutgoingVideo = showOutgoingVideo(videoState) &&
+ mPictureModeHelper.canShowPreviewVideoView();
+
Log.v(this, "showVideoUi : showIncoming = " + showIncomingVideo + " showOutgoing = "
+ showOutgoingVideo);
if (showIncomingVideo || showOutgoingVideo) {
ui.showVideoViews(showOutgoingVideo, showIncomingVideo);
- if (VideoProfile.isReceptionEnabled(videoState)) {
+ boolean hidePreview = shallHidePreview(isConf, videoState);
+ Log.v(this, "showVideoUi, hidePreview = " + hidePreview);
+ if (hidePreview) {
+ ui.showOutgoingVideoView(!hidePreview);
+ }
+
+ if (showOutgoingVideo) {
+ setPreviewSize(mDeviceOrientation, mPreviewAspectRatio);
+ }
+
+ if (isVideoReceptionEnabled) {
loadProfilePhotoAsync();
}
} else {
ui.hideVideoUi();
}
-
- InCallPresenter.getInstance().enableScreenTimeout(
- VideoProfile.isAudioOnly(videoState));
}
/**
* Determines if the incoming video surface should be shown based on the current videoState and
- * callState. The video surface is shown when incoming video is not paused, the call is active,
- * and video reception is enabled.
+ * callState. The video surface is shown when video reception is enabled AND either incoming
+ * video is not paused, the call is active or dialing, incoming video is available
+ * (i.e PLAYER_START event has been raised by lower layers)
*
* @param videoState The current video state.
* @param callState The current call state.
@@ -795,8 +919,12 @@
boolean isPaused = VideoProfile.isPaused(videoState);
boolean isCallActive = callState == Call.State.ACTIVE;
+ //Show incoming Video for dialing calls to support early media
+ boolean isCallOutgoing = Call.State.isDialing(callState) ||
+ callState == Call.State.CONNECTING;
- return !isPaused && isCallActive && VideoProfile.isReceptionEnabled(videoState);
+ return !isPaused && (isCallActive || isCallOutgoing) &&
+ VideoProfile.isReceptionEnabled(videoState) && mIsIncomingVideoAvailable;
}
/**
@@ -816,6 +944,40 @@
}
/**
+ * Opens camera if the camera has not yet been set on the {@link VideoCall}; negotiation has
+ * not yet started and if camera is required
+ */
+ private void maybeEnableCamera() {
+ if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()) {
+ enableCamera(mVideoCall, true);
+ }
+ }
+
+ /**
+ * This method gets invoked when visibility of InCallUI is changed. For eg.
+ * when UE moves in/out of the foreground, display either turns ON/OFF
+ * @param showing true if InCallUI is visible, false otherwise.
+ */
+ @Override
+ public void onUiShowing(boolean showing) {
+ Log.d(this, "onUiShowing, showing = " + showing + " mPrimaryCall = " + mPrimaryCall +
+ " mPreviewSurfaceState = " + mPreviewSurfaceState);
+
+ mIsInBackground = !showing;
+
+ if (mPrimaryCall == null || !VideoUtils.isActiveVideoCall(mPrimaryCall)) {
+ Log.w(this, "onUiShowing, received for non-active video call");
+ return;
+ }
+
+ if (showing) {
+ maybeEnableCamera();
+ } else if (mPreviewSurfaceState != PreviewSurfaceState.NONE) {
+ enableCamera(mVideoCall, false);
+ }
+ }
+
+ /**
* Handles peer video pause state changes.
*
* @param call The call which paused or un-pausedvideo transmission.
@@ -923,9 +1085,11 @@
aspectRatio = (float) width / (float) height;
}
+ mPreviewAspectRatio = aspectRatio;
+
// Resize the textureview housing the preview video and rotate it appropriately based on
// the device orientation
- setPreviewSize(mDeviceOrientation, aspectRatio);
+ setPreviewSize(mDeviceOrientation, mPreviewAspectRatio);
}
/**
@@ -940,10 +1104,13 @@
switch (event) {
case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE:
- sb.append("rx_pause");
- break;
case Connection.VideoProvider.SESSION_EVENT_RX_RESUME:
- sb.append("rx_resume");
+ mIsIncomingVideoAvailable =
+ event == Connection.VideoProvider.SESSION_EVENT_RX_RESUME;
+ showVideoUi(mCurrentVideoState, mCurrentCallState, isConfCall());
+ sb.append(mIsIncomingVideoAvailable ? "rx_resume" : "rx_pause");
+ InCallPresenter.getInstance().
+ notifyIncomingVideoAvailabilityChanged(mIsIncomingVideoAvailable);
break;
case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
sb.append("camera_failure");
@@ -996,6 +1163,9 @@
changePreviewDimensions(previewDimensions.x, previewDimensions.y);
ui.setPreviewRotation(mDeviceOrientation);
+ // Notify picture mode changed so that if camera preview is showing in non PIP
+ // mode, we can correctly resize the camera preview by swapping width and height.
+ showVideoUi(mCurrentVideoState, mCurrentCallState, isConfCall());
}
/**
@@ -1009,24 +1179,45 @@
* @param aspectRatio The aspect ratio of the camera (width / height).
*/
private void setPreviewSize(int orientation, float aspectRatio) {
+ Log.d(this, "setPreviewSize: orientation = " + orientation +
+ " aspectRatio = " + aspectRatio);
VideoCallUi ui = getUi();
if (ui == null) {
return;
}
- int height;
- int width;
+ float height = 0.0f;
+ float width = 0.0f;
+ final boolean isPipMode = mPictureModeHelper.isPipMode();
- if (orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_90 ||
- orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_270) {
- width = (int) (mMinimumVideoDimension * aspectRatio);
- height = (int) mMinimumVideoDimension;
+ if (isPipMode) {
+ width = mMinimumVideoDimension;
+ height = mMinimumVideoDimension;
} else {
- // Portrait or reverse portrait orientation.
- width = (int) mMinimumVideoDimension;
- height = (int) (mMinimumVideoDimension * aspectRatio);
+ Point size = getPreviewVideoSize();
+ // Swap width and height if landscape
+ final boolean isLayoutLandscape = mContext.getResources().getBoolean(
+ R.bool.is_layout_landscape);
+ width = isLayoutLandscape ? size.y : size.x;
+ height = isLayoutLandscape ? size.x : size.y;
}
- ui.setPreviewSize(width, height);
+
+ final boolean hasNoPreviewSizeInProp = ((SystemProperties.get(
+ PROP_CAMERA_PREVIEW_SIZE, "")).isEmpty());
+
+ // Do not apply aspect ratio if camera preview is set in the adb property -
+ // "persist.camera.preview.size". Aspect ratio is applied to full screen size for
+ // camera preview and for Pip mode
+ if (hasNoPreviewSizeInProp || isPipMode) {
+ if (orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_90 ||
+ orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_270) {
+ width = (aspectRatio > 1.0) ? width * aspectRatio : width / aspectRatio;
+ } else {
+ // Portrait or reverse portrait orientation.
+ height = (aspectRatio > 1.0) ? height * aspectRatio : height / aspectRatio;
+ }
+ }
+ ui.setPreviewSize((int) width, (int) height);
}
/**
@@ -1046,6 +1237,12 @@
Point size = ui.getScreenSize();
Log.v(this, "setDisplayVideoSize: windowmgr width=" + size.x
+ " windowmgr height=" + size.y);
+ size = resizeForAspectRatio(size, width, height);
+ ui.setDisplayVideoSize(size.x, size.y);
+ }
+
+ public static Point resizeForAspectRatio(Point inSize, int width, int height) {
+ Point size = new Point(inSize);
if (size.y * width > size.x * height) {
// current display height is too much. Correct it
size.y = (int) (size.x * height / width);
@@ -1053,7 +1250,7 @@
// current display width is too much. Correct it
size.x = (int) (size.y * width / height);
}
- ui.setDisplayVideoSize(size.x, size.y);
+ return size;
}
/**
@@ -1117,9 +1314,16 @@
}
Log.v(this, "cancelAutoFullScreen : cancelling pending");
mAutoFullScreenPending = false;
+ if (mHandler != null) {
+ mHandler.removeCallbacks(mAutoFullscreenRunnable);
+ }
}
- private static void updateCameraSelection(Call call) {
+ private static boolean isAudioRouteEnabled(int audioRoute, int audioRouteMask) {
+ return ((audioRoute & audioRouteMask) != 0);
+ }
+
+ private void updateCameraSelection(Call call) {
Log.d(TAG, "updateCameraSelection: call=" + call);
Log.d(TAG, "updateCameraSelection: call=" + toSimpleString(call));
@@ -1134,6 +1338,12 @@
+ " Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
}
+ // for preview scenario if it is supported
+ else if(isModifyCallPreview(mContext, call)) {
+ cameraDir = toCameraDirection(call.getRequestedVideoState());
+ call.getVideoSettings().setCameraDir(cameraDir);
+ }
+
// Clear camera direction if this is not a video call.
else if (VideoUtils.isAudioCall(call)) {
cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
@@ -1203,6 +1413,83 @@
}
/**
+ * The function is called to create and display picture mode alert dialog when user long
+ * presses on the video call screen
+ */
+ public boolean onLongClick() {
+ // Don't show the alert if either the adb property "persist.disable.pip.mode" is not set
+ // or if we are supposed to hide preview for conference calls
+ if ((SystemProperties.getInt(PROP_DISABLE_VIDEOCALL_PIP_MODE, 0) == 0) ||
+ shallHidePreview(isConfCall(), mCurrentVideoState)) {
+ return false;
+ }
+ mPictureModeHelper.create(mContext);
+ mPictureModeHelper.show();
+ return true;
+ }
+
+ /**
+ * Gets the preview video size either from the property - "persist.camera.preview.size" if it
+ * is set or return the full screen size
+ */
+ private Point getPreviewVideoSize() {
+ VideoCallUi ui = getUi();
+ if (ui == null) {
+ Log.e(this, "getPreviewVideoSize, VideoCallUi is null returning");
+ return null;
+ }
+
+ Point previewSize = getPreviewVideoSizeFromProp();
+
+ if (previewSize == null) {
+ previewSize = ui.getScreenSize();
+ }
+
+ return previewSize;
+ }
+
+ /**
+ * Gets the preview video size from the property - "persist.camera.preview.size"
+ * @return Point point - Size of the preview (width and height)
+ */
+ private static Point getPreviewVideoSizeFromProp() {
+ final String cameraPreviewSize = SystemProperties.get(
+ PROP_CAMERA_PREVIEW_SIZE, "");
+ if (!cameraPreviewSize.isEmpty()) {
+ final String[] sizeDimensions = cameraPreviewSize.split(CAMERA_PREVIEW_SIZE_DELIM);
+ final int width = Integer.parseInt(sizeDimensions[0]);
+ final int height = Integer.parseInt(sizeDimensions[1]);
+ return new Point(width, height);
+ }
+ return null;
+ }
+
+ /**
+ * Gets called when preview video selection changes
+ * @param boolean previewVideoSelection - New value for preview video selection
+ */
+ @Override
+ public void onPreviewVideoSelectionChanged() {
+ VideoCallUi ui = getUi();
+ if (ui == null) {
+ Log.e(this, "onPreviewVideoSelectionChanged, VideoCallUi is null returning");
+ return;
+ }
+
+ ui.showOutgoingVideoView(showOutgoingVideo(mCurrentVideoState) &&
+ mPictureModeHelper.canShowPreviewVideoView());
+ }
+
+ /**
+ * Gets called when incoming video selection changes
+ * @param boolean incomingVideoSelection - New value for incoming video selection
+ */
+ @Override
+ public void onIncomingVideoSelectionChanged() {
+ showVideoUi(mCurrentVideoState, mCurrentCallState, isConfCall());
+ }
+
+ /**
* Starts an asynchronous load of the user's profile photo.
*/
public void loadProfilePhotoAsync() {
@@ -1283,6 +1570,23 @@
}
/**
+ * Hide preview window if it is a VT conference call
+ */
+ private boolean shallHidePreview(boolean isConf, int videoState) {
+ return VideoProfile.isBidirectional(videoState) && isConf
+ && QtiImsExtUtils.shallHidePreviewInVtConference(mContext);
+ }
+
+ private boolean isConfCall() {
+ return mPrimaryCall != null ? mPrimaryCall.isConferenceCall() : false;
+ }
+
+ public boolean isCameraPreviewMode() {
+ return mPictureModeHelper.canShowPreviewVideoView() &&
+ !(mPictureModeHelper.canShowIncomingVideoView());
+ }
+
+ /**
* Defines the VideoCallUI interactions.
*/
public interface VideoCallUi extends Ui {
@@ -1302,5 +1606,71 @@
ImageView getPreviewPhotoView();
void adjustPreviewLocation(boolean shiftUp, int offset);
void setPreviewRotation(int orientation);
+ void showOutgoingVideoView(boolean show);
+ }
+
+ /**
+ * Returns true if camera preview shall be shown till remote user react on the request.
+ */
+ private static boolean isModifyCallPreview(Context ctx, Call call) {
+ if (call == null || !QtiCallUtils.shallShowPreviewWhileWaiting(ctx)) {
+ return false;
+ }
+ return (call.getSessionModificationState() ==
+ Call.SessionModificationState.WAITING_FOR_RESPONSE) &&
+ VideoProfile.isTransmissionEnabled(call.getRequestedVideoState());
+ }
+
+ private void listenToCallUpdates(Call call) {
+ if (!QtiCallUtils.shallShowPreviewWhileWaiting(mContext)) {
+ return;
+ }
+
+ if (mPrimaryCall != null) {
+ CallList.getInstance().removeCallUpdateListener(mPrimaryCall.getId(), this);
+ }
+
+ if (call != null) {
+ CallList.getInstance().addCallUpdateListener(call.getId(), this);
+ }
+ }
+
+ @Override
+ public void onSessionModificationStateChange(Call call, int sessionModificationState) {
+ Log.d(this, "onSessionModificationStateChange : sessionModificationState = " +
+ sessionModificationState + " call:" + call);
+ if (call != mPrimaryCall ||
+ (sessionModificationState == Call.SessionModificationState.NO_REQUEST)) {
+ return;
+ }
+ if (!VideoProfile.isTransmissionEnabled(call.getRequestedVideoState())) {
+ call.setRequestedVideoState(VideoProfile.STATE_AUDIO_ONLY);
+ return;
+ }
+
+ if (sessionModificationState != Call.SessionModificationState.WAITING_FOR_RESPONSE) {
+ call.setRequestedVideoState(VideoProfile.STATE_AUDIO_ONLY);
+ }
+
+ checkForVideoStateChange(call);
+
+ if (sessionModificationState == Call.SessionModificationState.REQUEST_REJECTED
+ || sessionModificationState == Call.SessionModificationState.REQUEST_FAILED
+ || sessionModificationState ==
+ Call.SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT) {
+ mCurrentVideoState = call.getVideoState();
+ }
+ }
+
+ @Override
+ public void onLastForwardedNumberChange() {
+ }
+
+ @Override
+ public void onCallChanged(Call call) {
+ }
+
+ @Override
+ public void onChildNumberChange() {
}
}
diff --git a/InCallUI/src/com/android/incallui/VideoPauseController.java b/InCallUI/src/com/android/incallui/VideoPauseController.java
index fb87350..a953f1d 100644
--- a/InCallUI/src/com/android/incallui/VideoPauseController.java
+++ b/InCallUI/src/com/android/incallui/VideoPauseController.java
@@ -29,8 +29,9 @@
* This class is responsible for generating video pause/resume requests when the InCall UI is sent
* to the background and subsequently brought back to the foreground.
*/
-class VideoPauseController implements InCallStateListener, IncomingCallListener {
- private static final String TAG = "VideoPauseController";
+class VideoPauseController implements InCallStateListener, IncomingCallListener,
+ InCallUiStateNotifierListener {
+ private static final String TAG = "VideoPauseController:";
/**
* Keeps track of the current active/foreground call.
@@ -106,6 +107,7 @@
mInCallPresenter = Preconditions.checkNotNull(inCallPresenter);
mInCallPresenter.addListener(this);
mInCallPresenter.addIncomingCallListener(this);
+ InCallUiStateNotifier.getInstance().addListener(this);
}
/**
@@ -114,6 +116,7 @@
*/
public void tearDown() {
log("tearDown...");
+ InCallUiStateNotifier.getInstance().removeListener(this);
mInCallPresenter.removeListener(this);
mInCallPresenter.removeIncomingCallListener(this);
clear();
@@ -194,11 +197,10 @@
Preconditions.checkState(!areSame(call, mPrimaryCallContext));
final boolean canVideoPause = VideoUtils.canVideoPause(call);
- if ((isIncomingCall(mPrimaryCallContext) || isDialing(mPrimaryCallContext) ||
- (call != null && VideoProfile.isPaused(call.getVideoState())))
+ if ((isIncomingCall(mPrimaryCallContext) || isDialing(mPrimaryCallContext))
&& canVideoPause && !mIsInBackground) {
// Send resume request for the active call, if user rejects incoming call, ends dialing
- // call, or the call was previously in a paused state and UI is in the foreground.
+ // call and UI is in the foreground.
sendRequest(call, true);
} else if (isIncomingCall(call) && canVideoPause(mPrimaryCallContext)) {
// Send pause request if there is an active video call, and we just received a new
@@ -243,10 +245,13 @@
}
/**
- * Called when UI goes in/out of the foreground.
- * @param showing true if UI is in the foreground, false otherwise.
+ * This method gets invoked when visibility of InCallUI is changed. For eg.
+ * when UE moves in/out of the foreground, display either turns ON/OFF
+ * @param showing true if InCallUI is visible, false otherwise.
*/
+ @Override
public void onUiShowing(boolean showing) {
+ log("onUiShowing, showing = " + showing);
// Only send pause/unpause requests if we are in the INCALL state.
if (mInCallPresenter == null) {
return;
@@ -267,6 +272,11 @@
private void onResume(boolean isInCall) {
log("onResume");
+ if (!mIsInBackground) {
+ log("onResume, Ignoring... already resumed");
+ return;
+ }
+
mIsInBackground = false;
if (canVideoPause(mPrimaryCallContext) && isInCall) {
sendRequest(mPrimaryCallContext.getCall(), true);
@@ -283,6 +293,11 @@
private void onPause(boolean isInCall) {
log("onPause");
+ if (mIsInBackground) {
+ log("onPause, Ignoring... already paused");
+ return;
+ }
+
mIsInBackground = true;
if (canVideoPause(mPrimaryCallContext) && isInCall) {
sendRequest(mPrimaryCallContext.getCall(), false);
@@ -366,7 +381,7 @@
* @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
*/
private static boolean isIncomingCall(CallContext call) {
- return call != null && isIncomingCall(call.getCall());
+ return call != null && isIncoming(call.getState());
}
/**
@@ -376,8 +391,17 @@
* @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
*/
private static boolean isIncomingCall(Call call) {
- return call != null && (call.getState() == Call.State.CALL_WAITING
- || call.getState() == Call.State.INCOMING);
+ return call != null && isIncoming(call.getState());
+ }
+
+ /**
+ * Determines if a call state is incoming/waiting.
+ *
+ * @param state The call state
+ * @return {@code true} if the state is incoming or waiting, {@code false} otherwise.
+ */
+ private static boolean isIncoming(int state) {
+ return state == Call.State.CALL_WAITING || state == Call.State.INCOMING;
}
/**
diff --git a/InCallUI/src/com/android/incallui/ZoomControl.java b/InCallUI/src/com/android/incallui/ZoomControl.java
new file mode 100644
index 0000000..7e25b98
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/ZoomControl.java
@@ -0,0 +1,132 @@
+/* Copyright (c) 2012 - 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution, Apache license notifications and license are retained
+ * for attribution purposes only.
+ *
+ * Copyright (C) 2011 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.incallui;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+/**
+ * A view that contains camera zoom control which could adjust the zoom in/out
+ * if the camera supports zooming.
+ */
+public abstract class ZoomControl extends RelativeLayout{
+ protected ImageView mZoomIn;
+ protected ImageView mZoomOut;
+ protected ImageView mZoomSlider;
+ protected int mOrientation;
+
+ public interface OnZoomChangedListener {
+ void onZoomValueChanged(int index); // only for immediate zoom
+ }
+
+ // The interface OnZoomIndexChangedListener is used to inform the
+ // ZoomIndexBar about the zoom index change. The index position is between
+ // 0 (the index is zero) and 1.0 (the index is mZoomMax).
+ public interface OnZoomIndexChangedListener {
+ void onZoomIndexChanged(double indexPosition);
+ }
+
+ protected int mZoomMax, mZoomIndex;
+ private OnZoomChangedListener mListener;
+
+ private int mStep;
+
+ public ZoomControl(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mZoomIn = addImageView(context, R.drawable.ic_zoom_in);
+ mZoomSlider = addImageView(context, R.drawable.ic_zoom_slider);
+ mZoomOut = addImageView(context, R.drawable.ic_zoom_out);
+ }
+
+ public void startZoomControl() {
+ mZoomSlider.setPressed(true);
+ setZoomIndex(mZoomIndex); // Update the zoom index bar.
+ }
+
+ protected ImageView addImageView(Context context, int iconResourceId) {
+ ImageView image = new ImageView(context);
+ image.setImageResource(iconResourceId);
+ addView(image);
+ return image;
+ }
+
+ public void closeZoomControl() {
+ mZoomSlider.setPressed(false);
+ }
+
+ public void setZoomMax(int zoomMax) {
+ mZoomMax = zoomMax;
+
+ // Layout should be requested as the maximum zoom level is the key to
+ // show the correct zoom slider position.
+ requestLayout();
+ }
+
+ public int getZoomMax() {
+ return mZoomMax;
+ }
+
+ public void setOnZoomChangeListener(OnZoomChangedListener listener) {
+ mListener = listener;
+ }
+
+ public void setZoomIndex(int index) {
+ if (index < 0 || index > mZoomMax) {
+ throw new IllegalArgumentException("Invalid zoom value:" + index);
+ }
+ mZoomIndex = index;
+ invalidate();
+ }
+
+ public int getZoomIndex() {
+ return mZoomIndex;
+ }
+
+ protected void setZoomStep(int step) {
+ mStep = step;
+ }
+
+ // Called from ZoomControlBar to change the zoom level.
+ protected void performZoom(double zoomPercentage) {
+ int index = (int) (mZoomMax * zoomPercentage);
+ if (mZoomIndex == index) return;
+ changeZoomIndex(index);
+ }
+
+ private boolean changeZoomIndex(int index) {
+ if (mListener != null) {
+ if (index > mZoomMax) index = mZoomMax;
+ if (index < 0) index = 0;
+ mListener.onZoomValueChanged(index);
+ mZoomIndex = index;
+ }
+ return true;
+ }
+
+ @Override
+ public void setActivated(boolean activated) {
+ super.setActivated(activated);
+ mZoomIn.setActivated(activated);
+ mZoomOut.setActivated(activated);
+ }
+}
diff --git a/InCallUI/src/com/android/incallui/ZoomControlBar.java b/InCallUI/src/com/android/incallui/ZoomControlBar.java
new file mode 100644
index 0000000..8a89218
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/ZoomControlBar.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2012 - 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution, Apache license notifications and license are retained
+ * for attribution purposes only.
+ *
+ * Copyright (C) 2011 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.incallui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * A view that contains camera zoom control and its layout.
+ */
+public class ZoomControlBar extends ZoomControl {
+ private static final int THRESHOLD_FIRST_MOVE = 10; // pixels
+ // Space between indicator icon and the zoom-in/out icon.
+ private static final int ICON_SPACING = 12;
+
+ private View mBar;
+ private boolean mStartChanging;
+ private static int mSliderPosition = 0;
+ private int mSliderLength;
+ private int mWidth;
+ private int mIconWidth;
+ private int mTotalIconWidth;
+
+ public ZoomControlBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mBar = new View(context);
+ mBar.setBackgroundResource(R.drawable.zoom_slider_bar);
+ addView(mBar);
+ }
+
+ @Override
+ public void setActivated(boolean activated) {
+ super.setActivated(activated);
+ mBar.setActivated(activated);
+ }
+
+ private int getSliderPosition(int x) {
+ // Calculate the absolute offset of the slider in the zoom control bar.
+ // For left-hand users, as the device is rotated for 180 degree for
+ // landscape mode, the zoom-in bottom should be on the top, so the
+ // position should be reversed.
+ int pos; // the relative position in the zoom slider bar
+ if (mOrientation == 90) {
+ pos = mWidth - mTotalIconWidth - x;
+ } else {
+ pos = x - mTotalIconWidth;
+ }
+ if (pos < 0) pos = 0;
+ if (pos > mSliderLength) pos = mSliderLength;
+ return pos;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ mIconWidth = mZoomIn.getMeasuredWidth();
+ mTotalIconWidth = mIconWidth + ICON_SPACING;
+ mSliderLength = mWidth - (2 * mTotalIconWidth);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (!isEnabled() || (mWidth == 0)) return false;
+ int action = event.getAction();
+
+ switch (action) {
+ case MotionEvent.ACTION_OUTSIDE:
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ setActivated(false);
+ closeZoomControl();
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ setActivated(true);
+ mStartChanging = false;
+ case MotionEvent.ACTION_MOVE:
+ int pos = getSliderPosition((int) event.getX());
+ if (!mStartChanging) {
+ // Make sure the movement is large enough before we start
+ // changing the zoom.
+ int delta = mSliderPosition - pos;
+ if ((delta > THRESHOLD_FIRST_MOVE) ||
+ (delta < -THRESHOLD_FIRST_MOVE)) {
+ mStartChanging = true;
+ }
+ }
+ if (mStartChanging) {
+ performZoom(1.0d * pos / mSliderLength);
+ mSliderPosition = pos;
+ }
+ requestLayout();
+ }
+ return true;
+ }
+
+ @Override
+ protected void onLayout(
+ boolean changed, int left, int top, int right, int bottom) {
+ if (mZoomMax == 0) return;
+ int height = bottom - top;
+ mBar.layout(mTotalIconWidth, 0, mWidth - mTotalIconWidth, height);
+ // For left-hand users, as the device is rotated for 180 degree,
+ // the zoom-in button should be on the top.
+ int pos; // slider position
+ int sliderPosition;
+ if (mSliderPosition != -1) { // -1 means invalid
+ sliderPosition = mSliderPosition;
+ } else {
+ sliderPosition = (int) ((double) mSliderLength * mZoomIndex / mZoomMax);
+ }
+ if (mOrientation == 90) {
+ mZoomIn.layout(0, 0, mIconWidth, height);
+ mZoomOut.layout(mWidth - mIconWidth, 0, mWidth, height);
+ pos = mBar.getRight() - sliderPosition;
+ } else {
+ mZoomOut.layout(0, 0, mIconWidth, height);
+ mZoomIn.layout(mWidth - mIconWidth, 0, mWidth, height);
+ pos = mBar.getLeft() + sliderPosition;
+ }
+ int sliderWidth = mZoomSlider.getMeasuredWidth();
+ mZoomSlider.layout((pos - sliderWidth / 2), 0,
+ (pos + sliderWidth / 2), height);
+ }
+
+ @Override
+ public void setZoomIndex(int index) {
+ super.setZoomIndex(index);
+ mSliderPosition = -1; // -1 means invalid
+ requestLayout();
+ }
+}
diff --git a/res/drawable-hdpi/fab_ic_wificall.png b/res/drawable-hdpi/fab_ic_wificall.png
new file mode 100755
index 0000000..1794845
--- /dev/null
+++ b/res/drawable-hdpi/fab_ic_wificall.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_add_group_holo_dark.png b/res/drawable-hdpi/ic_add_group_holo_dark.png
new file mode 100644
index 0000000..85924ab
--- /dev/null
+++ b/res/drawable-hdpi/ic_add_group_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_hm_videocam.png b/res/drawable-hdpi/ic_hm_videocam.png
new file mode 100644
index 0000000..f80c2f3
--- /dev/null
+++ b/res/drawable-hdpi/ic_hm_videocam.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_m_videocam.png b/res/drawable-hdpi/ic_m_videocam.png
new file mode 100644
index 0000000..7724a8d
--- /dev/null
+++ b/res/drawable-hdpi/ic_m_videocam.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_videocam_off.png b/res/drawable-hdpi/ic_videocam_off.png
new file mode 100644
index 0000000..f59144c
--- /dev/null
+++ b/res/drawable-hdpi/ic_videocam_off.png
Binary files differ
diff --git a/res/drawable-hdpi/stat_sys_vp_phone_call.png b/res/drawable-hdpi/stat_sys_vp_phone_call.png
new file mode 100644
index 0000000..69b9817
--- /dev/null
+++ b/res/drawable-hdpi/stat_sys_vp_phone_call.png
Binary files differ
diff --git a/res/drawable-hdpi/stat_sys_vp_phone_call_on_hold.png b/res/drawable-hdpi/stat_sys_vp_phone_call_on_hold.png
new file mode 100644
index 0000000..f4074c1
--- /dev/null
+++ b/res/drawable-hdpi/stat_sys_vp_phone_call_on_hold.png
Binary files differ
diff --git a/res/drawable-hdpi/wifi_calling_on_notification.png b/res/drawable-hdpi/wifi_calling_on_notification.png
new file mode 100755
index 0000000..fc34ae3
--- /dev/null
+++ b/res/drawable-hdpi/wifi_calling_on_notification.png
Binary files differ
diff --git a/res/drawable-mdpi/fab_ic_wificall.png b/res/drawable-mdpi/fab_ic_wificall.png
new file mode 100755
index 0000000..611f0d2
--- /dev/null
+++ b/res/drawable-mdpi/fab_ic_wificall.png
Binary files differ
diff --git a/res/drawable-xhdpi/fab_ic_wificall.png b/res/drawable-xhdpi/fab_ic_wificall.png
new file mode 100755
index 0000000..f4d522e
--- /dev/null
+++ b/res/drawable-xhdpi/fab_ic_wificall.png
Binary files differ
diff --git a/res/drawable-xhdpi/wifi_calling_on_notification.png b/res/drawable-xhdpi/wifi_calling_on_notification.png
new file mode 100755
index 0000000..2c11f32
--- /dev/null
+++ b/res/drawable-xhdpi/wifi_calling_on_notification.png
Binary files differ
diff --git a/res/drawable-xxhdpi/fab_ic_wificall.png b/res/drawable-xxhdpi/fab_ic_wificall.png
new file mode 100755
index 0000000..ef9d17d
--- /dev/null
+++ b/res/drawable-xxhdpi/fab_ic_wificall.png
Binary files differ
diff --git a/res/drawable-xxhdpi/wifi_calling_on_notification.png b/res/drawable-xxhdpi/wifi_calling_on_notification.png
new file mode 100755
index 0000000..23bb6f9
--- /dev/null
+++ b/res/drawable-xxhdpi/wifi_calling_on_notification.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/fab_ic_wificall.png b/res/drawable-xxxhdpi/fab_ic_wificall.png
new file mode 100755
index 0000000..4d78f05
--- /dev/null
+++ b/res/drawable-xxxhdpi/fab_ic_wificall.png
Binary files differ
diff --git a/res/drawable/btn_addparticipant.xml b/res/drawable/btn_addparticipant.xml
new file mode 100644
index 0000000..85ab180
--- /dev/null
+++ b/res/drawable/btn_addparticipant.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2014, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:drawable="@drawable/btn_background" />
+
+ <item>
+ <bitmap android:src="@drawable/ic_add_group_holo_dark"
+ android:gravity="center"
+ android:tint="@color/selectable_icon_tint" />
+ </item>
+
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/btn_change_to_hm_video.xml b/res/drawable/btn_change_to_hm_video.xml
new file mode 100644
index 0000000..af23bc2
--- /dev/null
+++ b/res/drawable/btn_change_to_hm_video.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2016, The Linux Foundation. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:id="@+id/backgroundItem"
+ android:drawable="@drawable/btn_background" />
+
+ <item>
+ <bitmap android:src="@drawable/ic_hm_videocam"
+ android:gravity="center"
+ android:tint="@color/selectable_icon_tint"
+ android:autoMirrored="true" />
+ </item>
+
+</layer-list>
diff --git a/res/drawable/clear.xml b/res/drawable/clear.xml
new file mode 100644
index 0000000..882ca1e
--- /dev/null
+++ b/res/drawable/clear.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ~
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:width="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41z"/>
+</vector>
diff --git a/res/drawable/color_cursor.xml b/res/drawable/color_cursor.xml
new file mode 100644
index 0000000..be96253
--- /dev/null
+++ b/res/drawable/color_cursor.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ~
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <size android:width="2dp"/>
+ <solid android:color="#FFFFFF"/>
+</shape>
diff --git a/res/drawable/ic_enhance_answer_rx_video.xml b/res/drawable/ic_enhance_answer_rx_video.xml
new file mode 100644
index 0000000..0dedbcb
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_rx_video.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="false"
+ android:drawable="@drawable/ic_enhance_answer_rx_video_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/ic_enhance_answer_rx_video_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/ic_enhance_answer_rx_video_activated_layer" />
+</selector>
diff --git a/res/drawable/ic_enhance_answer_rx_video_activated_layer.xml b/res/drawable/ic_enhance_answer_rx_video_activated_layer.xml
new file mode 100644
index 0000000..e0e19d5
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_rx_video_activated_layer.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/fab_blue" />
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_hm_videocam"
+ android:tint="@color/glowpad_widget_active_color"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_enhance_answer_rx_video_normal_layer.xml b/res/drawable/ic_enhance_answer_rx_video_normal_layer.xml
new file mode 100644
index 0000000..2d49b1d
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_rx_video_normal_layer.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- A fake circle to fix the size of this layer asset. -->
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00000000"/>
+ <size
+ android:width="@dimen/incoming_call_widget_circle_size"
+ android:height="@dimen/incoming_call_widget_circle_size" />
+ </shape>
+ </item>
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_hm_videocam"
+ android:tint="@color/glowpad_call_widget_normal_tint"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_enhance_answer_tx_video.xml b/res/drawable/ic_enhance_answer_tx_video.xml
new file mode 100644
index 0000000..33ba0e1
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_tx_video.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="false"
+ android:drawable="@drawable/ic_enhance_answer_tx_video_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/ic_enhance_answer_tx_video_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/ic_enhance_answer_tx_video_activated_layer" />
+</selector>
diff --git a/res/drawable/ic_enhance_answer_tx_video_activated_layer.xml b/res/drawable/ic_enhance_answer_tx_video_activated_layer.xml
new file mode 100644
index 0000000..76e04e9
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_tx_video_activated_layer.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/fab_blue" />
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_m_videocam"
+ android:tint="@color/glowpad_widget_active_color"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_enhance_answer_tx_video_normal_layer.xml b/res/drawable/ic_enhance_answer_tx_video_normal_layer.xml
new file mode 100644
index 0000000..95d6c81
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_tx_video_normal_layer.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- A fake circle to fix the size of this layer asset. -->
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00000000"/>
+ <size
+ android:width="@dimen/incoming_call_widget_circle_size"
+ android:height="@dimen/incoming_call_widget_circle_size" />
+ </shape>
+ </item>
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_m_videocam"
+ android:tint="@color/glowpad_call_widget_normal_tint"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_enhance_answer_video.xml b/res/drawable/ic_enhance_answer_video.xml
new file mode 100644
index 0000000..a79cd97
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_video.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="false"
+ android:drawable="@drawable/ic_enhance_answer_video_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/ic_enhance_answer_video_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/ic_enhance_answer_video_activated_layer" />
+</selector>
diff --git a/res/drawable/ic_enhance_answer_video_activated_layer.xml b/res/drawable/ic_enhance_answer_video_activated_layer.xml
new file mode 100644
index 0000000..76e04e9
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_video_activated_layer.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/fab_blue" />
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_m_videocam"
+ android:tint="@color/glowpad_widget_active_color"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_enhance_answer_video_normal_layer.xml b/res/drawable/ic_enhance_answer_video_normal_layer.xml
new file mode 100644
index 0000000..95d6c81
--- /dev/null
+++ b/res/drawable/ic_enhance_answer_video_normal_layer.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- A fake circle to fix the size of this layer asset. -->
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00000000"/>
+ <size
+ android:width="@dimen/incoming_call_widget_circle_size"
+ android:height="@dimen/incoming_call_widget_circle_size" />
+ </shape>
+ </item>
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_m_videocam"
+ android:tint="@color/glowpad_call_widget_normal_tint"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_enhance_decline_video.xml b/res/drawable/ic_enhance_decline_video.xml
new file mode 100644
index 0000000..854f7a2
--- /dev/null
+++ b/res/drawable/ic_enhance_decline_video.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="false"
+ android:drawable="@drawable/ic_enhance_decline_video_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/ic_enhance_decline_video_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/ic_enhance_decline_video_activated_layer" />
+</selector>
diff --git a/res/drawable/ic_enhance_decline_video_activated_layer.xml b/res/drawable/ic_enhance_decline_video_activated_layer.xml
new file mode 100644
index 0000000..074fe95
--- /dev/null
+++ b/res/drawable/ic_enhance_decline_video_activated_layer.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/fab_red" />
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_videocam_off"
+ android:tint="#ffffff"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_enhance_decline_video_normal_layer.xml b/res/drawable/ic_enhance_decline_video_normal_layer.xml
new file mode 100644
index 0000000..30c7d9c
--- /dev/null
+++ b/res/drawable/ic_enhance_decline_video_normal_layer.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- A fake circle to fix the size of this layer asset. -->
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00000000"/>
+ <size
+ android:width="56dp"
+ android:height="56dp" />
+ </shape>
+ </item>
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/ic_videocam_off"
+ android:tint="#0288d1"
+ android:autoMirrored="true" />
+ </item>
+</layer-list>
diff --git a/res/drawable/volte_video.xml b/res/drawable/volte_video.xml
new file mode 100644
index 0000000..0008559
--- /dev/null
+++ b/res/drawable/volte_video.xml
@@ -0,0 +1,36 @@
+<!--Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="13dp"
+ android:height="11dp"
+ android:autoMirrored="true"
+ android:viewportHeight="11.0"
+ android:viewportWidth="13.0">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M6.627,8.712c-0.404,0 -0.992,-0.063 -1.357,-0.185c-0.113,-0.039 -0.244,-0.01 -0.332,0.076L4.221,9.322C3.3,8.85 2.544,7.968 2.076,7.047l0.716,-0.718c0.091,-0.09 0.117,-0.218 0.081,-0.332c-0.12,-0.364 -0.186,-0.755 -0.186,-1.162c0,-0.181 -0.146,-0.326 -0.326,-0.326H1.223c-0.18,0 -0.326,0.146 -0.326,0.326c0,3.058 2.615,5.665 5.672,5.665c0.179,0 0.324,-0.146 0.324,-0.326V9.037C6.895,8.859 6.807,8.712 6.627,8.712zM10.857,0.5H4.212c-0.453,0 -0.683,0.379 -0.683,0.814v6.33c0,0.192 0.05,0.306 0.132,0.465C3.84,8.259 3.928,8.343 4.425,8.343l0.483,-0.483c0.188,-0.186 0.477,-0.254 0.725,-0.17C5.95,7.794 6.286,7.848 6.629,7.848c0.391,0 0.708,0.551 0.708,0.94v0.022h3.595c0.221,0 0.596,-0.19 0.596,-0.554V1.246C11.527,0.893 11.25,0.5 10.857,0.5zM4.561,7.166V1.514h5.972l0.001,5.652H4.561zM7.555,4.229c0.598,0 1.078,-0.48 1.078,-1.078c0,-0.599 -0.48,-1.137 -1.078,-1.137c-0.599,0 -1.08,0.538 -1.08,1.137C6.475,3.748 6.955,4.229 7.555,4.229zM8.178,4.877c-0.121,0 -1.02,-0.027 -1.186,-0.027c-0.506,0 -1.746,0.789 -1.746,0.899v0.76L9.87,6.492V5.746C9.869,5.624 8.504,4.877 8.178,4.877z" />
+</vector>
diff --git a/res/drawable/volte_voice.xml b/res/drawable/volte_voice.xml
new file mode 100644
index 0000000..1ce76fb
--- /dev/null
+++ b/res/drawable/volte_voice.xml
@@ -0,0 +1,36 @@
+<!--Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="13dp"
+ android:height="11dp"
+ android:autoMirrored="true"
+ android:viewportHeight="11.0"
+ android:viewportWidth="13.0">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M8.336,7.479c-0.574,0 -1.091,-0.093 -1.609,-0.264C6.564,7.158 6.379,7.199 6.254,7.326l-1.02,1.021C3.922,7.674 2.848,6.604 2.178,5.291L3.198,4.27c0.129,-0.13 0.167,-0.311 0.116,-0.475C3.143,3.276 3.05,2.586 3.05,2.006c0,-0.256 -0.209,-0.465 -0.464,-0.465H0.964C0.709,1.541 0.5,1.75 0.5,2.006c0,4.353 3.482,8.017 7.836,8.017c0.255,0 0.464,-0.21 0.464,-0.465V7.942C8.8,7.687 8.591,7.479 8.336,7.479zM5.453,4.054l0.689,-2.511H5.604l-0.402,1.82l-0.4,-1.82H4.266L4.95,4.054H5.453zM6.426,3.854c0.063,0.077 0.139,0.135 0.227,0.174c0.09,0.039 0.188,0.06 0.3,0.06c0.108,0 0.208,-0.021 0.296,-0.06c0.089,-0.039 0.165,-0.098 0.227,-0.174c0.063,-0.076 0.11,-0.171 0.145,-0.284c0.033,-0.113 0.05,-0.242 0.05,-0.389V3.059c0,-0.146 -0.019,-0.275 -0.052,-0.389C7.584,2.558 7.536,2.463 7.473,2.386C7.41,2.31 7.336,2.25 7.246,2.211c-0.088,-0.041 -0.188,-0.06 -0.298,-0.06c-0.109,0 -0.208,0.021 -0.296,0.06S6.488,2.31 6.426,2.386C6.364,2.463 6.316,2.558 6.282,2.67C6.248,2.783 6.23,2.912 6.23,3.059v0.123c0,0.146 0.018,0.277 0.052,0.389C6.316,3.684 6.364,3.777 6.426,3.854zM6.697,3.06c0,-0.17 0.021,-0.297 0.066,-0.378C6.809,2.6 6.87,2.558 6.949,2.558c0.082,0 0.145,0.042 0.189,0.124S7.205,2.89 7.205,3.06v0.123c0,0.173 -0.021,0.3 -0.063,0.38c-0.043,0.08 -0.106,0.12 -0.191,0.12c-0.08,0 -0.143,-0.041 -0.188,-0.12c-0.046,-0.08 -0.068,-0.207 -0.068,-0.38V3.06H6.697zM8.479,1.541H7.997v2.512h1.329v-0.42H8.48L8.479,1.541L8.479,1.541zM9.178,1.965h0.58v2.089h0.484V1.965h0.59V1.543H9.177L9.178,1.965L9.178,1.965zM11.609,3.633V2.967h0.75V2.558h-0.75V1.965h0.886V1.543h-1.368v2.511h1.372V3.633H11.609L11.609,3.633z" />
+</vector>
diff --git a/res/drawable/wifi_calling.xml b/res/drawable/wifi_calling.xml
new file mode 100644
index 0000000..87849c5
--- /dev/null
+++ b/res/drawable/wifi_calling.xml
@@ -0,0 +1,36 @@
+<!--Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="13dp"
+ android:height="11dp"
+ android:autoMirrored="true"
+ android:viewportHeight="11.0"
+ android:viewportWidth="13.75">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M8.608,7.242c-0.598,0 -1.223,-0.095 -1.762,-0.272C6.679,6.912 6.485,6.955 6.354,7.084L5.294,8.146C3.931,7.447 2.813,6.332 2.118,4.97l1.061,-1.062C3.313,3.774 3.352,3.586 3.3,3.416C3.122,2.877 3.024,2.299 3.024,1.697c0,-0.266 -0.217,-0.482 -0.48,-0.482H0.856c-0.266,0 -0.48,0.217 -0.48,0.482c0,4.525 3.707,8.193 8.233,8.193c0.265,0 0.481,-0.217 0.481,-0.482V7.727C9.089,7.459 8.874,7.242 8.608,7.242zM5.136,3.783l0.705,-2.574H5.29L4.878,3.076L4.466,1.209H3.919L4.62,3.783H5.136zM6.134,3.58c0.063,0.078 0.142,0.139 0.232,0.179c0.09,0.04 0.191,0.06 0.305,0.06s0.215,-0.02 0.305,-0.06c0.091,-0.04 0.168,-0.101 0.232,-0.179C7.271,3.5 7.319,3.404 7.354,3.289C7.389,3.174 7.405,3.041 7.405,2.89V2.765c0,-0.149 -0.018,-0.282 -0.052,-0.397C7.318,2.252 7.27,2.154 7.206,2.076c-0.063,-0.08 -0.143,-0.14 -0.232,-0.181C6.882,1.854 6.78,1.834 6.667,1.834c-0.111,0 -0.213,0.021 -0.304,0.062c-0.09,0.041 -0.167,0.101 -0.231,0.181C6.069,2.154 6.02,2.252 5.984,2.367C5.95,2.483 5.933,2.615 5.933,2.765V2.89c0,0.151 0.018,0.284 0.052,0.399C6.02,3.404 6.069,3.502 6.134,3.58zM6.411,2.766c0,-0.174 0.021,-0.305 0.067,-0.389s0.108,-0.125 0.188,-0.125c0.084,0 0.149,0.041 0.195,0.125c0.047,0.084 0.068,0.215 0.068,0.389v0.125c0,0.178 -0.021,0.309 -0.064,0.391C6.822,3.363 6.757,3.404 6.671,3.404c-0.082,0 -0.146,-0.041 -0.192,-0.123S6.409,3.068 6.409,2.891V2.766H6.411zM9.333,2.828L9.046,1.211H8.63L8.344,2.826L8.112,1.211H7.62l0.438,2.574h0.497l0.283,-1.518l0.285,1.518H9.62l0.437,-2.574h-0.49L9.333,2.828zM10.764,1.185c-0.022,-0.022 -0.05,-0.042 -0.081,-0.054C10.65,1.117 10.614,1.11 10.575,1.11s-0.074,0.007 -0.105,0.021c-0.031,0.013 -0.061,0.031 -0.081,0.054c-0.022,0.024 -0.041,0.052 -0.054,0.084s-0.019,0.067 -0.019,0.106s0.006,0.076 0.019,0.107c0.013,0.032 0.029,0.061 0.054,0.085c0.021,0.023 0.049,0.042 0.081,0.056c0.031,0.013 0.066,0.02 0.105,0.02s0.075,-0.006 0.107,-0.02c0.031,-0.014 0.059,-0.032 0.081,-0.056c0.022,-0.024 0.04,-0.053 0.052,-0.085c0.014,-0.033 0.02,-0.068 0.02,-0.107s-0.006,-0.074 -0.02,-0.106C10.804,1.236 10.786,1.209 10.764,1.185zM10.339,3.783h0.478V1.871h-0.478V3.783zM11.226,3.783h0.493v-1.05h0.769V2.303h-0.769v-0.66h0.862V1.209h-1.355V3.783zM13.356,1.269c-0.013,-0.033 -0.029,-0.062 -0.053,-0.084c-0.021,-0.022 -0.049,-0.042 -0.081,-0.054C13.19,1.117 13.155,1.11 13.116,1.11S13.04,1.117 13.01,1.131c-0.031,0.013 -0.06,0.031 -0.081,0.054c-0.021,0.024 -0.04,0.052 -0.053,0.084c-0.013,0.033 -0.019,0.067 -0.019,0.106s0.006,0.076 0.019,0.107c0.013,0.032 0.029,0.061 0.053,0.085c0.021,0.023 0.05,0.042 0.081,0.056c0.032,0.013 0.067,0.02 0.106,0.02s0.075,-0.006 0.106,-0.02c0.032,-0.014 0.06,-0.032 0.081,-0.056c0.021,-0.024 0.04,-0.053 0.053,-0.085c0.012,-0.033 0.018,-0.068 0.018,-0.107S13.368,1.302 13.356,1.269zM12.878,3.783h0.479V1.871h-0.479V3.783z" />
+</vector>
diff --git a/res/layout-land/dialpad_fragment.xml b/res/layout-land/dialpad_fragment.xml
index 70a38ae..0639899 100644
--- a/res/layout-land/dialpad_fragment.xml
+++ b/res/layout-land/dialpad_fragment.xml
@@ -81,6 +81,17 @@
</FrameLayout>
+ <TextView
+ android:id="@+id/dialpad_floating_operator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_above="@id/dialpad_floating_action_button_margin_bottom"
+ android:layout_toRightOf="@+id/dialpad_floating_action_button_container"
+ android:textColor="?attr/call_log_secondary_text_color"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:visibility="gone" />
+
</RelativeLayout>
</LinearLayout>
diff --git a/res/layout/add_speed_dial_dialog.xml b/res/layout/add_speed_dial_dialog.xml
new file mode 100644
index 0000000..7b8296f
--- /dev/null
+++ b/res/layout/add_speed_dial_dialog.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0"
+ android:gravity="center_vertical"
+ android:baselineAligned="false"
+ android:paddingStart="10dip"
+ android:paddingEnd="10dip">
+
+ <EditText
+ android:id="@+id/edit_container"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:orientation="vertical"
+ android:hint="@string/input_number"
+ android:inputType="phone"
+ android:singleLine="true" />
+
+ <ImageButton
+ android:id="@+id/select_contact"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dip"
+ android:src="@drawable/ic_person_24dp" />
+ </LinearLayout>
+
+ <View
+ android:background="?android:dividerHorizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip" />
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" >
+
+ <Button
+ android:id="@+id/btn_cancel"
+ android:focusable="true"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:text="@string/speed_dial_cancel"
+ style="?android:attr/buttonBarButtonStyle" />
+
+ <View
+ android:background="?android:dividerHorizontal"
+ android:layout_width="1dip"
+ android:layout_height="fill_parent" />
+
+ <Button
+ android:id="@+id/btn_complete"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:text="@string/speed_dial_ok"
+ style="?android:attr/buttonBarButtonStyle" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index 660bca3..c8a33c5 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -101,6 +101,14 @@
android:layout_marginEnd="@dimen/call_log_icon_margin"
android:layout_gravity="center_vertical" />
+ <ImageView
+ android:id="@+id/call_account_icon"
+ android:layout_width="@dimen/call_provider_small_icon_size"
+ android:layout_height="@dimen/call_provider_small_icon_size"
+ android:layout_marginEnd="@dimen/call_log_icon_margin"
+ android:layout_gravity="center_vertical"
+ android:scaleType="centerInside" />
+
<ImageView android:id="@+id/work_profile_icon"
android:src="@drawable/ic_work_profile"
android:layout_width="wrap_content"
diff --git a/res/layout/call_log_list_item_actions.xml b/res/layout/call_log_list_item_actions.xml
index 78203b7..4cce073 100644
--- a/res/layout/call_log_list_item_actions.xml
+++ b/res/layout/call_log_list_item_actions.xml
@@ -64,6 +64,7 @@
style="@style/CallLogActionStyle">
<ImageView
+ android:id="@+id/videoCallIcon"
style="@style/CallLogActionIconStyle"
android:src="@drawable/ic_videocam_24dp" />
diff --git a/res/layout/dialpad_fragment.xml b/res/layout/dialpad_fragment.xml
index 21cb586..5748f5d 100644
--- a/res/layout/dialpad_fragment.xml
+++ b/res/layout/dialpad_fragment.xml
@@ -73,4 +73,14 @@
</FrameLayout>
+ <TextView
+ android:id="@+id/dialpad_floating_operator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/dialpad_floating_action_button_container"
+ android:layout_centerHorizontal="true"
+ android:textColor="?attr/call_log_secondary_text_color"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:visibility="gone" />
</view>
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
index 782d4f3..70d0561 100644
--- a/res/layout/dialtacts_activity.xml
+++ b/res/layout/dialtacts_activity.xml
@@ -38,25 +38,35 @@
android:clipChildren="false" />
</FrameLayout>
- <FrameLayout
+ <LinearLayout
android:id="@+id/floating_action_button_container"
android:background="@drawable/fab_blue"
- android:layout_width="@dimen/floating_action_button_width"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/floating_action_button_height"
android:layout_marginBottom="@dimen/floating_action_button_margin_bottom"
- android:layout_gravity="center_horizontal|bottom"
- app:layout_behavior="com.android.dialer.FloatingActionButtonBehavior">
+ android:layout_gravity="center_horizontal|bottom">
+ <!-- app:layout_behavior="com.android.dialer.FloatingActionButtonBehavior" -->
<ImageButton
android:id="@+id/floating_action_button"
- android:background="@drawable/floating_action_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_gravity="bottom|left"
+ android:layout_weight="1"
+ android:background="@drawable/floating_action_button"
android:contentDescription="@string/action_menu_dialpad_button"
android:src="@drawable/fab_ic_dial"/>
-
- </FrameLayout>
-
+ <ImageButton
+ android:id="@+id/dialConferenceButton"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="bottom|right"
+ android:layout_weight="1"
+ android:visibility="gone"
+ android:background="@drawable/floating_action_button"
+ android:contentDescription="@string/action_menu_dialpad_button"
+ android:src="@drawable/ic_add_group_holo_dark"/>
+ </LinearLayout>
<!-- Host container for the contact tile drag shadow -->
<FrameLayout
android:id="@+id/activity_overlay"
diff --git a/res/layout/empty_content_view.xml b/res/layout/empty_content_view.xml
index 97ac4c7..9436d67 100644
--- a/res/layout/empty_content_view.xml
+++ b/res/layout/empty_content_view.xml
@@ -26,8 +26,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal|top"
- android:textSize="@dimen/empty_list_message_text_size"
- android:textColor="@color/empty_list_text_color"
+ android:textSize="@dimen/empty_list_message_text_size_for_Marshmallow"
+ android:textColor="@color/no_call_log"
android:paddingRight="16dp"
android:paddingLeft="16dp"
android:paddingTop="8dp"
diff --git a/res/layout/msim_call_log_activity.xml b/res/layout/msim_call_log_activity.xml
new file mode 100644
index 0000000..bca6bf4
--- /dev/null
+++ b/res/layout/msim_call_log_activity.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/calllog_frame">
+ <android.support.v4.view.ViewPager
+ android:id="@+id/call_log_pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/res/layout/msim_call_log_fragment.xml b/res/layout/msim_call_log_fragment.xml
new file mode 100644
index 0000000..6ee584b
--- /dev/null
+++ b/res/layout/msim_call_log_fragment.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<!-- Layout parameters are set programmatically. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@color/background_dialer_call_log">
+
+ <include layout="@layout/msim_call_log_spinner" />
+
+ <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/background_dialer_call_log"
+ android:paddingStart="@dimen/call_log_horizontal_margin"
+ android:paddingEnd="@dimen/call_log_horizontal_margin" />
+
+ <com.android.dialer.widget.EmptyContentView
+ android:id="@+id/empty_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:visibility="gone" />
+
+</LinearLayout>
diff --git a/res/layout/msim_call_log_spinner.xml b/res/layout/msim_call_log_spinner.xml
new file mode 100644
index 0000000..5310ace
--- /dev/null
+++ b/res/layout/msim_call_log_spinner.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingLeft="@dimen/call_log_outer_margin"
+ android:paddingRight="@dimen/call_log_outer_margin">
+ <Spinner
+ android:id="@+id/filter_sub_spinner"
+ android:layout_width="0dip"
+ android:textSize="@dimen/call_log_spinner_text_size"
+ android:textColor="@color/list_all_call"
+ android:layout_height="@dimen/list_section_divider_min_height"
+ android:layout_weight="1"
+ android:layout_marginTop="5dip" />
+ <Spinner
+ android:id="@+id/filter_status_spinner"
+ android:layout_width="0dip"
+ android:textSize="@dimen/call_log_spinner_text_size"
+ android:textColor="@color/list_all_call"
+ android:layout_height="@dimen/list_section_divider_min_height"
+ android:layout_weight="2"
+ android:layout_marginTop="5dip" />
+</LinearLayout>
diff --git a/res/layout/msim_call_log_spinner_item.xml b/res/layout/msim_call_log_spinner_item.xml
new file mode 100644
index 0000000..0b1bcf3
--- /dev/null
+++ b/res/layout/msim_call_log_spinner_item.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/CallLogSpinnerStyle"
+ android:layout_width="match_parent"
+ android:paddingTop="5dp"
+ android:layout_height="50dip"
+ android:paddingLeft="16dip"
+ android:paddingRight="12dip"
+ android:paddingBottom="8dp" />
diff --git a/res/layout/search_action_bar.xml b/res/layout/search_action_bar.xml
new file mode 100644
index 0000000..2953d72
--- /dev/null
+++ b/res/layout/search_action_bar.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dip"
+ android:orientation="horizontal"
+ android:layout_height="0dip" >
+
+ <EditText
+ android:layout_weight="1"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:layout_width="0dp"
+ android:singleLine="true"
+ android:id="@+id/search_view"
+ android:background="@null"
+ android:textCursorDrawable="@drawable/color_cursor"
+ android:layout_height="match_parent"
+ android:hint="@string/calllog_search_hint"
+ android:cursorVisible="true"
+ android:textColorHint="@color/searchview_edittext"
+ android:textColor="@color/actionbar_icon_color"
+ android:inputType="textFilter" />
+ <ImageView
+ android:id="@+id/search_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/clear"
+ android:clickable="true"
+ android:contentDescription="@string/description_clear_search"
+ />
+</LinearLayout>
diff --git a/res/layout/speed_dial_item.xml b/res/layout/speed_dial_item.xml
new file mode 100644
index 0000000..948d2e7
--- /dev/null
+++ b/res/layout/speed_dial_item.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The CyanogenMod 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:paddingStart="16dip"
+ android:paddingEnd="16dip"
+ android:orientation="horizontal"
+ android:minHeight="?android:attr/listPreferredItemHeight">
+
+ <TextView
+ android:id="@+id/index"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="12dp"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="?attr/call_log_primary_text_color"
+ android:textSize="@dimen/call_log_primary_text_size" />
+
+ <TextView
+ android:id="@+id/number"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/call_log_name_margin_bottom"
+ android:textColor="?attr/call_log_secondary_text_color"
+ android:textSize="@dimen/call_log_secondary_text_size" />
+
+ </LinearLayout>
+
+ <QuickContactBadge
+ android:id="@+id/photo"
+ android:layout_width="@dimen/contact_photo_size"
+ android:layout_height="@dimen/contact_photo_size"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="8dp" />
+
+</LinearLayout>
+
diff --git a/res/layout/video_call_welcome.xml b/res/layout/video_call_welcome.xml
new file mode 100644
index 0000000..1a9af34
--- /dev/null
+++ b/res/layout/video_call_welcome.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="16dip"
+ android:paddingEnd="16dip"
+ android:paddingTop="8dip"
+ android:paddingBottom="16dip"
+ android:orientation="vertical"
+ android:keepScreenOn="true">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/video_call_welcome_title"
+ android:paddingBottom="10dip"
+ style="?android:attr/textAppearanceMedium" />
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/video_call_welcome_message"
+ android:paddingBottom="10dip"
+ style="?android:attr/textAppearanceSmall" />
+ </ScrollView>
+ <CheckBox
+ android:id="@android:id/checkbox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/video_call_welcome_message_repeat" />
+</LinearLayout>
diff --git a/res/menu/call_log_options.xml b/res/menu/call_log_options.xml
index da38d86..6570714 100644
--- a/res/menu/call_log_options.xml
+++ b/res/menu/call_log_options.xml
@@ -15,6 +15,12 @@
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
+ android:id="@+id/search_calllog"
+ android:title="@string/calllog_search_hint"
+ android:showAsAction="never"
+ android:orderInCategory="1"/>
+ <item
+ android:layout_height="58dp"
android:id="@+id/delete_all"
android:title="@string/call_log_delete_all"
android:showAsAction="never"
diff --git a/res/menu/dialpad_options.xml b/res/menu/dialpad_options.xml
index 63fca07..00c0f1e 100644
--- a/res/menu/dialpad_options.xml
+++ b/res/menu/dialpad_options.xml
@@ -16,6 +16,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
+ android:id="@+id/menu_add_to_4g_conference_call"
+ android:title="@string/menu_add_to_4g_conference_call"
+ android:showAsAction="withText" />
+ <item
android:id="@+id/menu_2s_pause"
android:title="@string/add_2sec_pause"
android:showAsAction="withText" />
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index 0f068f5..a5ca720 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -16,6 +16,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
+ android:id="@+id/menu_4g_conference_call"
+ android:title="@string/menu_4g_conference_call" />
+ <item
android:id="@+id/menu_history"
android:icon="@drawable/ic_menu_history_lt"
android:title="@string/action_menu_call_history_description" />
diff --git a/res/values-zh-rCN/qtistrings.xml b/res/values-zh-rCN/qtistrings.xml
new file mode 100644
index 0000000..25f18c0
--- /dev/null
+++ b/res/values-zh-rCN/qtistrings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2015-2016 The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ~
+ -->
+
+<!-- The xml contains Qti specific resource strings neede for any value added features. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="video_call_upgrade">不能升级!</string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 200b6fe..ca28569 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -110,6 +110,9 @@
<string name="type_incoming_video" msgid="82323391702796181">"视频通话来电"</string>
<string name="type_outgoing_video" msgid="2858140021680755266">"拨出的视频通话"</string>
<string name="type_missed_video" msgid="954396897034220545">"错过的视频通话"</string>
+ <string name="type_incoming_volte">4G语音通话来电</string>
+ <string name="type_outgoing_volte">外拨4G语音通话</string>
+ <string name="type_missed_volte">未接4G语音通话</string>
<string name="type_voicemail" msgid="5153139450668549908">"语音信箱"</string>
<string name="type_rejected" msgid="7783201828312472691">"拒接的来电"</string>
<string name="type_blocked" msgid="3521686227115330015">"屏蔽的来电"</string>
@@ -130,6 +133,13 @@
<string name="payphone" msgid="7726415831153618726">"公用电话"</string>
<string name="callDetailsShortDurationFormat" msgid="3988146235579303592">"<xliff:g id="SECONDS">%s</xliff:g> 秒"</string>
<string name="callDetailsDurationFormat" msgid="6061406028764382234">"<xliff:g id="MINUTES">%s</xliff:g> 分钟 <xliff:g id="SECONDS">%s</xliff:g> 秒"</string>
+
+ <!-- Menu items for dialpad options as part of Pause and Wait ftr [CHAR LIMIT=30] -->
+ <string name="menu_add_to_4g_conference_call">"加入 4G 电话会议"</string>
+ <string name="menu_4g_conference_call">"4G 电话会议"</string>
+ <string name="add_2sec_pause">"延长暂停时间2秒"</string>
+ <string name="add_wait">"延长等待时间"</string>
+
<!-- no translation found for voicemailCallLogToday (682363079840402849) -->
<skip />
<string name="voicemailCallLogDateTimeFormat" msgid="4388070029056487713">"<xliff:g id="DATE">%1$s</xliff:g><xliff:g id="TIME">%2$s</xliff:g>"</string>
@@ -271,4 +281,82 @@
<string name="toast_cannot_write_system_settings" msgid="5614246168296606709">"电话应用不具备写入系统设置的权限。"</string>
<string name="blocked_number_call_log_label" msgid="8912042441473014712">"已屏蔽"</string>
<string name="accessibility_call_is_active" msgid="2297282583928508760">"正在与<xliff:g id="NAMEORNUMBER">^1</xliff:g>通话"</string>
+ <!-- for speed dial -->
+ <string name="speed_dial_settings">快速拨号设置</string>
+ <string name="speed_dial_not_set">(未设置)</string>
+ <string name="speed_dial_replace">替换</string>
+ <string name="speed_dial_delete">删除</string>
+ <string name="speed_dial_unassigned_dialog_title">按键未分配</string>
+ <string name="speed_dial_unassigned_dialog_message">没有设置快速拨号,现在设置?</string>
+ <string name="dialog_speed_dial_airplane_mode_message">关飞行模式后使用快速拨号</string>
+ <string name="yes">是</string>
+ <string name="no">否</string>
+ <string name="input_number">"输入号码"</string>
+ <string name="speed_dial_cancel">"取消"</string>
+ <string name="speed_dial_ok">"确定"</string>
+ <string name="call_log_show_all_slots">"所有 SIM 卡"</string>
+ <string name="call_log_all_calls_header">"所有通话"</string>
+ <string name="calllog_search_hint">"搜索通话记录"</string>
+ <string name="no_call_log">没有通话记录</string>
+ <string name="clear">清除</string>
+ <string name="description_clear_search">清除搜索记录</string>
+ <string name="recentCalls_empty">"您没有任何通话记录"</string>
+ <!-- The description text for the call log tab.
+ Note: AccessibilityServices use this attribute to announce what the view represents.
+ This is especially valuable for views without textual representation like ImageView.
+ [CHAR LIMIT=NONE] -->
+ <string name="recentCallsIconLabel">"通话记录"</string>
+ <!-- Menu item used to call a contact from the call log -->
+ <string name="recentCalls_callNumber">"呼叫<xliff:g id="NAME">%s</xliff:g>"</string>
+ <!-- Text for a menu item to report a call as having been incorrectly identified.
+ [CHAR LIMIT=30] -->
+ <string name="call_detail_menu_report">"报告错误的号码"</string>
+ <!-- Menu item used to copy a number from the call log to the dialer so it can be edited before calling it -->
+ <string name="recentCalls_editNumberBeforeCall">"呼叫之前编辑号码"</string>
+ <!-- Menu item used to add a number from the call log to contacts -->
+ <string name="recentCalls_addToContact">"添加到联系人"</string>
+ <!-- Menu item used to remove a single call from the call log -->
+ <string name="recentCalls_removeFromRecentList">"从通话记录中删除"</string>
+ <!-- Menu item used to remove all calls from the call log -->
+ <string name="recentCalls_deleteAll">"清除通话记录"</string>
+ <!-- Menu item used to delete a voicemail. [CHAR LIMIT=30] -->
+ <string name="recentCalls_trashVoicemail">"删除语音邮件"</string>
+ <!-- Menu item used to share a voicemail. [CHAR LIMIT=30] -->
+ <string name="recentCalls_shareVoicemail">"分享语音邮件"</string>
+ <!-- Label of the button displayed when the call log is empty. Allows the user to make a call. -->
+ <string name="recentCalls_empty_action">"拨打电话"</string>
+ <!-- String resource for the font-family to use for the call log activity's title
+ Do not translate. -->
+ <string name="call_log_activity_title_font_family">sans-serif-light</string>
+ <!-- String resource for the font-family to use for the full call history footer
+ Do not translate. -->
+ <string name="view_full_call_history_font_family">sans-serif</string>
+ <!-- Text displayed when the list of incoming calls is empty -->
+ <string name="recentIncoming_empty">"您没有任何来电。"</string>
+ <!-- Text displayed when the list of outgoing calls is empty -->
+ <string name="recentOutgoing_empty">"您没有任何外拨电话。"</string>
+ <!-- Text displayed when the list of missed calls is empty -->
+ <string name="recentMissed_empty">"您没有任何未接电话。"</string>
+ <!-- Text displayed when the list of voicemails is empty -->
+ <string name="recentVoicemails_empty">"您未收到任何语音邮件。"</string>
+
+ <string name="add_to_white_list">"加入白名单"</string>
+ <string name="add_to_black_list">"加入黑名单"</string>
+ <string name="remove_from_black_list">"从黑名单中移除"</string>
+ <string name="remove_from_white_list">"从白名单中移除"</string>
+ <string name="firewall_remove_success">"已成功移除"</string>
+ <string name="yes">"是"</string>
+ <string name="no">"否"</string>
+ <string name="input_number">"输入号码"</string>
+ <string name="speed_dial_cancel">"取消"</string>
+ <string name="speed_dial_ok">"确定"</string>
+ <string name="call_data_info_label">"通话/流量计时器"</string>
+ <string name="call_data_info_description">"语音通话时长和流量使用时间"</string>
+ <string name="alert_call_over_wifi">"将通过 Wi-Fi 拨打电话。"</string>
+ <string name="alert_call_no_cellular_coverage">"移动网络不可用。请连接至可用的 Wi-Fi 拨打电话。"</string>
+ <string name="alert_user_connect_to_wifi_for_call">"请连接至 Wi-Fi 拨打电话。"</string>
+ <string name="video_call">"视频通话"</string>
+ <string name="video_call_welcome_title"><b>"欢迎使用全新的视频通话拨号器"</b></string>
+ <string name="video_call_welcome_message">"我们都希望在电话的另一端传来熟悉的声音,但打电话显然比不上面对面交流!下面介绍了如何获取实时(或 FaceTime?)连接: 需要 4G LTE 或 Wi-Fi 连接;被叫方设备必须也支持视频通话;视频通话使用高速数据;您也可以将语音通话升级到视频通话;访问 "<a>"https://support.t-mobile.com/docs/DOC-23574"</a>" 了解详细信息。"</string>
+ <string name="video_call_welcome_message_repeat">"每次都显示这条信息"</string>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 38fd6b3..8b1fa55 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -139,4 +139,8 @@
<color name="call_detail_footer_text_color">#616161</color>
<color name="call_detail_footer_icon_tint">@color/call_detail_footer_text_color</color>
+ <color name="searchview_edittext">#59ffffff</color>
+ <color name="no_call_log">#42000000</color>
+ <color name="list_all_call">#d3000000</color>
+ <bool name="config_regional_presence_enable">false</bool>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..b6632c1
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ /* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+-->
+<resources>
+ <bool name="config_regional_video_call_welcome_dialog">false</bool>
+ <bool name="config_regional_pup_no_available_network">false</bool>
+ <bool name="config_regional_call_data_usage_enable">false</bool>
+ <!--not display SIP dial icon -->
+ <bool name="config_hide_SIP_dial_icon">false</bool>
+
+ <integer name="speed_dial_emergency_number_assigned_key">9</integer>
+
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 371a1c6..d9c425d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -117,6 +117,7 @@
<dimen name="call_log_actions_top_padding">8dp</dimen>
<dimen name="call_log_actions_bottom_padding">8dp</dimen>
<dimen name="call_log_primary_text_size">16sp</dimen>
+ <dimen name="call_log_secondary_text_size">14sp</dimen>
<dimen name="call_log_detail_text_size">12sp</dimen>
<dimen name="call_log_day_group_heading_size">14sp</dimen>
<dimen name="call_log_voicemail_transcription_text_size">14sp</dimen>
@@ -173,4 +174,8 @@
<dimen name="call_type_icon_size">12dp</dimen>
<dimen name="tab_unread_count_margin_left">0dp</dimen>
+
+ <dimen name="list_all_calls">200dp</dimen>
+ <dimen name="empty_list_message_text_size_for_Marshmallow">16dp</dimen>
+ <dimen name="call_log_spinner_text_size">16sp</dimen>
</resources>
diff --git a/res/values/qtistrings.xml b/res/values/qtistrings.xml
new file mode 100644
index 0000000..a11e5e5
--- /dev/null
+++ b/res/values/qtistrings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<!-- The xml contains Qti specific resource strings neede for any value added features. -->
+<resources>
+ <!-- OEM Key strings -->
+ <string name="oem_key_code_action"></string>
+ <string name="oem_code"></string>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cb85684..ce088cd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -314,6 +314,8 @@
<string name="menu_show_all_calls">Show all calls</string>
<!-- Menu items for dialpad options as part of Pause and Wait ftr [CHAR LIMIT=30] -->
+ <string name="menu_add_to_4g_conference_call">Add to 4G conference call</string>
+ <string name="menu_4g_conference_call">4G conference call</string>
<string name="add_2sec_pause">Add 2-sec pause</string>
<string name="add_wait">Add wait</string>
@@ -365,6 +367,42 @@
<!-- Title for missed video call in call details screen [CHAR LIMIT=60] -->
<string name="type_missed_video">Missed video call</string>
+ <!-- Title for incoming 4G voice call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_incoming_volte">Incoming 4G voice call</string>
+
+ <!-- Title for incoming 4G video call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_incoming_video_lte">Incoming 4G video call</string>
+
+ <!-- Title for outgoing 4G voice call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_outgoing_volte">Outgoing 4G voice call</string>
+
+ <!-- Title for incoming 4G video call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_outgoing_video_lte">Outgoing 4G video call</string>
+
+ <!-- Title for missed 4G voice call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_missed_volte">Missed 4G voice call</string>
+
+ <!-- Title for missed 4G video call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_missed_video_lte">Missed 4G video call</string>
+
+ <!-- Title for incoming wifi voice call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_incoming_vowifi">Incoming WiFi voice call</string>
+
+ <!-- Title for incoming wifi video call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_incoming_video_wifi">Incoming WiFi video call</string>
+
+ <!-- Title for outgoing wifi voice call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_outgoing_vowifi">Outgoing WiFi voice call</string>
+
+ <!-- Title for outgoing wifi video call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_outgoing_video_wifi">Outgoing WiFi video call</string>
+
+ <!-- Title for missed wifi voice call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_missed_vowifi">Missed WiFi voice call</string>
+
+ <!-- Title for missed wifi video call in call details screen [CHAR LIMIT=60] -->
+ <string name="type_missed_video_wifi">Missed WiFi video call</string>
+
<!-- Title for voicemail details screen -->
<string name="type_voicemail">Voicemail</string>
@@ -1063,4 +1101,70 @@
<!-- Accessibility announcement to indicate which call is active -->
<string name="accessibility_call_is_active"><xliff:g id="nameOrNumber">^1</xliff:g> is active</string>
+
+ <!-- for speed dial -->
+ <string name="speed_dial_settings">Speed dial settings</string>
+
+ <string name="speed_dial_not_set">(not set)</string>
+
+ <string name="speed_dial_replace">Replace</string>
+
+ <string name="speed_dial_delete">Delete</string>
+
+ <string name="speed_dial_unassigned_dialog_title">Key unassigned</string>
+
+ <string name="speed_dial_unassigned_dialog_message">No speed dial action is assigned to number k
+ey \'<xliff:g id="number">%s</xliff:g>\'. Do you want to assign an action now?</string>
+
+ <string name="dialog_speed_dial_airplane_mode_message">To use speed dial, first turn off Airplan
+e mode.</string>
+
+ <!-- Speed Dial can not be set for the key used for Emergency number-->
+ <string name="speed_dial_can_not_be_set">Speed Dial Can not be set for this Key</string>
+
+ <string name="yes">Yes</string>
+
+ <string name="no">No</string>
+
+ <string name="input_number">Input Number</string>
+
+ <string name="speed_dial_cancel">Cancel</string>
+
+ <string name="speed_dial_ok">OK</string>
+
+ <string name="call_log_show_all_slots">All SIMs</string>
+
+ <string name="call_log_all_calls_header">All calls</string>
+
+ <!-- Text displayed when the list of incoming calls is empty -->
+ <string name="recentIncoming_empty">You have no incoming calls.</string>
+
+ <!-- Text displayed when the list of outgoing calls is empty -->
+ <string name="recentOutgoing_empty">You have no outgoing calls.</string>
+
+ <!-- Text displayed when the call log is empty. -->
+ <string name="recentCalls_empty">Your call log is empty</string>
+ <string name="calllog_search_hint">"Search call log"</string>
+ <string name="no_call_log">No call log</string>
+ <string name="clear">Clear</string>
+ <string name="description_clear_search">Clear search</string>
+ <string name="video_call_welcome_title"><b>Welcome to the new Video Calling Dialer</b></string>
+ <string name="video_call_welcome_message">We all love to hear that familiar voice at the end of the line, but talking face-to-face is so much better! Here\'s what you should know to get your real-time (or FaceTime?) connections going: You\'ll need to be on 4G LTE or a Wi-Fi connection The device you\'re calling must also be video calling compatible Video calls use your high speed data You can also upgrade a voice call to a video call Check out <a>https://support.t-mobile.com/docs/DOC-23574</a> for more information.</string>
+ <string name="video_call_welcome_message_repeat">Show this message every time</string>
+
+ <string name="toast_change_video_call_failed">Can not change to video call, incorrect number format</string>
+ <string name="toast_make_video_call_failed">Can not make video call, incorrect number format</string>
+ <!-- Add for enhance videocall ui -->
+ <string name="overflowBothCallMenuItemText">Video Call, show me</string>
+ <string name="overflowRXCallMenuItemText">Video Call, hide me</string>
+ <string name="overflowVOCallMenuItemText">Voice Call</string>
+ <string name="video_call">Video Calling</string>
+ <string name="call_data_info_label">Call/data Usage Timers</string>
+ <string name="call_data_info_description">Voice call usage time and data usage time</string>
+ <string name="alert_call_over_wifi">Calls will be made over Wi-Fi.</string>
+ <string name="alert_call_no_cellular_coverage">No cellular network available. Connect to available Wi-Fi to make calls.</string>
+ <string name="alert_user_connect_to_wifi_for_call">Connect to Wi-Fi to make calls</string>
+ <string name="alert_user_connect_to_wifi_for_call_text">Tap here to view available networks</string>
+ <string name="missing_account_type">(No type)</string>
+ <string name="missing_account_name">(No name)</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 6a40d09..dbe3244 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -343,4 +343,110 @@
<item name="android:layout_height">1dp</item>
<item name="android:background">?android:attr/listDivider</item>
</style>
+
+ <style name="SpeedDialtactsTheme" parent="android:Theme.Material.Light">
+ <item name="android:textColorPrimary">@color/dialtacts_primary_text_color</item>
+ <item name="android:textColorSecondary">@color/dialtacts_secondary_text_color</item>
+
+ <!-- Styles that require AppCompat compatibility, remember to update both sets -->
+ <item name="android:windowActionBarOverlay">true</item>
+ <item name="windowActionBarOverlay">true</item>
+ <item name="android:windowActionModeOverlay">true</item>
+ <item name="windowActionModeOverlay">true</item>
+ <item name="android:actionBarStyle">@style/SpeedDialtactsActionBarStyle</item>
+ <item name="actionBarStyle">@style/SpeedDialtactsActionBarStyle</item>
+ <!-- Style for the overflow button in the actionbar. -->
+ <item name="android:actionOverflowButtonStyle">@style/DialtactsActionBarOverflow</item>
+ <item name="actionOverflowButtonStyle">@style/DialtactsActionBarOverflow</item>
+
+ <!-- Drawable for the back button -->
+ <item name="android:homeAsUpIndicator">@drawable/ic_back_arrow</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:listViewStyle">@style/ListViewStyle</item>
+ <item name="android:overlapAnchor">true</item>
+ <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
+ <item name="activated_background">@drawable/list_item_activated_background</item>
+ <item name="section_header_background">@drawable/list_title_holo</item>
+ <item name="list_section_header_height">32dip</item>
+ <item name="list_item_padding_top">12dp</item>
+ <item name="list_item_padding_right">24dp</item>
+ <item name="list_item_padding_bottom">12dp</item>
+ <item name="list_item_padding_left">16dp</item>
+ <item name="list_item_gap_between_image_and_text">
+ @dimen/contact_browser_list_item_gap_between_image_and_text
+ </item>
+ <item name="list_item_gap_between_label_and_data">8dip</item>
+ <item name="list_item_presence_icon_margin">4dip</item>
+ <item name="list_item_presence_icon_size">16dip</item>
+ <item name="list_item_photo_size">@dimen/contact_browser_list_item_photo_size</item>
+ <item name="list_item_profile_photo_size">70dip</item>
+ <item name="list_item_prefix_highlight_color">@color/people_app_theme_color</item>
+ <item name="list_item_background_color">@color/background_dialer_light</item>
+ <item name="list_item_header_text_indent">8dip</item>
+ <item name="list_item_header_text_color">@color/dialtacts_secondary_text_color</item>
+ <item name="list_item_header_text_size">14sp</item>
+ <item name="list_item_header_height">30dip</item>
+ <item name="list_item_data_width_weight">5</item>
+ <item name="list_item_label_width_weight">3</item>
+ <item name="contact_browser_list_padding_left">0dp</item>
+ <item name="contact_browser_list_padding_right">0dp</item>
+ <item name="contact_browser_background">@color/background_dialer_results</item>
+ <item name="list_item_name_text_color">@color/contact_list_name_text_color</item>
+ <item name="list_item_name_text_size">16sp</item>
+ <item name="list_item_text_indent">@dimen/contact_browser_list_item_text_indent</item>
+ <item name="list_item_text_offset_top">-2dp</item>
+ <!-- CallLog -->
+ <item name="call_log_primary_text_color">@color/dialtacts_primary_text_color</item>
+ <item name="call_log_primary_background_color">#000000</item>
+ <item name="call_log_secondary_text_color">@color/dialtacts_secondary_text_color</item>
+ <item name="call_log_secondary_background_color">#333333</item>
+ <item name="call_log_header_color">#33b5e5</item>
+ <!-- VoicemailStatus -->
+ <item name="call_log_voicemail_status_height">48dip</item>
+ <item name="call_log_voicemail_status_background_color">#262626</item>
+ <item name="call_log_voicemail_status_text_color">#888888</item>
+ <item name="call_log_voicemail_status_action_text_color">#33b5e5</item>
+ <!-- Favorites -->
+ <item name="favorites_padding_bottom">?android:attr/actionBarSize</item>
+ <item name="android:colorPrimary">@color/dialer_theme_color</item>
+ <item name="android:colorPrimaryDark">@color/dialer_theme_color_dark</item>
+ <item name="dialpad_key_button_touch_tint">@color/dialer_dialpad_touch_tint</item>
+ <item name="android:colorControlActivated">@color/dialer_theme_color</item>
+ <item name="android:colorButtonNormal">@color/dialer_theme_color</item>
+ <item name="android:textAppearanceButton">@style/DialerButtonTextStyle</item>
+
+ <!-- Video call icon -->
+ <item name="list_item_video_call_icon_size">32dip</item>
+ <item name="list_item_video_call_icon_margin">8dip</item>
+ </style>
+
+ <style name="SpeedDialtactsActionBarStyle"
+ parent="android:Widget.Material.ActionBar">
+ <!-- Styles that require AppCompat compatibility, remember to update both sets -->
+ <item name="android:background">@color/actionbar_background_color</item>
+ <item name="background">@color/actionbar_background_color</item>
+ <item name="android:titleTextStyle">@style/DialtactsActionBarTitleText</item>
+ <item name="titleTextStyle">@style/DialtactsActionBarTitleText</item>
+ <item name="android:height">@dimen/action_bar_height</item>
+ <item name="height">@dimen/action_bar_height</item>
+ <item name="android:elevation">@dimen/action_bar_elevation</item>
+ <item name="elevation">@dimen/action_bar_elevation</item>
+ <!-- Empty icon -->
+ <item name="android:icon">@android:color/transparent</item>
+ <item name="icon">@android:color/transparent</item>
+ <!-- Shift the title text to the right -->
+ <item name="android:contentInsetStart">@dimen/actionbar_contentInsetStart</item>
+ <item name="contentInsetStart">@dimen/actionbar_contentInsetStart</item>
+ </style>
+
+ <style name="CallLogSpinnerStyle">
+ <item name="android:textSize">@dimen/call_log_spinner_text_size</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:ellipsize">marquee</item>
+ <item name="android:singleLine">true</item>
+ </style>
+ <style name="NOCallLOG">
+ <item name="android:textStyle">bold</item>
+ <item name="android:textColor">#000000</item>
+ </style>
</resources>
diff --git a/res/values/symbols.xml b/res/values/symbols.xml
new file mode 100644
index 0000000..2b1c756
--- /dev/null
+++ b/res/values/symbols.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ ~
+ ~ Redistribution and use in source and binary forms, with or without
+ ~ modification, are permitted provided that the following conditions are
+ ~ met:
+ ~ * Redistributions of source code must retain the above copyright
+ ~ notice, this list of conditions and the following disclaimer.
+ ~ * Redistributions in binary form must reproduce the above
+ ~ copyright notice, this list of conditions and the following
+ ~ disclaimer in the documentation and/or other materials provided
+ ~ with the distribution.
+ ~ * Neither the name of The Linux Foundation nor the names of its
+ ~ contributors may be used to endorse or promote products derived
+ ~ from this software without specific prior written permission.
+ ~
+ ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ~ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <java-symbol type="bool" name="config_regional_number_patterns_video_call" />
+</resources>
diff --git a/res/xml/video_calling_settings.xml b/res/xml/video_calling_settings.xml
new file mode 100644
index 0000000..65e1da6
--- /dev/null
+++ b/res/xml/video_calling_settings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <SwitchPreference
+ android:key="video_calling_preference"
+ android:persistent="false"
+ android:title="@string/video_call" />
+
+</PreferenceScreen>
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index 94c2f00..db4abe7 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -283,10 +283,12 @@
if (TextUtils.isEmpty(mNumber)) {
return;
}
- mContext.startActivity(
- new CallIntentBuilder(getDialableNumber())
- .setCallInitiationType(LogState.INITIATION_CALL_DETAILS)
- .build());
+ Intent dialIntent = new CallIntentBuilder(getDialableNumber())
+ .setCallInitiationType(LogState.INITIATION_CALL_DETAILS).build();
+ if (DialerUtils.isConferenceURICallLog(mNumber, mPostDialDigits)) {
+ dialIntent.putExtra("org.codeaurora.extra.DIAL_CONFERENCE_URI", true);
+ }
+ mContext.startActivity(dialIntent);
}
});
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index d063fef..2ae567f 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -25,6 +25,7 @@
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.Manifest;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
@@ -32,6 +33,7 @@
import android.speech.RecognizerIntent;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewPager;
+import android.support.v4.app.ActivityCompat;
import android.support.v7.app.ActionBar;
import android.telecom.PhoneAccount;
import android.text.Editable;
@@ -56,6 +58,7 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.android.contacts.common.CallUtil;
import com.android.contacts.common.dialog.ClearFrequentsDialog;
import com.android.contacts.common.interactions.ImportExportDialogFragment;
import com.android.contacts.common.interactions.TouchPointManager;
@@ -85,15 +88,19 @@
import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.IntentUtil;
import com.android.dialer.util.IntentUtil.CallIntentBuilder;
+import com.android.dialer.util.PresenceHelper;
import com.android.dialer.util.TelecomUtil;
+import com.android.dialer.util.WifiCallUtils;
import com.android.dialer.voicemail.VoicemailArchiveActivity;
import com.android.dialer.widget.ActionBarController;
import com.android.dialer.widget.SearchEditTextLayout;
import com.android.dialerbind.DatabaseHelperManager;
import com.android.dialerbind.ObjectFactory;
import com.android.phone.common.animation.AnimUtils;
+import com.android.ims.ImsManager;
import com.android.phone.common.animation.AnimationListenerAdapter;
import com.google.common.annotations.VisibleForTesting;
+import com.android.contacts.common.CallUtil;
import java.util.ArrayList;
import java.util.List;
@@ -132,6 +139,10 @@
private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial";
private static final String TAG_FAVORITES_FRAGMENT = "favorites";
+ /* Define for Activity permission request for reading phone state */
+ private static final int PERMISSION_REQUEST_CODE_PHONE_STATE_ENABLED = 0;
+ private static final int PERMISSION_REQUEST_CODE_PHONE_STATE_DISABLED = 1;
+ private static final int PERMISSION_REQUEST_CODE_LOCATION = 2;
/**
* Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
*/
@@ -159,6 +170,8 @@
*/
private SmartDialSearchFragment mSmartDialSearchFragment;
+ private boolean mDialConferenceButtonPressed = false;
+
/**
* Animation that slides in.
*/
@@ -202,6 +215,7 @@
private boolean mClearSearchOnPause;
private boolean mIsDialpadShown;
private boolean mShowDialpadOnResume;
+ private WifiCallUtils mWifiCallUtils;
/**
* Whether or not the device is in landscape orientation.
@@ -227,6 +241,7 @@
private PopupMenu mOverflowMenu;
private EditText mSearchView;
private View mVoiceSearchButton;
+ private View mDialCallButton;
private String mSearchQuery;
private String mDialpadQuery;
@@ -234,7 +249,9 @@
private DialerDatabaseHelper mDialerDatabaseHelper;
private DragDropController mDragDropController;
private ActionBarController mActionBarController;
+ private ImageButton mFloatingActionButton;
+ private ImageButton mConferenceDialButton;
private FloatingActionButtonController mFloatingActionButtonController;
private int mActionBarHeight;
@@ -261,11 +278,15 @@
mListsFragment.getSpeedDialFragment() != null &&
mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission);
- menu.findItem(R.id.menu_import_export).setVisible(hasContactsPermission);
- menu.findItem(R.id.menu_add_contact).setVisible(hasContactsPermission);
+ // Hide import/export function in Dialer, Contact supports it already
+ menu.findItem(R.id.menu_import_export).setVisible(false);
+ menu.findItem(R.id.menu_add_contact).setVisible(false);
menu.findItem(R.id.menu_history).setVisible(
PermissionsUtil.hasPhonePermissions(DialtactsActivity.this));
+ final MenuItem ConferDialerOption = menu.findItem(R.id.menu_4g_conference_call);
+ ConferDialerOption.setVisible(
+ IntentUtil.isConferDialerEnabled(getApplicationContext()));
super.show();
}
}
@@ -425,10 +446,15 @@
mPreviouslySelectedTabIndex = ListsFragment.TAB_INDEX_SPEED_DIAL;
final View floatingActionButtonContainer = findViewById(
R.id.floating_action_button_container);
- ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
- floatingActionButton.setOnClickListener(this);
+ mFloatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
+ mDialCallButton = findViewById(R.id.floating_action_button);
+ mFloatingActionButton.setOnClickListener(this);
+ if (!getResources().getBoolean(R.bool.config_hide_SIP_dial_icon)) {
+ mConferenceDialButton = (ImageButton) findViewById(R.id.dialConferenceButton);
+ mConferenceDialButton.setOnClickListener(this);
+ }
mFloatingActionButtonController = new FloatingActionButtonController(this,
- floatingActionButtonContainer, floatingActionButton);
+ floatingActionButtonContainer,mFloatingActionButton);
ImageButton optionsMenuButton =
(ImageButton) searchEditTextLayout.findViewById(R.id.dialtacts_options_menu_button);
@@ -441,6 +467,7 @@
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
+ .add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT)
.commit();
} else {
mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
@@ -492,10 +519,38 @@
Trace.beginSection(TAG + " initialize smart dialing");
mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this);
SmartDialPrefix.initializeNanpSettings(this);
- Trace.endSection();
- Trace.endSection();
- }
+ boolean isPresenceEnabled = this.getResources().getBoolean(
+ R.bool.config_regional_presence_enable);
+ if (isPresenceEnabled && !PresenceHelper.isBound()) {
+ PresenceHelper.bindService((Context) DialtactsActivity.this);
+ }
+
+ mWifiCallUtils = new WifiCallUtils();
+ if (resources.getBoolean(R.bool.config_regional_pup_no_available_network)
+ && mFirstLaunch) {
+ mWifiCallUtils.addWifiCallReadyMarqueeMessage((Context) DialtactsActivity.this);
+ if (ActivityCompat.checkSelfPermission(DialtactsActivity.this,
+ Manifest.permission.ACCESS_COARSE_LOCATION) !=
+ PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(
+ new String[] {Manifest.permission.ACCESS_COARSE_LOCATION},
+ PERMISSION_REQUEST_CODE_LOCATION);
+ } else {
+ mWifiCallUtils.showWifiCallNotification((Context) DialtactsActivity.this);
+ }
+ }
+
+ Trace.endSection();
+ Trace.endSection();
+
+ if (getResources().getBoolean(
+ R.bool.config_regional_video_call_welcome_dialog)) {
+ if(CallUtil.isVideoEnabled(this)) {
+ showVideoCallWelcomeDialog();
+ }
+ }
+ }
@Override
protected void onResume() {
Trace.beginSection(TAG + " onResume");
@@ -535,6 +590,8 @@
prepareVoiceSearchButton();
mDialerDatabaseHelper.startSmartDialUpdateThread();
mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
+ setConferenceDialButtonImage(false);
+ setConferenceDialButtonVisibility(true);
if (Calls.CONTENT_TYPE.equals(getIntent().getType())) {
// Externally specified extras take precedence to EXTRA_SHOW_TAB, which is only
@@ -582,6 +639,14 @@
}
@Override
+ protected void onStop() {
+ if (PresenceHelper.isBound()) {
+ PresenceHelper.unbindService((Context) DialtactsActivity.this);
+ }
+ super.onStop();
+ }
+
+ @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
@@ -626,6 +691,10 @@
public void onClick(View view) {
int resId = view.getId();
if (resId == R.id.floating_action_button) {
+ mDialConferenceButtonPressed = false;
+ if (mDialpadFragment != null) {
+ mDialpadFragment.showDialConference(false);
+ }
if (mListsFragment.getCurrentTabIndex()
== ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch) {
DialerUtils.startActivityWithErrorToast(
@@ -635,6 +704,13 @@
} else if (!mIsDialpadShown) {
mInCallDialpadUp = false;
showDialpadFragment(true);
+ mFloatingActionButton.setImageResource(R.drawable.fab_ic_call);
+ mFloatingActionButton.setVisibility(view.VISIBLE);
+ setConferenceDialButtonImage(false);
+ setConferenceDialButtonVisibility(true);
+ } else {
+ // Dial button was pressed; tell the Dialpad fragment
+ mDialpadFragment.dialButtonPressed();
}
} else if (resId == R.id.voice_search_button) {
try {
@@ -644,6 +720,15 @@
Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available,
Toast.LENGTH_SHORT).show();
}
+ } else if (resId == R.id.dialConferenceButton) {
+ mDialConferenceButtonPressed = true;
+ showDialpadFragment(true);
+ mIsDialpadShown = false;
+ mDialCallButton.setVisibility(view.VISIBLE);
+ mDialpadFragment.dialConferenceButtonPressed();
+ mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial);
+ mFloatingActionButtonController.align(getFabAlignment(), true);
+ mFloatingActionButton.setVisibility(view.VISIBLE);
} else if (resId == R.id.dialtacts_options_menu_button) {
mOverflowMenu.show();
} else {
@@ -692,6 +777,9 @@
final Intent intent = new Intent(this, VoicemailArchiveActivity.class);
startActivity(intent);
return true;
+ } else if (resId == R.id.menu_4g_conference_call){
+ this.startActivity(IntentUtil.getConferenceDialerIntent(null));
+ return true;
}
return false;
}
@@ -755,6 +843,7 @@
maybeEnterSearchUi();
}
mActionBarController.onDialpadUp();
+ setConferenceDialButtonVisibility(animate);
mListsFragment.getView().animate().alpha(0).withLayer();
@@ -767,6 +856,13 @@
*/
public void onDialpadShown() {
Assert.assertNotNull(mDialpadFragment);
+ if (mDialConferenceButtonPressed || !mIsDialpadShown) {
+ mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial);
+ mDialConferenceButtonPressed = false;
+ } else {
+ mFloatingActionButton.setImageResource(R.drawable.fab_ic_call);
+ }
+ mFloatingActionButtonController.align(getFabAlignment(), mDialpadFragment.getAnimate());
if (mDialpadFragment.getAnimate()) {
mDialpadFragment.getView().startAnimation(mSlideIn);
} else {
@@ -794,7 +890,8 @@
mDialpadFragment.getDigitsWidget().setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
- if (!mIsDialpadShown) {
+ if (!mIsDialpadShown && !mDialpadFragment.isRecipientsShown()) {
+ mFloatingActionButtonController.align(getFabAlignment(), animate);
return;
}
mIsDialpadShown = false;
@@ -803,6 +900,7 @@
mListsFragment.sendScreenViewForCurrentPosition();
updateSearchFragmentPosition();
+ mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial);
mFloatingActionButtonController.align(getFabAlignment(), animate);
if (animate) {
@@ -825,7 +923,7 @@
/**
* Finishes hiding the dialpad fragment after any animations are completed.
*/
- private void commitDialpadFragmentHide() {
+ public void commitDialpadFragmentHide() {
if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) {
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(mDialpadFragment);
@@ -1122,7 +1220,10 @@
if (mStateSaved) {
return;
}
- if (mIsDialpadShown) {
+ setConferenceDialButtonImage(false);
+ setConferenceDialButtonVisibility(true);
+ boolean mIsRecipientsShown = mDialpadFragment.isRecipientsShown();
+ if (mIsDialpadShown || mIsRecipientsShown) {
if (TextUtils.isEmpty(mSearchQuery) ||
(mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()
&& mSmartDialSearchFragment.getAdapter().getCount() == 0)) {
@@ -1224,6 +1325,42 @@
// interactions with the ListsFragments.
}
+ @Override
+ public void setConferenceDialButtonVisibility(boolean enabled) {
+ boolean imsUseEnabled = false;
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[] {Manifest.permission.READ_PHONE_STATE}
+ , enabled ? PERMISSION_REQUEST_CODE_PHONE_STATE_ENABLED
+ : PERMISSION_REQUEST_CODE_PHONE_STATE_DISABLED);
+ } else {
+ imsUseEnabled = ImsManager.isVolteEnabledByPlatform(this)
+ && ImsManager.isEnhanced4gLteModeSettingEnabledByUser(this);
+ }
+ if(mConferenceDialButton != null) {
+ boolean isCurrentTabAllContacts = (mListsFragment != null) &&
+ (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_ALL_CONTACTS);
+ mConferenceDialButton.setVisibility((enabled && imsUseEnabled &&
+ !isCurrentTabAllContacts) ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ @Override
+ public void setConferenceDialButtonImage(boolean setAddParticipantButton) {
+ if(mConferenceDialButton != null) {
+ /*
+ * If dial conference view is shown, button should show dialpad
+ * image. Pressing the button again will return to normal dialpad
+ * view. If normal dialpad view is shown, button should show dial
+ * conference image. Pressing the button again will show dial
+ * conference view
+ */
+ mConferenceDialButton
+ .setImageResource(setAddParticipantButton ? R.drawable.fab_ic_call
+ : R.drawable.ic_add_group_holo_dark);
+ }
+ }
+
private boolean phoneIsInUse() {
return TelecomUtil.isInCall(this);
}
@@ -1300,6 +1437,12 @@
// an error message.
phoneNumber = "";
}
+ if (getResources().getBoolean(R.bool.config_regional_number_patterns_video_call) &&
+ !CallUtil.isVideoCallNumValid(phoneNumber) &&
+ isVideoCall && (CallUtil.isVideoEnabled(this))) {
+ Toast.makeText(this,R.string.toast_make_video_call_failed, Toast.LENGTH_LONG).show();
+ return;
+ }
final Intent intent = new CallIntentBuilder(phoneNumber)
.setIsVideoCall(isVideoCall)
@@ -1343,10 +1486,12 @@
int tabIndex = mListsFragment.getCurrentTabIndex();
mPreviouslySelectedTabIndex = tabIndex;
if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
+ setConferenceDialButtonVisibility(false);
mFloatingActionButtonController.changeIcon(
getResources().getDrawable(R.drawable.ic_person_add_24dp),
getResources().getString(R.string.search_shortcut_create_new_contact));
} else {
+ setConferenceDialButtonVisibility(true);
mFloatingActionButtonController.changeIcon(
getResources().getDrawable(R.drawable.fab_ic_dial),
getResources().getString(R.string.action_menu_dialpad_button));
@@ -1403,9 +1548,47 @@
return FloatingActionButtonController.ALIGN_END;
}
+ private void showVideoCallWelcomeDialog() {
+ if (DialerUtils.canShowWelcomeScreen(this) || DialerUtils.isFirstLaunch(this)) {
+ final Intent intent = new Intent(this, VideoCallWelcomeActivity.class);
+ startActivity(intent);
+ }
+ }
+
private void updateMissedCalls() {
if (mPreviouslySelectedTabIndex == ListsFragment.TAB_INDEX_HISTORY) {
mListsFragment.markMissedCallsAsReadAndRemoveNotifications();
}
}
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ switch (requestCode) {
+ case PERMISSION_REQUEST_CODE_PHONE_STATE_ENABLED:
+ case PERMISSION_REQUEST_CODE_PHONE_STATE_DISABLED:
+ boolean imsUseEnabled = false;
+ boolean enabled = requestCode == PERMISSION_REQUEST_CODE_PHONE_STATE_ENABLED;
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ imsUseEnabled = ImsManager.isVolteEnabledByPlatform(this)
+ && ImsManager.isEnhanced4gLteModeSettingEnabledByUser(this);
+ }
+ if(mConferenceDialButton != null) {
+ boolean isCurrentTabAllContacts = (mListsFragment != null)
+ && (mListsFragment.getCurrentTabIndex() ==
+ ListsFragment.TAB_INDEX_ALL_CONTACTS);
+ mConferenceDialButton.setVisibility((enabled && imsUseEnabled
+ && !isCurrentTabAllContacts) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case PERMISSION_REQUEST_CODE_LOCATION:
+ if (grantResults.length > 0 && grantResults[0] ==
+ PackageManager.PERMISSION_GRANTED) {
+ WifiCallUtils.showWifiCallNotification((Context) DialtactsActivity.this);
+ }
+ break;
+ default:
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
}
diff --git a/src/com/android/dialer/PhoneCallDetails.java b/src/com/android/dialer/PhoneCallDetails.java
index 17f1c2b..103a07d 100644
--- a/src/com/android/dialer/PhoneCallDetails.java
+++ b/src/com/android/dialer/PhoneCallDetails.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.support.annotation.Nullable;
@@ -54,7 +55,10 @@
* There might be multiple types if this represents a set of entries grouped together.
*/
public int[] callTypes;
-
+ /**
+ * The icon for the account associated with the call.
+ */
+ public Drawable accountIcon;
// The date of the call, in milliseconds since the epoch.
public long date;
// The duration of the call in milliseconds, or 0 for missed calls.
diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java
index 4303f3e..fff7d19 100644
--- a/src/com/android/dialer/SpecialCharSequenceMgr.java
+++ b/src/com/android/dialer/SpecialCharSequenceMgr.java
@@ -32,7 +32,9 @@
import android.provider.Settings;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -74,6 +76,9 @@
private static final String MMI_IMEI_DISPLAY = "*#06#";
private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#";
+ private static final String PRL_VERSION_DISPLAY = "*#0000#";
+
+ private static final int IMEI_14_DIGIT = 14;
/**
* Remembers the previous {@link QueryHandler} and cancel the operation when needed, to
* prevent possible crash.
@@ -137,6 +142,7 @@
String dialString = PhoneNumberUtils.stripSeparators(input);
if (handleDeviceIdDisplay(context, dialString)
+ || handlePRLVersion(context, dialString)
|| handleRegulatoryInfoDisplay(context, dialString)
|| handlePinEntry(context, dialString)
|| handleAdnEntry(context, dialString, textField)
@@ -147,6 +153,20 @@
return false;
}
+ static private boolean handlePRLVersion(Context context, String input) {
+ if (input.equals(PRL_VERSION_DISPLAY)) {
+ try {
+ Intent intent = new Intent("android.intent.action.ENGINEER_MODE_DEVICEINFO");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.d(TAG, "no activity to handle showing device info");
+ }
+ }
+ return false;
+ }
+
/**
* Cleanup everything around this class. Must be run inside the main thread.
*
@@ -182,7 +202,15 @@
context.sendBroadcast(intent);
return true;
}
-
+ if (!TextUtils.isEmpty(context.getString(R.string.oem_key_code_action))) {
+ if (len > 10 && !input.startsWith("*#*#")
+ && input.startsWith("*#") && input.endsWith("#")) {
+ Intent intent = new Intent(context.getString(R.string.oem_key_code_action));
+ intent.putExtra(context.getString(R.string.oem_code), input);
+ context.sendBroadcast(intent);
+ return true;
+ }
+ }
return false;
}
@@ -336,6 +364,26 @@
"getDeviceId", Integer.TYPE)) {
for (int slot = 0; slot < telephonyManager.getPhoneCount(); slot++) {
String deviceId = telephonyManager.getDeviceId(slot);
+ boolean enable14DigitImei = false;
+ try {
+ CarrierConfigManager configManager =
+ (CarrierConfigManager) context.getSystemService(
+ Context.CARRIER_CONFIG_SERVICE);
+ int[] subIds = SubscriptionManager.getSubId(slot);
+ if (configManager != null &&
+ configManager.getConfigForSubId(subIds[0]) != null) {
+ enable14DigitImei =
+ configManager.getConfigForSubId(subIds[0]).getBoolean(
+ "config_enable_display_14digit_imei");
+ }
+ } catch(RuntimeException ex) {
+ //do Nothing
+ Log.e(TAG, "Config for 14 digit IMEI not found: " + ex);
+ }
+ if (enable14DigitImei && !TextUtils.isEmpty(deviceId)
+ && deviceId.length() > IMEI_14_DIGIT) {
+ deviceId = deviceId.substring(0, IMEI_14_DIGIT);
+ }
if (!TextUtils.isEmpty(deviceId)) {
deviceIds.add(deviceId);
}
diff --git a/src/com/android/dialer/SpeedDialListActivity.java b/src/com/android/dialer/SpeedDialListActivity.java
new file mode 100644
index 0000000..e979151
--- /dev/null
+++ b/src/com/android/dialer/SpeedDialListActivity.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2013-2016, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.dialer;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.Settings;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ListView;
+import android.widget.PopupMenu;
+import android.widget.QuickContactBadge;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.internal.telephony.PhoneConstants;
+import com.google.common.base.FinalizablePhantomReference;
+
+import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
+import java.util.List;
+
+public class SpeedDialListActivity extends ListActivity implements
+ AdapterView.OnItemClickListener, PopupMenu.OnMenuItemClickListener {
+ private static final String TAG = "SpeedDial";
+ private static final String ACTION_ADD_VOICEMAIL =
+ "com.android.phone.CallFeaturesSetting.ADD_VOICEMAIL";
+ public static final String EXTRA_INITIAL_PICK_NUMBER = "initialPickNumber";
+
+ // Extra on intent containing the id of a subscription.
+ public static final String SUB_ID_EXTRA =
+ "com.android.phone.settings.SubscriptionInfoHelper.SubscriptionId";
+ // Extra on intent containing the label of a subscription.
+ private static final String SUB_LABEL_EXTRA =
+ "com.android.phone.settings.SubscriptionInfoHelper.SubscriptionLabel";
+
+ private static final String[] LOOKUP_PROJECTION = new String[] {
+ ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME,
+ ContactsContract.Contacts.PHOTO_ID,
+ ContactsContract.PhoneLookup.NUMBER,
+ ContactsContract.PhoneLookup.NORMALIZED_NUMBER
+ };
+
+ private static final String[] PICK_PROJECTION = new String[] {
+ ContactsContract.Data.CONTACT_ID,
+ ContactsContract.Data.DISPLAY_NAME,
+ ContactsContract.Data.PHOTO_ID,
+ ContactsContract.CommonDataKinds.Phone.NUMBER,
+ ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER
+ };
+ private static final int COLUMN_ID = 0;
+ private static final int COLUMN_NAME = 1;
+ private static final int COLUMN_PHOTO = 2;
+ private static final int COLUMN_NUMBER = 3;
+ private static final int COLUMN_NORMALIZED = 4;
+ private static final int MENU_REPLACE = 1001;
+ private static final int MENU_DELETE = 1002;
+ private int mItemPosition;
+ private static String SPEAD_DIAL_NUMBER = "SpeedDialNumber";
+ private static String SAVE_CLICKED_POS = "Clicked_pos";
+ private String mInputNumber;
+ private boolean mConfigChanged;
+
+ private static final String PROPERTY_RADIO_ATEL_CARRIER = "persist.radio.atel.carrier";
+ private static final String CARRIER_ONE_DEFAULT_MCC_MNC = "405854";
+
+ private static class Record {
+ long contactId;
+ String name;
+ String number;
+ String normalizedNumber;
+ long photoId;
+ public Record(String number) {
+ this.number = number;
+ this.contactId = -1;
+ }
+ }
+
+ private SparseArray<Record> mRecords;
+
+ private int mPickNumber;
+ private int mInitialPickNumber;
+ private SpeedDialAdapter mAdapter;
+ private AlertDialog mAddSpeedDialDialog;
+ private EditText mEditNumber;
+ private Button mCompleteButton;
+
+ private static final int PICK_CONTACT_RESULT = 0;
+
+ private SubscriptionManager mSubscriptionManager;
+
+ private boolean mEmergencyCallSpeedDial = false;
+ private int mSpeedDialKeyforEmergncyCall = -1;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mInitialPickNumber = getIntent().getIntExtra(EXTRA_INITIAL_PICK_NUMBER, -1);
+ mRecords = new SparseArray<Record>();
+
+ //the first item is the "1.voice mail", it never changes
+ mRecords.put(1, new Record(getString(R.string.voicemail)));
+
+ mSubscriptionManager = SubscriptionManager.from(this);
+
+ ListView listview = getListView();
+ listview.setOnItemClickListener(this);
+
+ // compensate for action bar overlay specified in theme
+ int actionBarHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height);
+ listview.setPaddingRelative(0, actionBarHeight, 0, 0);
+
+ final ActionBar actionBar = getActionBar();
+ actionBar.setDisplayShowHomeEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ mAdapter = new SpeedDialAdapter();
+ setListAdapter(mAdapter);
+
+ String property = SystemProperties.get(PROPERTY_RADIO_ATEL_CARRIER);
+ mEmergencyCallSpeedDial = CARRIER_ONE_DEFAULT_MCC_MNC.equals(property);
+ mSpeedDialKeyforEmergncyCall = getResources().getInteger(
+ R.integer.speed_dial_emergency_number_assigned_key);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mAddSpeedDialDialog == null || !mAddSpeedDialDialog.isShowing()) {
+ outState.clear();
+ return;
+ }
+ outState.putInt(SAVE_CLICKED_POS, mItemPosition);
+ outState.putString(SPEAD_DIAL_NUMBER, mEditNumber.getText().toString());
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+ if (state.isEmpty()) {
+ return;
+ }
+ mConfigChanged = true;
+ int number = state.getInt(SAVE_CLICKED_POS, mItemPosition);
+ mInputNumber = state.getString(SPEAD_DIAL_NUMBER, "");
+ showAddSpeedDialDialog(number);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // get number from shared preferences
+ for (int i = 2; i <= 9; i++) {
+ String phoneNumber = SpeedDialUtils.getNumber(this, i);
+ Record record = null;
+ if (phoneNumber != null) {
+ Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(phoneNumber));
+ record = getRecordFromQuery(uri, LOOKUP_PROJECTION);
+ if (record == null) {
+ record = new Record(phoneNumber);
+ }
+ }
+ mRecords.put(i, record);
+ }
+
+ mAdapter.notifyDataSetChanged();
+
+ if (mInitialPickNumber >= 2 && mInitialPickNumber <= 9) {
+ pickContact(mInitialPickNumber);
+ // we only want to trigger the picker once
+ mInitialPickNumber = -1;
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private Record getRecordFromQuery(Uri uri, String[] projection) {
+ Record record = null;
+ Cursor cursor = null;
+ try {
+ cursor = getContentResolver().query(uri, projection, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ record = new Record(cursor.getString(COLUMN_NUMBER));
+ record.contactId = cursor.getLong(COLUMN_ID);
+ record.photoId = cursor.getLong(COLUMN_PHOTO);
+ record.name = cursor.getString(COLUMN_NAME);
+ record.normalizedNumber = cursor.getString(COLUMN_NORMALIZED);
+ if (record.normalizedNumber == null) {
+ record.normalizedNumber = record.number;
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return record;
+ }
+
+ private void showAddSpeedDialDialog(final int number) {
+ mPickNumber = number;
+ mItemPosition = number;
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.speed_dial_settings);
+ View contentView = LayoutInflater.from(this).inflate(
+ R.layout.add_speed_dial_dialog, null);
+ builder.setView(contentView);
+ ImageButton pickContacts = (ImageButton) contentView
+ .findViewById(R.id.select_contact);
+ pickContacts.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ pickContact(number);
+ dismissDialog();
+ }
+ });
+ mEditNumber = (EditText) contentView.findViewById(R.id.edit_container);
+ if (null != mRecords.get(number)) {
+ mEditNumber.setText(SpeedDialUtils.getNumber(this, number));
+ }
+ if (mConfigChanged && !mInputNumber.isEmpty()) {
+ mEditNumber.setText(mInputNumber);
+ mConfigChanged = false;
+ mInputNumber = "";
+ }
+ Button cancelButton = (Button) contentView
+ .findViewById(R.id.btn_cancel);
+ cancelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismissDialog();
+ }
+ });
+ mCompleteButton = (Button) contentView.findViewById(R.id.btn_complete);
+ mCompleteButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mEditNumber.getText().toString().isEmpty()) {
+ dismissDialog();
+ return;
+ }
+ saveSpeedDial();
+ dismissDialog();
+ }
+ });
+ mAddSpeedDialDialog = builder.create();
+ mAddSpeedDialDialog.show();
+ }
+
+ private void saveSpeedDial() {
+ String number = mEditNumber.getText().toString();
+ Record record = null;
+ if (number != null) {
+ Uri uri = Uri.withAppendedPath(
+ ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(number));
+ record = getRecordFromQuery(uri, LOOKUP_PROJECTION);
+ if (record == null) {
+ record = new Record(number);
+ record.normalizedNumber = number;
+ }
+ }
+ if (record != null) {
+ SpeedDialUtils.saveNumber(this, mPickNumber,
+ record.normalizedNumber);
+ mRecords.put(mPickNumber, record);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private void dismissDialog() {
+ if (null != mAddSpeedDialDialog && mAddSpeedDialDialog.isShowing()) {
+ mAddSpeedDialDialog.dismiss();
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (position == 0) {
+ Intent intent = new Intent(ACTION_ADD_VOICEMAIL);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ if (TelephonyManager.getDefault().getPhoneCount() > 1) {
+ int sub = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(sub);
+ if (subInfo != null) {
+ intent.putExtra(SUB_ID_EXTRA, subInfo.getSubscriptionId());
+ intent.putExtra(SUB_LABEL_EXTRA, subInfo.getDisplayName().toString());
+ }
+ }
+ try {
+ startActivity(intent);
+ } catch(ActivityNotFoundException e) {
+ Log.w(TAG, "Could not find voice mail setup activity");
+ }
+ } else {
+ int number = position + 1;
+ if (mEmergencyCallSpeedDial && (number == mSpeedDialKeyforEmergncyCall)) {
+ Toast.makeText(SpeedDialListActivity.this, R.string.speed_dial_can_not_be_set,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ mItemPosition = number;
+ final Record record = mRecords.get(number);
+ if (record == null) {
+ showAddSpeedDialDialog(number);
+ } else {
+ PopupMenu pm = new PopupMenu(this, view, Gravity.START);
+ pm.getMenu().add(number, MENU_REPLACE, 0, R.string.speed_dial_replace);
+ pm.getMenu().add(number, MENU_DELETE, 0, R.string.speed_dial_delete);
+ pm.setOnMenuItemClickListener(this);
+ pm.show();
+ }
+ }
+ }
+
+ private boolean isMultiAccountAvailable() {
+ TelecomManager telecomManager = getTelecomManager(this);
+ return (telecomManager.getUserSelectedOutgoingPhoneAccount() == null)
+ && (telecomManager.getAllPhoneAccountsCount() > 1);
+ }
+
+ private void showSelectAccountDialog(Context context) {
+ TelecomManager telecomManager = getTelecomManager(context);
+ List<PhoneAccountHandle> accountsList = telecomManager
+ .getCallCapablePhoneAccounts();
+ final PhoneAccountHandle[] accounts = accountsList
+ .toArray(new PhoneAccountHandle[accountsList.size()]);
+ CharSequence[] accountEntries = new CharSequence[accounts.length];
+ for (int i = 0; i < accounts.length; i++) {
+ CharSequence label = telecomManager.getPhoneAccount(accounts[i])
+ .getLabel();
+ accountEntries[i] = (label == null) ? null : label.toString();
+ }
+ AlertDialog dialog = new AlertDialog.Builder(context)
+ .setTitle(R.string.select_account_dialog_title)
+ .setItems(accountEntries, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent(ACTION_ADD_VOICEMAIL);
+ int sub = Integer.parseInt(accounts[which].getId());
+ intent.setClassName("com.android.phone",
+ "com.android.phone.MSimCallFeaturesSubSetting");
+ intent.putExtra(SUBSCRIPTION_KEY, sub);
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "can not find activity deal with voice mail");
+ }
+ }
+ })
+ .create();
+ dialog.show();
+ }
+
+ private TelecomManager getTelecomManager(Context context) {
+ return TelecomManager.from(context);
+ }
+
+ /*
+ * goto contacts, used to set or replace speed number
+ */
+ private void pickContact(int number) {
+ mPickNumber = number;
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
+ startActivityForResult(intent, PICK_CONTACT_RESULT);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != PICK_CONTACT_RESULT) {
+ super.onActivityResult(requestCode, resultCode, data);
+ return;
+ }
+
+ if (resultCode == RESULT_OK) {
+ Record record = getRecordFromQuery(data.getData(), PICK_PROJECTION);
+ if (record != null) {
+ SpeedDialUtils.saveNumber(this, mPickNumber, record.normalizedNumber);
+ mRecords.put(mPickNumber, record);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ int number = item.getGroupId();
+
+ switch (item.getItemId()) {
+ case MENU_REPLACE:
+ showAddSpeedDialDialog(number);
+ return true;
+ case MENU_DELETE:
+ mRecords.put(number, null);
+ SpeedDialUtils.saveNumber(this, number, null);
+ mAdapter.notifyDataSetChanged();
+ return true;
+ }
+ return false;
+ }
+
+ private class SpeedDialAdapter extends BaseAdapter {
+ private LayoutInflater mInflater;
+ private ContactPhotoManager mPhotoManager;
+
+ public SpeedDialAdapter() {
+ mInflater = LayoutInflater.from(SpeedDialListActivity.this);
+ mPhotoManager = ContactPhotoManager.getInstance(SpeedDialListActivity.this);
+ }
+
+ @Override
+ public int getCount() {
+ return mRecords.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position + 1;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mRecords.get(position + 1);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.speed_dial_item, parent, false);
+ }
+
+ TextView index = (TextView) convertView.findViewById(R.id.index);
+ TextView name = (TextView) convertView.findViewById(R.id.name);
+ TextView number = (TextView) convertView.findViewById(R.id.number);
+ QuickContactBadge photo = (QuickContactBadge) convertView.findViewById(R.id.photo);
+ Record record = mRecords.get(position + 1);
+
+ index.setText(String.valueOf(position + 1));
+ if (record != null && record.name != null) {
+ name.setText(record.name);
+ number.setText(record.number);
+ number.setVisibility(View.VISIBLE);
+ } else {
+ name.setText(record != null ?
+ record.number : getString(R.string.speed_dial_not_set));
+ number.setVisibility(View.GONE);
+ }
+
+ if (record != null && record.contactId != -1) {
+ DefaultImageRequest request = new DefaultImageRequest(record.name,
+ record.normalizedNumber, true /* isCircular */);
+ mPhotoManager.removePhoto(photo);
+ mPhotoManager.loadThumbnail(photo, record.photoId,
+ false /* darkTheme */, true /* isCircular */, request);
+ photo.assignContactUri(ContentUris.withAppendedId(
+ ContactsContract.Contacts.CONTENT_URI, record.contactId));
+ photo.setVisibility(View.VISIBLE);
+ } else {
+ photo.setVisibility(View.GONE);
+ }
+ photo.setOverlay(null);
+
+ return convertView;
+ }
+ };
+}
diff --git a/src/com/android/dialer/SpeedDialUtils.java b/src/com/android/dialer/SpeedDialUtils.java
new file mode 100644
index 0000000..24f0e8c
--- /dev/null
+++ b/src/com/android/dialer/SpeedDialUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013-2016, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.dialer;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class SpeedDialUtils {
+ private static final String NUMBER_KEY_PREFIX = "number_";
+
+ public static void saveNumber(Context context, int position, String phoneNumber) {
+ if (position < 2 || position > 9) {
+ return;
+ }
+ SharedPreferences.Editor editor = getPrefs(context).edit();
+ String key = NUMBER_KEY_PREFIX + position;
+ if (phoneNumber == null) {
+ editor.remove(key);
+ } else {
+ editor.putString(key, phoneNumber);
+ }
+ editor.commit();
+ }
+
+ public static String getNumber(Context context, int position) {
+ if (position < 2 || position > 9) {
+ return null;
+ }
+ String key = NUMBER_KEY_PREFIX + position;
+ return getPrefs(context).getString(key, null);
+ }
+
+ private static SharedPreferences getPrefs(Context context) {
+ return context.getSharedPreferences("speeddial", context.MODE_PRIVATE);
+ }
+}
diff --git a/src/com/android/dialer/VideoCallWelcomeActivity.java b/src/com/android/dialer/VideoCallWelcomeActivity.java
new file mode 100644
index 0000000..4a72f13
--- /dev/null
+++ b/src/com/android/dialer/VideoCallWelcomeActivity.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+package com.android.dialer;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.dialer.util.DialerUtils;
+
+public class VideoCallWelcomeActivity extends AlertActivity
+ implements DialogInterface.OnClickListener {
+
+ private CheckBox mConfirmRepeat;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mView = LayoutInflater.from(this).inflate(R.layout.video_call_welcome, null);
+ ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mPositiveButtonListener = this;
+
+ mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
+ mConfirmRepeat.setChecked(DialerUtils.canShowWelcomeScreen(this));
+
+ setupAlert();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ // Remember confirm state, and launch target
+ DialerUtils.setShowingState(this,
+ mConfirmRepeat.isChecked());
+ }
+
+ finish();
+ }
+}
+
diff --git a/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java b/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java
index ac56332..bbd632c 100644
--- a/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java
+++ b/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java
@@ -20,6 +20,7 @@
import android.provider.CallLog.Calls;
import android.text.format.DateUtils;
import android.text.format.Formatter;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,10 +31,14 @@
import com.android.dialer.PhoneCallDetails;
import com.android.dialer.R;
import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.AppCompatConstants;
+import com.android.dialer.util.PresenceHelper;
import com.google.common.collect.Lists;
import java.util.ArrayList;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
/**
* Adapter for a ListView containing history items from the details of a call.
*/
@@ -105,12 +110,25 @@
TextView durationView = (TextView) result.findViewById(R.id.duration);
int callType = details.callTypes[0];
- boolean isVideoCall = (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO
- && CallUtil.isVideoEnabled(mContext);
-
+ boolean isPresenceEnabled = mContext.getResources().getBoolean(
+ R.bool.config_regional_presence_enable);
+ boolean isVideoCall = (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO;
+ if (isPresenceEnabled) {
+ isVideoCall &= PresenceHelper.startAvailabilityFetch(details.number.toString());
+ }
+ Log.d("CallDetailHistoryAdapter", "isVideoCall = " + isVideoCall
+ + ", callType = " + callType);
callTypeIconView.clear();
callTypeIconView.add(callType);
- callTypeIconView.setShowVideo(isVideoCall);
+ /**
+ * Ims icon(VoLTE/VoWifi) or CarrierOne video icon will be shown if carrierOne is supported
+ * otherwise, default video icon will be shown if it is a video call.
+ */
+ if (QtiImsExtUtils.isCarrierOneSupported()) {
+ callTypeIconView.addImsOrVideoIcon(callType, isVideoCall);
+ } else {
+ callTypeIconView.setShowVideo(isVideoCall);
+ }
callTypeTextView.setText(mCallTypeHelper.getCallTypeText(callType, isVideoCall));
// Set the date.
CharSequence dateValue = DateUtils.formatDateRange(mContext, details.date, details.date,
@@ -118,7 +136,10 @@
DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR);
dateView.setText(dateValue);
// Set the duration
- if (Calls.VOICEMAIL_TYPE == callType || CallTypeHelper.isMissedCallType(callType)) {
+ boolean callDurationEnabled = mContext.getResources()
+ .getBoolean(R.bool.call_duration_enabled);
+ if (Calls.VOICEMAIL_TYPE == callType || CallTypeHelper.isMissedCallType(callType) ||
+ !callDurationEnabled) {
durationView.setVisibility(View.GONE);
} else {
durationView.setVisibility(View.VISIBLE);
diff --git a/src/com/android/dialer/calllog/CallLogActivity.java b/src/com/android/dialer/calllog/CallLogActivity.java
index 1823a5b..d661018 100644
--- a/src/com/android/dialer/calllog/CallLogActivity.java
+++ b/src/com/android/dialer/calllog/CallLogActivity.java
@@ -18,23 +18,40 @@
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
+import android.telephony.TelephonyManager;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBar.LayoutParams;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.inputmethod.InputMethodManager;
+import android.text.TextWatcher;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.SearchView;
+import android.util.Log;
import com.android.contacts.common.interactions.TouchPointManager;
import com.android.contacts.common.list.ViewPagerTabs;
+import com.android.contacts.common.SimContactsConstants;
import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.commonbind.analytics.AnalyticsUtil;
import com.android.dialer.DialtactsActivity;
@@ -44,15 +61,21 @@
import com.android.dialer.logging.ScreenEvent;
import com.android.dialer.util.DialerUtils;
-public class CallLogActivity extends TransactionSafeActivity implements ViewPager.OnPageChangeListener {
+public class CallLogActivity extends TransactionSafeActivity implements ViewPager.OnPageChangeListener,
+ CallLogFragment.HostInterface {
private ViewPager mViewPager;
private ViewPagerTabs mViewPagerTabs;
- private ViewPagerAdapter mViewPagerAdapter;
+ private FragmentPagerAdapter mViewPagerAdapter;
private CallLogFragment mAllCallsFragment;
private CallLogFragment mMissedCallsFragment;
+ private MSimCallLogFragment mMSimCallsFragment;
+ private CallLogSearchFragment mSearchFragment;
+ private EditText mSearchView;
+ private ImageView mClearButtonView;
+ private boolean mInSearchUi;
private String[] mTabTitles;
-
+ private String mSearchQuery;
private static final int TAB_INDEX_ALL = 0;
private static final int TAB_INDEX_MISSED = 1;
@@ -60,6 +83,9 @@
private boolean mIsResumed;
+ private static final int TAB_INDEX_MSIM = 0;
+ private static final int TAB_INDEX_COUNT_MSIM = 1;
+
public class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
@@ -108,6 +134,39 @@
}
}
+ public class MSimViewPagerAdapter extends FragmentPagerAdapter {
+ public MSimViewPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ switch (position) {
+ case TAB_INDEX_MSIM:
+ mMSimCallsFragment = new MSimCallLogFragment();
+ return mMSimCallsFragment;
+ }
+ throw new IllegalStateException("No fragment at position " + position);
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ final MSimCallLogFragment fragment =
+ (MSimCallLogFragment) super.instantiateItem(container, position);
+ switch (position) {
+ case TAB_INDEX_MSIM:
+ mMSimCallsFragment = fragment;
+ break;
+ }
+ return fragment;
+ }
+
+ @Override
+ public int getCount() {
+ return TAB_INDEX_COUNT_MSIM;
+ }
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -120,15 +179,20 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.call_log_activity);
- getWindow().setBackgroundDrawable(null);
-
final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setElevation(0);
+ if ( TelephonyManager.getDefault().isMultiSimEnabled()) {
+ initMSimCallLog();
+ return;
+ }
+
+ setContentView(R.layout.call_log_activity);
+ getWindow().setBackgroundDrawable(null);
+
int startingTab = TAB_INDEX_ALL;
final Intent intent = getIntent();
if (intent != null) {
@@ -156,6 +220,16 @@
}
@Override
+ public void onAttachFragment(Fragment fragment) {
+ if (fragment instanceof CallLogSearchFragment) {
+ if (mViewPagerAdapter != null) {
+ mSearchFragment = (CallLogSearchFragment) fragment;
+ setupSearchUi();
+ }
+ }
+ }
+
+ @Override
protected void onResume() {
mIsResumed = true;
super.onResume();
@@ -166,6 +240,25 @@
protected void onPause() {
mIsResumed = false;
super.onPause();
+ if (mInSearchUi) {
+ exitSearchUi();
+ }
+ }
+
+ private void initMSimCallLog() {
+ setContentView(R.layout.msim_call_log_activity);
+ getWindow().setBackgroundDrawable(null);
+
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayShowHomeEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+
+ mViewPager = (ViewPager) findViewById(R.id.call_log_pager);
+
+ mViewPagerAdapter = new MSimViewPagerAdapter(getFragmentManager());
+ mViewPager.setAdapter(mViewPagerAdapter);
+ mViewPager.setOffscreenPageLimit(1);
}
@Override
@@ -178,11 +271,32 @@
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final MenuItem itemDeleteAll = menu.findItem(R.id.delete_all);
- if (mAllCallsFragment != null && itemDeleteAll != null) {
- // If onPrepareOptionsMenu is called before fragments are loaded, don't do anything.
- final CallLogAdapter adapter = mAllCallsFragment.getAdapter();
+ final MenuItem itemSearchCallLog = menu.findItem(R.id.search_calllog);
+
+ if (mMSimCallsFragment != null && itemDeleteAll != null) {
+ final CallLogAdapter adapter = mMSimCallsFragment.getAdapter();
itemDeleteAll.setVisible(adapter != null && !adapter.isEmpty());
}
+ if (mInSearchUi) {
+ if (itemDeleteAll != null) {
+ itemDeleteAll.setVisible(false);
+ }
+ if (itemSearchCallLog != null) {
+ itemSearchCallLog.setVisible(false);
+ }
+ } else {
+ if (mSearchFragment != null && itemSearchCallLog != null) {
+ final CallLogAdapter adapter = mSearchFragment.getAdapter();
+ itemSearchCallLog.setVisible(adapter != null
+ && !adapter.isEmpty());
+ }
+ // If onPrepareOptionsMenu is called before fragments loaded. Don't do anything.
+ if (mAllCallsFragment != null && itemDeleteAll != null) {
+ // If onPrepareOptionsMenu is called before fragments are loaded, don't do anything.
+ final CallLogAdapter adapter = mAllCallsFragment.getAdapter();
+ itemDeleteAll.setVisible(adapter != null && !adapter.isEmpty());
+ }
+ }
return true;
}
@@ -198,12 +312,19 @@
startActivity(intent);
return true;
} else if (item.getItemId() == R.id.delete_all) {
- ClearCallLogDialog.show(getFragmentManager());
+ onDeleteCallLog();
+ return true;
+ } else if (item.getItemId() == R.id.search_calllog){
+ enterSearchUi();
return true;
}
return super.onOptionsItemSelected(item);
}
+ private void onDeleteCallLog() {
+ startActivity(new Intent(SimContactsConstants.ACTION_MULTI_PICK_CALL));
+ }
+
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mViewPagerTabs.onPageScrolled(position, positionOffset, positionOffsetPixels);
@@ -232,4 +353,205 @@
}
return position;
}
+
+ private void enterSearchUi() {
+ mInSearchUi = true;
+ if (mSearchFragment == null) {
+ addSearchFragment();
+ return;
+ }
+ mSearchFragment.setUserVisibleHint(true);
+ final FragmentTransaction transaction = getFragmentManager()
+ .beginTransaction();
+ transaction.show(mSearchFragment);
+ transaction.commitAllowingStateLoss();
+ getFragmentManager().executePendingTransactions();
+ setupSearchUi();
+ }
+
+ private void setupSearchUi() {
+ if (mSearchView == null) {
+ prepareSearchView();
+ }
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayShowCustomEnabled(true);
+ if (mMSimCallsFragment != null) {
+ updateMSimFragmentVisibility(false);
+ } else {
+ for (int i = 0; i < mViewPagerAdapter.getCount(); i++) {
+ updateFragmentVisibility(i, false /* not visible */);
+ }
+ }
+ mViewPager.setVisibility(View.GONE);
+ if (mViewPagerTabs != null) {
+ mViewPagerTabs.setVisibility(View.GONE);
+ }
+ }
+
+ private void updateFragmentVisibility(int position, boolean visibility) {
+ if (position >= TAB_INDEX_ALL) {
+ final Fragment fragment = getFragmentAt(position);
+ if (fragment != null) {
+ fragment.setMenuVisibility(visibility);
+ fragment.setUserVisibleHint(visibility);
+ }
+ }
+ }
+
+ private void updateMSimFragmentVisibility(boolean visibility) {
+ if (mMSimCallsFragment != null) {
+ mMSimCallsFragment.setMenuVisibility(visibility);
+ mMSimCallsFragment.setUserVisibleHint(visibility);
+ }
+ }
+
+ private Fragment getFragmentAt(int position) {
+ switch (position) {
+ case TAB_INDEX_ALL:
+ return mAllCallsFragment;
+ case TAB_INDEX_MISSED:
+ return mMissedCallsFragment;
+ default:
+ throw new IllegalStateException("Unknown fragment index: "
+ + position);
+ }
+ }
+
+ private void addSearchFragment() {
+ if (mSearchFragment != null) {
+ return;
+ }
+ final FragmentTransaction ft = getFragmentManager().beginTransaction();
+ final Fragment searchFragment = new CallLogSearchFragment();
+ searchFragment.setUserVisibleHint(false);
+ ft.add(R.id.calllog_frame, searchFragment);
+ ft.commitAllowingStateLoss();
+ }
+
+ private void prepareSearchView() {
+ final View searchViewLayout = getLayoutInflater().inflate(
+ R.layout.search_action_bar, null);
+ mSearchView = (EditText) searchViewLayout
+ .findViewById(R.id.search_view);
+ mClearButtonView = (ImageView)searchViewLayout.findViewById(R.id.search_close_button);
+ mClearButtonView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSearchView.setText("");
+ }
+ });
+ mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
+ mClearButtonView.setVisibility(View.GONE);
+ mSearchView.setOnFocusChangeListener(new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ showInputMethod(v.findFocus());
+ } else {
+ hideInputMethod(v);
+ }
+ }
+ });
+ getSupportActionBar().setCustomView(
+ searchViewLayout,
+ new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT));
+ }
+
+ /**
+ * Implemented to satisfy {@link CallLogFragment.HostInterface}
+ */
+ @Override
+ public void showDialpad() {
+ finish();
+ if (mInSearchUi) {
+ exitSearchUi();
+ }
+ startActivity(new Intent(CallLogActivity.this, DialtactsActivity.class));
+ }
+
+ private void showInputMethod(View view) {
+ InputMethodManager imm = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+
+ private void hideInputMethod(View view) {
+ InputMethodManager imm = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ if (imm != null && view != null) {
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+
+ /**
+ * Listener used to send search queries to the phone search fragment.
+ */
+ private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ final String newText = s.toString();
+ if (newText.equals(mSearchQuery)) {
+ // If the query hasn't changed (perhaps due to activity being destroyed
+ // and restored, or user launching the same DIAL intent twice), then there is
+ // no need to do anything here.
+ return;
+ }
+ mSearchQuery = newText;
+ if (mSearchFragment != null) {
+ mClearButtonView.setVisibility(TextUtils.isEmpty(s) ? View.GONE : View.VISIBLE);
+ mSearchFragment.setQueryString(mSearchQuery);
+ }
+ }
+ @Override
+ public void afterTextChanged(Editable e) {
+ }
+ };
+
+ @Override
+ public void onBackPressed() {
+ if (mInSearchUi) {
+ // We should let the user go back to usual screens with tabs.
+ exitSearchUi();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ private void exitSearchUi() {
+ final ActionBar actionBar = getSupportActionBar();
+ if (mSearchFragment != null) {
+ mSearchFragment.setUserVisibleHint(false);
+
+ final FragmentTransaction transaction = getFragmentManager()
+ .beginTransaction();
+ transaction.remove(mSearchFragment);
+ mSearchFragment = null;
+ transaction.commitAllowingStateLoss();
+ }
+
+ // We want to hide SearchView and show Tabs. Also focus on previously
+ // selected one.
+ actionBar.setDisplayShowCustomEnabled(false);
+ if (mMSimCallsFragment != null) {
+ updateMSimFragmentVisibility(true);
+ } else {
+ for (int i = 0; i < mViewPagerAdapter.getCount(); i++) {
+ updateFragmentVisibility(i, i == mViewPager.getCurrentItem());
+ }
+ }
+ mViewPager.setVisibility(View.VISIBLE);
+ if (mViewPagerTabs != null) {
+ mViewPagerTabs.setVisibility(View.VISIBLE);
+ }
+ hideInputMethod(getCurrentFocus());
+ invalidateOptionsMenu();
+ mSearchView.clearFocus();
+ mInSearchUi = false;
+ }
+
}
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 3958611..6a447a6 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -23,6 +23,7 @@
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
@@ -59,11 +60,13 @@
import com.android.dialer.logging.InteractionEvent;
import com.android.dialer.logging.Logger;
import com.android.dialer.service.ExtendedBlockingButtonRenderer;
+import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.PhoneNumberUtil;
import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
import java.util.HashMap;
import java.util.Map;
+import java.util.regex.Pattern;
/**
* Adapter class to fill in data for the Call Log.
@@ -107,6 +110,7 @@
private final Map<String, Boolean> mBlockedNumberCache = new ArrayMap<>();
protected ContactInfoCache mContactInfoCache;
+ private String mFilterString;
private final int mActivityType;
@@ -179,7 +183,9 @@
if (viewHolder.callType == CallLog.Calls.MISSED_TYPE) {
CallLogAsyncTaskUtil.markCallAsRead(mContext, viewHolder.callIds);
if (mActivityType == ACTIVITY_TYPE_DIALTACTS) {
- ((DialtactsActivity) v.getContext()).updateTabUnreadCounts();
+ if (v.getContext() instanceof DialtactsActivity) {
+ ((DialtactsActivity) v.getContext()).updateTabUnreadCounts();
+ }
}
}
expandViewHolderActions(viewHolder);
@@ -355,6 +361,12 @@
mContactInfoCache.invalidate();
}
+ public void startCache() {
+ if (PermissionsUtil.hasPermission(mContext, android.Manifest.permission.READ_CONTACTS)) {
+ mContactInfoCache.start();
+ }
+ }
+
public void onResume() {
if (PermissionsUtil.hasPermission(mContext, android.Manifest.permission.READ_CONTACTS)) {
mContactInfoCache.start();
@@ -370,6 +382,13 @@
}
}
+ public void onStop () {
+ pauseCache();
+ if (mHiddenItemUri != null) {
+ CallLogAsyncTaskUtil.deleteVoicemail(mContext, mHiddenItemUri, null);
+ }
+ }
+
@VisibleForTesting
/* package */ void pauseCache() {
mContactInfoCache.stop();
@@ -488,12 +507,15 @@
}
int count = getGroupSize(position);
-
- final String number = c.getString(CallLogQuery.NUMBER);
+ final String phoneNumber = c.getString(CallLogQuery.NUMBER);
+ Pattern pattern = Pattern.compile("[,;]");
+ String[] num = pattern.split(phoneNumber);
final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
final String postDialDigits = CompatUtils.isNCompatible()
&& mActivityType != ACTIVITY_TYPE_ARCHIVE ?
c.getString(CallLogQuery.POST_DIAL_DIGITS) : "";
+ final String number = DialerUtils.isConferenceURICallLog(phoneNumber, postDialDigits) ?
+ phoneNumber : num.length > 0 ? num[0] : "";
final String viaNumber = CompatUtils.isNCompatible()
&& mActivityType != ACTIVITY_TYPE_ARCHIVE ?
c.getString(CallLogQuery.VIA_NUMBER) : "";
@@ -501,6 +523,7 @@
final PhoneAccountHandle accountHandle = PhoneAccountUtils.getAccount(
c.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME),
c.getString(CallLogQuery.ACCOUNT_ID));
+ final Drawable accountIcon = mCallLogCache.getAccountIcon(accountHandle);
final ContactInfo cachedContactInfo = ContactInfoHelper.getContactInfo(c);
final boolean isVoicemailNumber =
mCallLogCache.isVoicemailNumber(accountHandle, number);
@@ -511,8 +534,11 @@
ContactInfo info = ContactInfo.EMPTY;
if (PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemailNumber) {
// Lookup contacts with this number
- info = mContactInfoCache.getValue(number + postDialDigits,
- countryIso, cachedContactInfo);
+ boolean isConfCallLog = num != null && num.length > 1
+ && DialerUtils.isConferenceURICallLog(phoneNumber, postDialDigits);
+ String queryNumber = isConfCallLog ? phoneNumber : number;
+ info = mContactInfoCache.getValue(queryNumber, postDialDigits,
+ countryIso, cachedContactInfo, isConfCallLog);
}
CharSequence formattedNumber = info.formattedNumber == null
? null : PhoneNumberUtilsCompat.createTtsSpannable(info.formattedNumber);
@@ -522,6 +548,7 @@
postDialDigits, isVoicemailNumber);
details.viaNumber = viaNumber;
details.accountHandle = accountHandle;
+ details.accountIcon = accountIcon;
details.countryIso = countryIso;
details.date = c.getLong(CallLogQuery.DATE);
details.duration = c.getLong(CallLogQuery.DURATION);
@@ -592,7 +619,7 @@
views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
}
- mCallLogListItemHelper.setPhoneCallDetails(views, details);
+ mCallLogListItemHelper.setPhoneCallDetails(views, details, mFilterString);
if (mCurrentlyExpandedRowId == views.rowId) {
// In case ViewHolders were added/removed, update the expanded position if the rowIds
@@ -604,7 +631,7 @@
}
views.updatePhoto();
- mCallLogListItemHelper.setPhoneCallDetails(views, details);
+ mCallLogListItemHelper.setPhoneCallDetails(views, details, mFilterString);
}
private String getPreferredDisplayName(ContactInfo contactInfo) {
@@ -915,4 +942,8 @@
PromoCardViewHolder viewHolder = PromoCardViewHolder.create(view);
return viewHolder;
}
+
+ public void setQueryString(String filter) {
+ mFilterString = filter;
+ }
}
diff --git a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
index 34b2f0e..667d6b9 100644
--- a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
+++ b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
@@ -41,11 +41,13 @@
import com.android.dialer.util.AsyncTaskExecutor;
import com.android.dialer.util.AsyncTaskExecutors;
import com.android.dialer.util.PhoneNumberUtil;
+import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.TelecomUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
+import java.util.regex.Pattern;
public class CallLogAsyncTaskUtil {
private static String TAG = CallLogAsyncTaskUtil.class.getSimpleName();
@@ -208,13 +210,20 @@
PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemail;
ContactInfo info = ContactInfo.EMPTY;
+ Pattern pattern = Pattern.compile("[,;]");
+ String[] num = pattern.split(number);
+ boolean isConf = num != null && num.length > 1
+ && DialerUtils.isConferenceURICallLog(number, postDialDigits);
+ String phoneNumber = num != null && num.length > 0 ? num[0] : "";
+ String queryNumber = isConf ? number : phoneNumber;
if (shouldLookupNumber) {
- ContactInfo lookupInfo = contactInfoHelper.lookupNumber(number, countryIso);
+ ContactInfo lookupInfo = contactInfoHelper.lookupNumber(queryNumber, postDialDigits,
+ countryIso, isConf);
info = lookupInfo != null ? lookupInfo : ContactInfo.EMPTY;
}
PhoneCallDetails details = new PhoneCallDetails(
- context, number, numberPresentation, info.formattedNumber,
+ context, queryNumber, numberPresentation, info.formattedNumber,
postDialDigits, isVoicemail);
details.viaNumber = viaNumber;
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index 67b72a5..96551a4 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -82,8 +82,8 @@
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
- private CallLogAdapter mAdapter;
- private CallLogQueryHandler mCallLogQueryHandler;
+ protected CallLogAdapter mAdapter;
+ protected CallLogQueryHandler mCallLogQueryHandler;
private boolean mScrollToTop;
@@ -354,14 +354,13 @@
@Override
public void onPause() {
cancelDisplayUpdate();
- mAdapter.onPause();
super.onPause();
}
@Override
public void onStop() {
updateOnTransition();
-
+ mAdapter.onStop();
super.onStop();
}
@@ -414,7 +413,11 @@
messageId = R.string.call_log_voicemail_empty;
break;
case CallLogQueryHandler.CALL_TYPE_ALL:
- messageId = R.string.call_log_all_empty;
+ if (mIsCallLogActivity) {
+ messageId = R.string.no_call_log;
+ } else {
+ messageId = R.string.recentCalls_empty;
+ }
break;
default:
throw new IllegalArgumentException("Unexpected filter type in CallLogFragment: "
@@ -424,6 +427,7 @@
if (mIsCallLogActivity) {
mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL);
} else if (filterType == CallLogQueryHandler.CALL_TYPE_ALL) {
+ mEmptyListView.setImage(R.drawable.empty_call_log);
mEmptyListView.setActionLabel(R.string.call_log_all_empty_action);
}
}
diff --git a/src/com/android/dialer/calllog/CallLogGroupBuilder.java b/src/com/android/dialer/calllog/CallLogGroupBuilder.java
index aa45029..ab9d535 100644
--- a/src/com/android/dialer/calllog/CallLogGroupBuilder.java
+++ b/src/com/android/dialer/calllog/CallLogGroupBuilder.java
@@ -27,6 +27,9 @@
import com.android.contacts.common.util.DateUtils;
import com.android.contacts.common.util.PhoneNumberHelper;
import com.android.dialer.util.AppCompatConstants;
+import com.android.dialer.util.DialerUtils;
+
+import java.util.regex.Pattern;
/**
* Groups together calls in the call log. The primary grouping attempts to group together calls
@@ -130,6 +133,8 @@
int groupCallType = cursor.getInt(CallLogQuery.CALL_TYPE);
String groupAccountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
String groupAccountId = cursor.getString(CallLogQuery.ACCOUNT_ID);
+ boolean isGroupConfCallLog = DialerUtils.isConferenceURICallLog(groupNumber,
+ groupPostDialDigits);
int groupSize = 1;
String number;
@@ -138,6 +143,7 @@
int callType;
String accountComponentName;
String accountId;
+ boolean isNumberConfCallLog = false;
while (cursor.moveToNext()) {
// Obtain the values for the current call to group.
@@ -149,8 +155,10 @@
callType = cursor.getInt(CallLogQuery.CALL_TYPE);
accountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
accountId = cursor.getString(CallLogQuery.ACCOUNT_ID);
+ isNumberConfCallLog = DialerUtils.isConferenceURICallLog(number, numberPostDialDigits);
- final boolean isSameNumber = equalNumbers(groupNumber, number);
+ final boolean isSameNumber = equalNumbers(groupNumber, isGroupConfCallLog,
+ number, isNumberConfCallLog);
final boolean isSamePostDialDigits = groupPostDialDigits.equals(numberPostDialDigits);
final boolean isSameViaNumbers = groupViaNumbers.equals(numberViaNumbers);
final boolean isSameAccount = isSameAccount(
@@ -184,6 +192,8 @@
groupCallType = callType;
groupAccountComponentName = accountComponentName;
groupAccountId = accountId;
+ isGroupConfCallLog = DialerUtils.isConferenceURICallLog(groupNumber,
+ groupPostDialDigits);
}
// Save the day group associated with the current call.
@@ -224,8 +234,27 @@
@VisibleForTesting
boolean equalNumbers(String number1, String number2) {
+ return equalNumbers(number1, false, number2, false);
+ }
+
+ boolean equalNumbers(String number1, boolean isConf1, String number2, boolean isConf2) {
if (PhoneNumberHelper.isUriNumber(number1) || PhoneNumberHelper.isUriNumber(number2)) {
return compareSipAddresses(number1, number2);
+ } else if (isConf1 && isConf2) {
+ Pattern pattern = Pattern.compile("[,;]");
+ String[] num1 = pattern.split(number1);
+ String[] num2 = pattern.split(number2);
+ if (num1 == null || num2 == null || num1.length != num2.length) {
+ return false;
+ }
+ for (int i = 0; i < num1.length; i++) {
+ if (!PhoneNumberUtils.compare(num1[i], num2[i])) {
+ return false;
+ }
+ }
+ return true;
+ } else if (isConf1 != isConf2) {
+ return false;
} else {
return PhoneNumberUtils.compare(number1, number2);
}
diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java
index 07e2bb4..0aa9fdc 100644
--- a/src/com/android/dialer/calllog/CallLogListItemHelper.java
+++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java
@@ -63,8 +63,8 @@
*/
public void setPhoneCallDetails(
CallLogListItemViewHolder views,
- PhoneCallDetails details) {
- mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details);
+ PhoneCallDetails details, String filter) {
+ mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details, filter);
// Set the accessibility text for the contact badge
views.quickContactView.setContentDescription(getContactBadgeDescription(details));
diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
index baf2e1a..d63531b 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
@@ -20,6 +20,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
@@ -60,6 +62,7 @@
import com.android.dialer.service.ExtendedBlockingButtonRenderer;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.PhoneNumberUtil;
+import com.android.dialer.util.PresenceHelper;
import com.android.dialer.voicemail.VoicemailPlaybackLayout;
import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
import com.android.dialerbind.ObjectFactory;
@@ -67,6 +70,9 @@
import java.util.List;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+import org.codeaurora.presenceserv.IPresenceService;
+
/**
* This is an object containing references to views contained by the call log list item. This
* improves performance by reducing the frequency with which we need to find views by IDs.
@@ -104,6 +110,7 @@
public View detailsButtonView;
public View callWithNoteButtonView;
public ImageView workIconView;
+ public ImageView videoCallIconView;
/**
* The row Id for the first call associated with the call log entry. Used as a key for the
@@ -418,6 +425,8 @@
videoCallButtonView = actionsView.findViewById(R.id.video_call_action);
videoCallButtonView.setOnClickListener(this);
+ videoCallIconView = (ImageView) actionsView.findViewById(R.id.videoCallIcon);
+
createNewContactButtonView = actionsView.findViewById(R.id.create_new_contact_action);
createNewContactButtonView.setOnClickListener(this);
@@ -504,12 +513,22 @@
} else {
callButtonView.setVisibility(View.GONE);
}
-
- // If one of the calls had video capabilities, show the video call button.
- if (mCallLogCache.isVideoEnabled() && canPlaceCallToNumber &&
- phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
+ boolean isPresenceEnabled = mContext.getResources().getBoolean(
+ R.bool.config_regional_presence_enable);
+ boolean showVideoCallBtn = isPresenceEnabled ? PresenceHelper.startAvailabilityFetch(number)
+ : phoneCallDetailsViews.callTypeIcons.isVideoShown();
+ //If presence is enabled,only both of sides had video capabilities,
+ //show the video call button,If not, one of the calls had video capabilities,
+ //the video call button will be shown.
+ if (mCallLogCache.isVideoEnabled() && canPlaceCallToNumber && showVideoCallBtn) {
videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
videoCallButtonView.setVisibility(View.VISIBLE);
+ if (QtiImsExtUtils.isCarrierOneSupported()) {
+ Drawable drawable = mContext.getResources().getDrawable(R.drawable.volte_video);
+ drawable.setColorFilter(mContext.getResources().getColor(
+ R.color.dialtacts_secondary_text_color), PorterDuff.Mode.MULTIPLY);
+ videoCallIconView.setImageDrawable(drawable);
+ }
} else {
videoCallButtonView.setVisibility(View.GONE);
}
@@ -691,6 +710,9 @@
if (intentProvider != null) {
final Intent intent = intentProvider.getIntent(mContext);
// See IntentProvider.getCallDetailIntentProvider() for why this may be null.
+ if (DialerUtils.isConferenceURICallLog(number, postDialDigits)) {
+ intent.putExtra("org.codeaurora.extra.DIAL_CONFERENCE_URI", true);
+ }
if (intent != null) {
DialerUtils.startActivityWithErrorToast(mContext, intent);
}
@@ -729,4 +751,4 @@
viewHolder.workIconView = new ImageButton(context);
return viewHolder;
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java
index cf86bad..116d9ef 100644
--- a/src/com/android/dialer/calllog/CallLogQueryHandler.java
+++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java
@@ -50,6 +50,8 @@
/** Handles asynchronous queries to the call log. */
public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler {
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
private static final String TAG = "CallLogQueryHandler";
private static final int NUM_LOGS_TO_DISPLAY = 1000;
@@ -76,6 +78,11 @@
*/
public static final int CALL_TYPE_ALL = -1;
+ /**
+ * To specify all slots.
+ */
+ public static final int CALL_SUB_ALL = -1;
+
private final WeakReference<Listener> mListener;
private final Context mContext;
@@ -155,10 +162,33 @@
}
}
+ public void fetchCalls(int callType, long newerThan, int sub) {
+ cancelFetch();
+ if (PermissionsUtil.hasPhonePermissions(mContext)) {
+ fetchCalls(QUERY_CALLLOG_TOKEN, callType, false /* newOnly */, newerThan, sub);
+ } else {
+ updateAdapterData(null);
+ }
+ }
+
public void fetchCalls(int callType) {
fetchCalls(callType, 0);
}
+ public void fetchCalls(String filter) {
+ cancelFetch();
+ fetchCalls(QUERY_CALLLOG_TOKEN, filter);
+ }
+
+ public void fetchCalls(int token, String filter) {
+ String selection = "(" + Calls.NUMBER + " like '%" + filter
+ + "%' or " + Calls.CACHED_NAME + " like '%" + filter + "%' )";
+
+ startQuery(token, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
+ CallLogQuery._PROJECTION, selection, null,
+ Calls.DEFAULT_SORT_ORDER);
+ }
+
public void fetchVoicemailStatus() {
if (TelecomUtil.hasReadWriteVoicemailPermissions(mContext)) {
startQuery(QUERY_VOICEMAIL_STATUS_TOKEN, null, Status.CONTENT_URI,
@@ -195,13 +225,35 @@
}
if (callType > CALL_TYPE_ALL) {
- where.append(" AND (").append(Calls.TYPE).append(" = ?)");
+ if (where.length() > 0) {
+ where.append(" AND ");
+ }
+
+ if ((callType == Calls.INCOMING_TYPE) || (callType == Calls.OUTGOING_TYPE)
+ || (callType == Calls.MISSED_TYPE)) {
+ where.append(String.format("(%s = ? OR %s = ? OR %s = ?)",
+ Calls.TYPE, Calls.TYPE, Calls.TYPE));
+ } else {
+ where.append(String.format("(%s = ?)", Calls.TYPE));
+ }
selectionArgs.add(Integer.toString(callType));
+ if (callType == Calls.INCOMING_TYPE) {
+ selectionArgs.add(Integer.toString(AppCompatConstants.INCOMING_IMS_TYPE));
+ selectionArgs.add(Integer.toString(AppCompatConstants.INCOMING_WIFI_TYPE));
+ } else if (callType == Calls.OUTGOING_TYPE) {
+ selectionArgs.add(Integer.toString(AppCompatConstants.OUTGOING_IMS_TYPE));
+ selectionArgs.add(Integer.toString(AppCompatConstants.OUTGOING_WIFI_TYPE));
+ } else if (callType == Calls.MISSED_TYPE) {
+ selectionArgs.add(Integer.toString(AppCompatConstants.MISSED_IMS_TYPE));
+ selectionArgs.add(Integer.toString(AppCompatConstants.MISSED_WIFI_TYPE));
+ }
} else {
+ // Add a clause to fetch only items of type voicemail.
where.append(" AND NOT ");
where.append("(" + Calls.TYPE + " = " + AppCompatConstants.CALLS_VOICEMAIL_TYPE + ")");
}
+ // Add a clause to fetch only items newer than the requested date
if (newerThan > 0) {
where.append(" AND (").append(Calls.DATE).append(" > ?)");
selectionArgs.add(Long.toString(newerThan));
@@ -216,6 +268,74 @@
new String[selectionArgs.size()]), Calls.DEFAULT_SORT_ORDER);
}
+ private void fetchCalls(int token, int callType, boolean newOnly,
+ long newerThan, int sub) {
+ // We need to check for NULL explicitly otherwise entries with where READ is NULL
+ // may not match either the query or its negation.
+ // We consider the calls that are not yet consumed (i.e. IS_READ = 0) as "new".
+ StringBuilder where = new StringBuilder();
+ List<String> selectionArgs = Lists.newArrayList();
+
+ // Ignore voicemails marked as deleted
+ where.append(Voicemails.DELETED);
+ where.append(" = 0");
+
+ if (newOnly) {
+ where.append(" AND ");
+ where.append(Calls.NEW);
+ where.append(" = 1");
+ }
+
+ if (callType > CALL_TYPE_ALL) {
+ where.append(" AND ");
+ if ((callType == Calls.INCOMING_TYPE) || (callType == Calls.OUTGOING_TYPE)
+ || (callType == Calls.MISSED_TYPE)) {
+ where.append(String.format("(%s = ? OR %s = ? OR %s = ?)",
+ Calls.TYPE, Calls.TYPE, Calls.TYPE));
+ } else {
+ // Add a clause to fetch only items of type voicemail.
+ where.append(String.format("(%s = ?)", Calls.TYPE));
+ }
+ // Add a clause to fetch only items newer than the requested date
+ selectionArgs.add(Integer.toString(callType));
+ if (callType == Calls.INCOMING_TYPE) {
+ selectionArgs.add(Integer.toString(AppCompatConstants.INCOMING_IMS_TYPE));
+ selectionArgs.add(Integer.toString(AppCompatConstants.INCOMING_WIFI_TYPE));
+ } else if (callType == Calls.OUTGOING_TYPE) {
+ selectionArgs.add(Integer.toString(AppCompatConstants.OUTGOING_IMS_TYPE));
+ selectionArgs.add(Integer.toString(AppCompatConstants.OUTGOING_WIFI_TYPE));
+ } else if (callType == Calls.MISSED_TYPE) {
+ selectionArgs.add(Integer.toString(AppCompatConstants.MISSED_IMS_TYPE));
+ selectionArgs.add(Integer.toString(AppCompatConstants.MISSED_WIFI_TYPE));
+ }
+ } else {
+ where.append(" AND NOT ");
+ where.append("(" + Calls.TYPE + " = " + Calls.VOICEMAIL_TYPE + ")");
+ }
+
+ if (sub > CALL_SUB_ALL) {
+ where.append(" AND ");
+ where.append(String.format("(%s = ?)", Calls.PHONE_ACCOUNT_ID));
+ selectionArgs.add(Integer.toString(sub));
+ }
+
+ if (newerThan > 0) {
+ where.append(" AND ");
+ where.append(String.format("(%s > ?)", Calls.DATE));
+ selectionArgs.add(Long.toString(newerThan));
+ }
+
+ final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit;
+ final String selection = where.length() > 0 ? where.toString() : null;
+ Uri uri = TelecomUtil.getCallLogUri(mContext).buildUpon()
+ .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit))
+ .build();
+ startQuery(token, null, uri,
+ CallLogQuery._PROJECTION, selection, selectionArgs.toArray(EMPTY_STRING_ARRAY),
+ Calls.DEFAULT_SORT_ORDER);
+ }
+
+
/** Cancel any pending fetch request. */
private void cancelFetch() {
cancelOperation(QUERY_CALLLOG_TOKEN);
diff --git a/src/com/android/dialer/calllog/CallLogSearchFragment.java b/src/com/android/dialer/calllog/CallLogSearchFragment.java
new file mode 100644
index 0000000..4c8db97
--- /dev/null
+++ b/src/com/android/dialer/calllog/CallLogSearchFragment.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.dialer.calllog;
+
+import android.app.ListFragment;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.provider.CallLog.Calls;
+
+import com.android.contacts.common.GeoUtil;
+import com.android.dialer.R;
+import com.android.dialerbind.ObjectFactory;
+
+
+public class CallLogSearchFragment extends CallLogFragment {
+
+ private String mQueryString;
+
+ private void updateCallList(int filterType) {
+ mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL);
+ }
+
+ public void fetchCalls() {
+ if (TextUtils.isEmpty(mQueryString)) {
+ mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL);
+ } else {
+ mCallLogQueryHandler.fetchCalls(mQueryString);
+ }
+ }
+
+ public void startCallsQuery() {
+ mAdapter.setLoading(true);
+ if (TextUtils.isEmpty(mQueryString)) {
+ mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL);
+ } else {
+ mCallLogQueryHandler.fetchCalls(mQueryString);
+ }
+ }
+
+ public void setQueryString(String queryString) {
+ if (!TextUtils.equals(mQueryString, queryString)) {
+ mQueryString = queryString;
+ if (mAdapter != null) {
+ mAdapter.setLoading(true);
+ mAdapter.setQueryString(mQueryString);
+ if (TextUtils.isEmpty(queryString)) {
+ mCallLogQueryHandler
+ .fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL);
+ } else {
+ mCallLogQueryHandler.fetchCalls(queryString);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/dialer/calllog/CallTypeHelper.java b/src/com/android/dialer/calllog/CallTypeHelper.java
index acc114c..045b604 100644
--- a/src/com/android/dialer/calllog/CallTypeHelper.java
+++ b/src/com/android/dialer/calllog/CallTypeHelper.java
@@ -37,6 +37,30 @@
private final CharSequence mOutgoingVideoName;
/** Name used to identify missed video calls. */
private final CharSequence mMissedVideoName;
+ /** Name used to identify incoming video calls. */
+ private final CharSequence mIncomingVoLTEName;
+ /** Name used to identify outgoing video calls. */
+ private final CharSequence mOutgoingVoLTEName;
+ /** Name used to identify missed video calls. */
+ private final CharSequence mMissedVoLTEName;
+ /** Name used to identify incoming videoLTE calls. */
+ private final CharSequence mIncomingVideoLTEName;
+ /** Name used to identify outgoing videoLTE calls. */
+ private final CharSequence mOutgoingVideoLTEName;
+ /** Name used to identify missed videoLTE calls. */
+ private final CharSequence mMissedVideoLTEName;
+ /** Name used to identify incoming VoWifi calls. */
+ private final CharSequence mIncomingVoWifiName;
+ /** Name used to identify outgoing VoWifi calls. */
+ private final CharSequence mOutgoingVoWifiName;
+ /** Name used to identify missed VoWifi calls. */
+ private final CharSequence mMissedVoWifiName;
+ /** Name used to identify incoming video wifi calls. */
+ private final CharSequence mIncomingVideoWifiName;
+ /** Name used to identify outgoing video wifi calls. */
+ private final CharSequence mOutgoingVideoWifiName;
+ /** Name used to identify missed video wifi calls. */
+ private final CharSequence mMissedVideoWifiName;
/** Name used to identify voicemail calls. */
private final CharSequence mVoicemailName;
/** Name used to identify rejected calls. */
@@ -56,6 +80,18 @@
mIncomingVideoName = resources.getString(R.string.type_incoming_video);
mOutgoingVideoName = resources.getString(R.string.type_outgoing_video);
mMissedVideoName = resources.getString(R.string.type_missed_video);
+ mIncomingVoLTEName = resources.getString(R.string.type_incoming_volte);
+ mOutgoingVoLTEName = resources.getString(R.string.type_outgoing_volte);
+ mMissedVoLTEName = resources.getString(R.string.type_missed_volte);
+ mIncomingVideoLTEName = resources.getString(R.string.type_incoming_video_lte);
+ mOutgoingVideoLTEName = resources.getString(R.string.type_outgoing_video_lte);
+ mMissedVideoLTEName = resources.getString(R.string.type_missed_video_lte);
+ mIncomingVoWifiName = resources.getString(R.string.type_incoming_vowifi);
+ mOutgoingVoWifiName = resources.getString(R.string.type_outgoing_vowifi);
+ mMissedVoWifiName = resources.getString(R.string.type_missed_vowifi);
+ mIncomingVideoWifiName = resources.getString(R.string.type_incoming_video_wifi);
+ mOutgoingVideoWifiName = resources.getString(R.string.type_outgoing_video_wifi);
+ mMissedVideoWifiName = resources.getString(R.string.type_missed_video_wifi);
mVoicemailName = resources.getString(R.string.type_voicemail);
mRejectedName = resources.getString(R.string.type_rejected);
mBlockedName = resources.getString(R.string.type_blocked);
@@ -73,6 +109,20 @@
return mIncomingName;
}
+ case AppCompatConstants.INCOMING_IMS_TYPE:
+ if (isVideoCall) {
+ return mIncomingVideoLTEName;
+ } else {
+ return mIncomingVoLTEName;
+ }
+
+ case AppCompatConstants.INCOMING_WIFI_TYPE:
+ if (isVideoCall) {
+ return mIncomingVideoWifiName;
+ } else {
+ return mIncomingVoWifiName;
+ }
+
case AppCompatConstants.CALLS_OUTGOING_TYPE:
if (isVideoCall) {
return mOutgoingVideoName;
@@ -80,6 +130,20 @@
return mOutgoingName;
}
+ case AppCompatConstants.OUTGOING_IMS_TYPE:
+ if (isVideoCall) {
+ return mOutgoingVideoLTEName;
+ } else {
+ return mOutgoingVoLTEName;
+ }
+
+ case AppCompatConstants.OUTGOING_WIFI_TYPE:
+ if (isVideoCall) {
+ return mOutgoingVideoWifiName;
+ } else {
+ return mOutgoingVoWifiName;
+ }
+
case AppCompatConstants.CALLS_MISSED_TYPE:
if (isVideoCall) {
return mMissedVideoName;
@@ -87,6 +151,20 @@
return mMissedName;
}
+ case AppCompatConstants.MISSED_IMS_TYPE:
+ if (isVideoCall) {
+ return mMissedVideoLTEName;
+ } else {
+ return mMissedVoLTEName;
+ }
+
+ case AppCompatConstants.MISSED_WIFI_TYPE:
+ if (isVideoCall) {
+ return mMissedVideoWifiName;
+ } else {
+ return mMissedVoWifiName;
+ }
+
case AppCompatConstants.CALLS_VOICEMAIL_TYPE:
return mVoicemailName;
@@ -105,14 +183,20 @@
public Integer getHighlightedColor(int callType) {
switch (callType) {
case AppCompatConstants.CALLS_INCOMING_TYPE:
+ case AppCompatConstants.INCOMING_IMS_TYPE:
+ case AppCompatConstants.INCOMING_WIFI_TYPE:
// New incoming calls are not highlighted.
return null;
case AppCompatConstants.CALLS_OUTGOING_TYPE:
+ case AppCompatConstants.OUTGOING_IMS_TYPE:
+ case AppCompatConstants.OUTGOING_WIFI_TYPE:
// New outgoing calls are not highlighted.
return null;
case AppCompatConstants.CALLS_MISSED_TYPE:
+ case AppCompatConstants.MISSED_IMS_TYPE:
+ case AppCompatConstants.MISSED_WIFI_TYPE:
return mNewMissedColor;
case AppCompatConstants.CALLS_VOICEMAIL_TYPE:
@@ -129,6 +213,10 @@
public static boolean isMissedCallType(int callType) {
return (callType != AppCompatConstants.CALLS_INCOMING_TYPE
&& callType != AppCompatConstants.CALLS_OUTGOING_TYPE
+ && callType != AppCompatConstants.INCOMING_IMS_TYPE
+ && callType != AppCompatConstants.OUTGOING_IMS_TYPE
+ && callType != AppCompatConstants.INCOMING_WIFI_TYPE
+ && callType != AppCompatConstants.OUTGOING_WIFI_TYPE
&& callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE);
}
}
diff --git a/src/com/android/dialer/calllog/CallTypeIconsView.java b/src/com/android/dialer/calllog/CallTypeIconsView.java
index 1474843..56bd605 100644
--- a/src/com/android/dialer/calllog/CallTypeIconsView.java
+++ b/src/com/android/dialer/calllog/CallTypeIconsView.java
@@ -34,6 +34,8 @@
import java.util.List;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
/**
* View that draws one or more symbols for different types of calls (missed calls, outgoing etc).
* The symbols are set up horizontally. As this view doesn't create subviews, it is better suited
@@ -47,12 +49,15 @@
private static Resources sResources;
+ private static boolean mIsCarrierOneSupported = false;
+
public CallTypeIconsView(Context context) {
this(context, null);
}
public CallTypeIconsView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mIsCarrierOneSupported = QtiImsExtUtils.isCarrierOneSupported();
if (sResources == null) {
sResources = new Resources(context);
}
@@ -74,6 +79,40 @@
invalidate();
}
+ public void addImsOrVideoIcon(int callType, boolean showVideo) {
+ mShowVideo = showVideo;
+ if (showVideo) {
+ mWidth += sResources.videoCall.getIntrinsicWidth();
+ mHeight = Math.max(mHeight, sResources.videoCall.getIntrinsicHeight());
+ invalidate();
+ } else {
+ final Drawable drawable = getImsOrWifiDrawable(callType);
+ if (drawable != null) {
+ // calculating drawable's width and adding it to total width for correct position
+ // of icon.
+ // calculating height by max of drawable height and other icons' height.
+ mWidth += drawable.getIntrinsicWidth();
+ mHeight = Math.max(mHeight, drawable.getIntrinsicHeight());
+ invalidate();
+ }
+ }
+ }
+
+ private Drawable getImsOrWifiDrawable(int callType) {
+ switch(callType) {
+ case AppCompatConstants.INCOMING_IMS_TYPE:
+ case AppCompatConstants.OUTGOING_IMS_TYPE:
+ case AppCompatConstants.MISSED_IMS_TYPE:
+ return sResources.imsCall;
+ case AppCompatConstants.INCOMING_WIFI_TYPE:
+ case AppCompatConstants.OUTGOING_WIFI_TYPE:
+ case AppCompatConstants.MISSED_WIFI_TYPE:
+ return sResources.wifiCall;
+ default:
+ return null;
+ }
+ }
+
/**
* Determines whether the video call icon will be shown.
*
@@ -81,6 +120,12 @@
*/
public void setShowVideo(boolean showVideo) {
mShowVideo = showVideo;
+ if (mIsCarrierOneSupported) {
+ // Don't show video icon in call log item. For CarrierOne, show more precise icon
+ // based on call type in call detail history.
+ return;
+ }
+
if (showVideo) {
mWidth += sResources.videoCall.getIntrinsicWidth();
mHeight = Math.max(mHeight, sResources.videoCall.getIntrinsicHeight());
@@ -110,10 +155,16 @@
private Drawable getCallTypeDrawable(int callType) {
switch (callType) {
case AppCompatConstants.CALLS_INCOMING_TYPE:
+ case AppCompatConstants.INCOMING_IMS_TYPE:
+ case AppCompatConstants.INCOMING_WIFI_TYPE:
return sResources.incoming;
case AppCompatConstants.CALLS_OUTGOING_TYPE:
+ case AppCompatConstants.OUTGOING_IMS_TYPE:
+ case AppCompatConstants.OUTGOING_WIFI_TYPE:
return sResources.outgoing;
case AppCompatConstants.CALLS_MISSED_TYPE:
+ case AppCompatConstants.MISSED_IMS_TYPE:
+ case AppCompatConstants.MISSED_WIFI_TYPE:
return sResources.missed;
case AppCompatConstants.CALLS_VOICEMAIL_TYPE:
return sResources.voicemail;
@@ -147,9 +198,19 @@
// If showing the video call icon, draw it scaled appropriately.
if (mShowVideo) {
final Drawable drawable = sResources.videoCall;
- final int right = left + sResources.videoCall.getIntrinsicWidth();
- drawable.setBounds(left, 0, right, sResources.videoCall.getIntrinsicHeight());
+ final int right = left + drawable.getIntrinsicWidth();
+ drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight());
drawable.draw(canvas);
+ left = right + sResources.iconMargin;
+ }
+
+ for (Integer callType : mCallTypes) {
+ final Drawable drawableIms = getImsOrWifiDrawable(callType);
+ if (drawableIms != null) {
+ final int right = left + drawableIms.getIntrinsicWidth();
+ drawableIms.setBounds(left, 0, right, drawableIms.getIntrinsicHeight());
+ drawableIms.draw(canvas);
+ }
}
}
@@ -179,6 +240,15 @@
public final int iconMargin;
/**
+ * Drawable repesenting a wifi call.
+ */
+ public final Drawable wifiCall;
+
+ /**
+ * Drawable repesenting a IMS call.
+ */
+ public final Drawable imsCall;
+ /**
* Configures the call icon drawables.
* A single white call arrow which points down and left is used as a basis for all of the
* call arrow icons, applying rotation and colors as needed.
@@ -205,11 +275,26 @@
blocked = getScaledBitmap(context, R.drawable.ic_block_24dp);
blocked.setColorFilter(r.getColor(R.color.blocked_call), PorterDuff.Mode.MULTIPLY);
- videoCall = getScaledBitmap(context, R.drawable.ic_videocam_24dp);
+ if (mIsCarrierOneSupported) {
+ videoCall = r.getDrawable(R.drawable.volte_video).mutate();
+ } else {
+ // Get the video call icon, scaled to match the height of the call arrows.
+ // We want the video call icon to be the same height as the call arrows, while keeping
+ // the same width aspect ratio.
+ videoCall = getScaledBitmap(context, R.drawable.ic_videocam_24dp);
+ }
videoCall.setColorFilter(r.getColor(R.color.dialtacts_secondary_text_color),
PorterDuff.Mode.MULTIPLY);
iconMargin = r.getDimensionPixelSize(R.dimen.call_log_icon_margin);
+
+ wifiCall = r.getDrawable(R.drawable.wifi_calling).mutate();
+ wifiCall.setColorFilter(r.getColor(R.color.dialtacts_secondary_text_color),
+ PorterDuff.Mode.MULTIPLY);
+
+ imsCall = r.getDrawable(R.drawable.volte_voice).mutate();
+ imsCall.setColorFilter(r.getColor(R.color.dialtacts_secondary_text_color),
+ PorterDuff.Mode.MULTIPLY);
}
// Gets the icon, scaled to the height of the call type icons. This helps display all the
diff --git a/src/com/android/dialer/calllog/ClearCallLogDialog.java b/src/com/android/dialer/calllog/ClearCallLogDialog.java
index bef5010..457f1df 100644
--- a/src/com/android/dialer/calllog/ClearCallLogDialog.java
+++ b/src/com/android/dialer/calllog/ClearCallLogDialog.java
@@ -88,10 +88,9 @@
};
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.clearCallLogConfirmation_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(R.string.clearCallLogConfirmation)
.setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, okListener)
+ .setPositiveButton(R.string.clear, okListener)
.setCancelable(true)
.create();
}
diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java
index b0ef0ab..ea39d03 100644
--- a/src/com/android/dialer/calllog/ContactInfoHelper.java
+++ b/src/com/android/dialer/calllog/ContactInfoHelper.java
@@ -43,6 +43,8 @@
import com.android.dialer.util.TelecomUtil;
import com.android.dialerbind.ObjectFactory;
+import java.util.regex.Pattern;
+
import org.json.JSONException;
import org.json.JSONObject;
@@ -76,6 +78,26 @@
*/
@Nullable
public ContactInfo lookupNumber(String number, String countryIso) {
+ return lookupNumber(number, null, countryIso, false);
+ }
+
+ /**
+ * Returns the contact information for the given number.
+ * <p>
+ * If the number does not match any contact, returns a contact info containing only the number
+ * and the formatted number.
+ * <p>
+ * If an error occurs during the lookup, it returns null.
+ *
+ * @param number the number to look up
+ * @param postDialString append into number if required
+ * @param countryIso the country associated with this number
+ * @param isConfUrlLog whether call log is for Conference URL call
+ */
+ @Nullable
+ public ContactInfo lookupNumber(String number, String postDialString, String countryIso,
+ boolean isConfUrlCallLog) {
+
if (TextUtils.isEmpty(number)) {
return null;
}
@@ -89,12 +111,14 @@
// If lookup failed, check if the "username" of the SIP address is a phone number.
String username = PhoneNumberHelper.getUsernameFromUriNumber(number);
if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
- info = queryContactInfoForPhoneNumber(username, countryIso, true);
+ info = queryContactInfoForPhoneNumber(username, null, countryIso, true,
+ isConfUrlCallLog);
}
}
} else {
// Look for a contact that has the given phone number.
- info = queryContactInfoForPhoneNumber(number, countryIso, false);
+ info = queryContactInfoForPhoneNumber(number, postDialString, countryIso,
+ false, isConfUrlCallLog);
}
final ContactInfo updatedInfo;
@@ -106,6 +130,9 @@
if (info == ContactInfo.EMPTY) {
// Did not find a matching contact.
updatedInfo = new ContactInfo();
+ if (!isConfUrlCallLog && !TextUtils.isEmpty(postDialString)) {
+ number += postDialString;
+ }
updatedInfo.number = number;
updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
updatedInfo.normalizedNumber = PhoneNumberUtils.formatNumberToE164(
@@ -244,14 +271,47 @@
* <p>
* If the lookup fails for some other reason, it returns null.
*/
- private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso,
- boolean isSip) {
+ private ContactInfo queryContactInfoForPhoneNumber(String number, String postDialString,
+ String countryIso, boolean isSip, boolean isConfUrlLog) {
if (TextUtils.isEmpty(number)) {
return null;
}
ContactInfo info = lookupContactFromUri(getContactInfoLookupUri(number), isSip);
+ if (isConfUrlLog) {
+ Pattern pattern = Pattern.compile("[,;]");
+ String[] nums = pattern.split(number);
+ if (nums != null && nums.length > 1) {
+ if (info == null || info == ContactInfo.EMPTY) {
+ info = new ContactInfo();
+ info.number = number;
+ info.formattedNumber = formatPhoneNumber(number, null, countryIso);
+ info.lookupUri = createTemporaryContactUri(info.formattedNumber);
+ info.normalizedNumber = PhoneNumberUtils.formatNumberToE164(number,
+ countryIso);
+ }
+ String combName = "";
+ for (String num : nums) {
+ ContactInfo singleCi = lookupContactFromUri(getContactInfoLookupUri(num),
+ isSip);
+ // If contact does not exist, need to avoid changing static empty-contact.
+ if (singleCi == ContactInfo.EMPTY) {
+ singleCi = new ContactInfo();
+ }
+ if (TextUtils.isEmpty(singleCi.name)) {
+ singleCi.name = formatPhoneNumber(num, null, countryIso);
+ }
+ combName += singleCi.name + ";";
+ }
+ if (!TextUtils.isEmpty(combName) && combName.length() > 1) {
+ info.name = combName.substring(0, combName.length() - 1);
+ }
+ }
+ }
if (info != null && info != ContactInfo.EMPTY) {
+ if (!isConfUrlLog && TextUtils.isEmpty(postDialString)) {
+ number += postDialString;
+ }
info.formattedNumber = formatPhoneNumber(number, null, countryIso);
} else if (mCachedNumberLookupService != null) {
CachedContactInfo cacheInfo =
diff --git a/src/com/android/dialer/calllog/MSimCallLogFragment.java b/src/com/android/dialer/calllog/MSimCallLogFragment.java
new file mode 100644
index 0000000..1eefe53
--- /dev/null
+++ b/src/com/android/dialer/calllog/MSimCallLogFragment.java
@@ -0,0 +1,658 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved
+ * Not a Contribution.
+ * Copyright (C) 2011 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.dialer.calllog;
+
+import static android.Manifest.permission.READ_CALL_LOG;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.KeyguardManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
+import android.provider.VoicemailContract.Status;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.LinearLayoutManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.util.PermissionsUtil;
+import com.android.contacts.common.util.ViewUtil;
+import com.android.dialer.R;
+import com.android.dialer.list.ListsFragment.HostInterface;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.EmptyLoader;
+import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
+import com.android.dialer.voicemail.VoicemailStatusHelper;
+import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;
+import com.android.dialer.voicemail.VoicemailStatusHelperImpl;
+import com.android.dialer.widget.EmptyContentView;
+import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
+import com.android.dialerbind.ObjectFactory;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.util.Log;
+import android.preference.PreferenceManager;
+import android.telephony.SubscriptionManager;
+
+import java.util.List;
+
+/**
+ * Displays a list of call log entries. To filter for a particular kind of call
+ * (all, missed or voicemails), specify it in the constructor.
+ */
+public class MSimCallLogFragment extends Fragment implements CallLogQueryHandler.Listener,
+ CallLogAdapter.CallFetcher, OnEmptyViewActionButtonClickedListener {
+ private static final String TAG = "CallLogFragment";
+
+ /**
+ * ID of the empty loader to defer other fragments.
+ */
+ private static final int EMPTY_LOADER_ID = 0;
+
+ private static final String KEY_FILTER_TYPE = "filter_type";
+ private static final String KEY_LOG_LIMIT = "log_limit";
+ private static final String KEY_DATE_LIMIT = "date_limit";
+
+ // No limit specified for the number of logs to show; use the CallLogQueryHandler's default.
+ private static final int NO_LOG_LIMIT = -1;
+ // No date-based filtering.
+ private static final int NO_DATE_LIMIT = 0;
+
+ private static final int READ_CALL_LOG_PERMISSION_REQUEST_CODE = 1;
+
+ private RecyclerView mRecyclerView;
+ private LinearLayoutManager mLayoutManager;
+ private CallLogAdapter mAdapter;
+ protected CallLogQueryHandler mCallLogQueryHandler;
+ private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
+ private boolean mScrollToTop;
+
+ /** Whether there is at least one voicemail source installed. */
+ private boolean mVoicemailSourcesAvailable = false;
+
+ private EmptyContentView mEmptyListView;
+ private KeyguardManager mKeyguardManager;
+
+ private boolean mEmptyLoaderRunning;
+ private boolean mCallLogFetched;
+ private boolean mVoicemailStatusFetched;
+
+ private final Handler mHandler = new Handler();
+
+ // The Spinners to filter call log.
+ private Spinner mFilterSubSpinnerView;
+ private Spinner mFilterStatusSpinnerView;
+ // Default to all slots.
+ private int mCallSubFilter = CallLogQueryHandler.CALL_SUB_ALL;
+ /**
+ * Key for the call log sub saved in the default preference.
+ */
+ private static final String PREFERENCE_KEY_CALLLOG_SUB = "call_log_sub";
+
+ private class CustomContentObserver extends ContentObserver {
+ public CustomContentObserver() {
+ super(mHandler);
+ }
+ @Override
+ public void onChange(boolean selfChange) {
+ mRefreshDataRequired = true;
+ }
+ }
+
+ // See issue 6363009
+ private final ContentObserver mCallLogObserver = new CustomContentObserver();
+ private final ContentObserver mContactsObserver = new CustomContentObserver();
+ private final ContentObserver mVoicemailStatusObserver = new CustomContentObserver();
+ private boolean mRefreshDataRequired = true;
+
+ private boolean mHasReadCallLogPermission = false;
+
+ // Exactly same variable is in Fragment as a package private.
+ private boolean mMenuVisible = true;
+
+ // Default to all calls.
+ protected int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL;
+
+ // Log limit - if no limit is specified, then the default in {@link CallLogQueryHandler}
+ // will be used.
+ private int mLogLimit = NO_LOG_LIMIT;
+
+ // Date limit (in millis since epoch) - when non-zero, only calls which occurred on or after
+ // the date filter are included. If zero, no date-based filtering occurs.
+ private long mDateLimit = NO_DATE_LIMIT;
+
+ /*
+ * True if this instance of the CallLogFragment is the Recents screen shown in
+ * DialtactsActivity.
+ */
+ private boolean mIsRecentsFragment;
+
+ public interface HostInterface {
+ public void showDialpad();
+ }
+
+ public MSimCallLogFragment() {
+ this(CallLogQueryHandler.CALL_TYPE_ALL, NO_LOG_LIMIT);
+ }
+
+ public MSimCallLogFragment(int filterType) {
+ this(filterType, NO_LOG_LIMIT);
+ }
+
+ public MSimCallLogFragment(int filterType, int logLimit) {
+ this(filterType, logLimit, NO_DATE_LIMIT);
+ }
+
+ /**
+ * Creates a call log fragment, filtering to include only calls of the desired type, occurring
+ * after the specified date.
+ * @param filterType type of calls to include.
+ * @param dateLimit limits results to calls occurring on or after the specified date.
+ */
+ public MSimCallLogFragment(int filterType, long dateLimit) {
+ this(filterType, NO_LOG_LIMIT, dateLimit);
+ }
+
+ /**
+ * Creates a call log fragment, filtering to include only calls of the desired type, occurring
+ * after the specified date. Also provides a means to limit the number of results returned.
+ * @param filterType type of calls to include.
+ * @param logLimit limits the number of results to return.
+ * @param dateLimit limits results to calls occurring on or after the specified date.
+ */
+ public MSimCallLogFragment(int filterType, int logLimit, long dateLimit) {
+ mCallTypeFilter = filterType;
+ mLogLimit = logLimit;
+ mDateLimit = dateLimit;
+ }
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(state);
+ if (state != null) {
+ mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter);
+ mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit);
+ mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit);
+ }
+
+ mIsRecentsFragment = mLogLimit != NO_LOG_LIMIT;
+
+ final Activity activity = getActivity();
+ final ContentResolver resolver = activity.getContentResolver();
+ String currentCountryIso = GeoUtil.getCurrentCountryIso(activity);
+ mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit);
+ mKeyguardManager =
+ (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
+ resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver);
+ resolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true,
+ mContactsObserver);
+ resolver.registerContentObserver(Status.CONTENT_URI, true, mVoicemailStatusObserver);
+ setHasOptionsMenu(true);
+
+ if (mCallTypeFilter == Calls.VOICEMAIL_TYPE) {
+ mVoicemailPlaybackPresenter = VoicemailPlaybackPresenter
+ .getInstance(activity, state);
+ }
+ }
+
+ /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
+ @Override
+ public boolean onCallsFetched(Cursor cursor) {
+ if (getActivity() == null || getActivity().isFinishing()) {
+ // Return false; we did not take ownership of the cursor
+ return false;
+ }
+
+ mAdapter.setLoading(false);
+ mAdapter.changeCursor(cursor);
+ // This will update the state of the "Clear call log" menu item.
+ getActivity().invalidateOptionsMenu();
+
+ boolean showListView = cursor != null && cursor.getCount() > 0;
+ mRecyclerView.setVisibility(showListView ? View.VISIBLE : View.GONE);
+ mEmptyListView.setVisibility(!showListView ? View.VISIBLE : View.GONE);
+
+ if (mScrollToTop) {
+ // The smooth-scroll animation happens over a fixed time period.
+ // As a result, if it scrolls through a large portion of the list,
+ // each frame will jump so far from the previous one that the user
+ // will not experience the illusion of downward motion. Instead,
+ // if we're not already near the top of the list, we instantly jump
+ // near the top, and animate from there.
+ if (mLayoutManager.findFirstVisibleItemPosition() > 5) {
+ // TODO: Jump to near the top, then begin smooth scroll.
+ mRecyclerView.smoothScrollToPosition(0);
+ }
+ // Workaround for framework issue: the smooth-scroll doesn't
+ // occur if setSelection() is called immediately before.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (getActivity() == null || getActivity().isFinishing()) {
+ return;
+ }
+ mRecyclerView.smoothScrollToPosition(0);
+ }
+ });
+
+ mScrollToTop = false;
+ }
+ mCallLogFetched = true;
+ destroyEmptyLoaderIfAllDataFetched();
+ return true;
+ }
+
+ /**
+ * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider.
+ */
+ @Override
+ public void onVoicemailStatusFetched(Cursor statusCursor) {
+ Activity activity = getActivity();
+ if (activity == null || activity.isFinishing()) {
+ return;
+ }
+
+ mVoicemailStatusFetched = true;
+ destroyEmptyLoaderIfAllDataFetched();
+ }
+
+ @Override
+ public void onVoicemailUnreadCountFetched(Cursor cursor){
+ //to do somthing
+ }
+
+ @Override
+ public void onMissedCallsUnreadCountFetched(Cursor cursor){
+ //to do somthing
+ }
+
+ private void destroyEmptyLoaderIfAllDataFetched() {
+ if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) {
+ mEmptyLoaderRunning = false;
+ getLoaderManager().destroyLoader(EMPTY_LOADER_ID);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ View view = inflater.inflate(R.layout.msim_call_log_fragment, container, false);
+
+ mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
+ mRecyclerView.setHasFixedSize(true);
+ mLayoutManager = new LinearLayoutManager(getActivity());
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ mEmptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view);
+ mEmptyListView.setImage(R.drawable.empty_call_log);
+ mEmptyListView.setActionClickedListener(this);
+ mFilterSubSpinnerView = (Spinner) view.findViewById(R.id.filter_sub_spinner);
+ mFilterStatusSpinnerView = (Spinner) view.findViewById(R.id.filter_status_spinner);
+ // Update the filter views.
+ updateFilterSpinnerViews();
+
+ String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
+ mAdapter = ObjectFactory.newCallLogAdapter(
+ getActivity(),
+ this,
+ new ContactInfoHelper(getActivity(), currentCountryIso),
+ mVoicemailPlaybackPresenter,
+ CallLogAdapter.ACTIVITY_TYPE_CALL_LOG);
+ mRecyclerView.setAdapter(mAdapter);
+
+ fetchCalls();
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ updateEmptyMessage(mCallTypeFilter);
+ mAdapter.onRestoreInstanceState(savedInstanceState);
+ }
+
+ @Override
+ public void onStart() {
+ // Start the empty loader now to defer other fragments. We destroy it when both calllog
+ // and the voicemail status are fetched.
+ getLoaderManager().initLoader(EMPTY_LOADER_ID, null,
+ new EmptyLoader.Callback(getActivity()));
+ mEmptyLoaderRunning = true;
+ super.onStart();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ final boolean hasReadCallLogPermission =
+ PermissionsUtil.hasPermission(getActivity(), READ_CALL_LOG);
+ if (!mHasReadCallLogPermission && hasReadCallLogPermission) {
+ // We didn't have the permission before, and now we do. Force a refresh of the call log.
+ // Note that this code path always happens on a fresh start, but mRefreshDataRequired
+ // is already true in that case anyway.
+ mRefreshDataRequired = true;
+ updateEmptyMessage(mCallTypeFilter);
+ }
+ mHasReadCallLogPermission = hasReadCallLogPermission;
+ refreshData();
+ mAdapter.startCache();
+ }
+
+ @Override
+ public void onPause() {
+ if (mVoicemailPlaybackPresenter != null) {
+ mVoicemailPlaybackPresenter.onPause();
+ }
+ mAdapter.pauseCache();
+ super.onPause();
+ }
+
+ @Override
+ public void onStop() {
+ updateOnTransition(false /* onEntry */);
+
+ super.onStop();
+ }
+
+ @Override
+ public void onDestroy() {
+ mAdapter.pauseCache();
+ mAdapter.changeCursor(null);
+
+ if (mVoicemailPlaybackPresenter != null) {
+ mVoicemailPlaybackPresenter.onDestroy();
+ }
+
+ getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
+ getActivity().getContentResolver().unregisterContentObserver(mContactsObserver);
+ getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter);
+ outState.putInt(KEY_LOG_LIMIT, mLogLimit);
+ outState.putLong(KEY_DATE_LIMIT, mDateLimit);
+
+ mAdapter.onSaveInstanceState(outState);
+
+ if (mVoicemailPlaybackPresenter != null) {
+ mVoicemailPlaybackPresenter.onSaveInstanceState(outState);
+ }
+ }
+
+ @Override
+ public void fetchCalls() {
+ if (mFilterSubSpinnerView.isEnabled()) {
+ int[] subId = SubscriptionManager.getSubId(mCallSubFilter);
+ if (subId != null) {
+ Log.d(TAG, "fetchCalls subId = " + subId[0]);
+ mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit, subId[0]);
+ } else {
+ mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
+ }
+ } else {
+ mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
+ }
+ updateEmptyMessage(mCallTypeFilter);
+ }
+
+ private void updateEmptyMessage(int filterType) {
+ final Context context = getActivity();
+ if (context == null) {
+ return;
+ }
+
+ if (!PermissionsUtil.hasPermission(context, READ_CALL_LOG)) {
+ mEmptyListView.setDescription(R.string.permission_no_calllog);
+ mEmptyListView.setActionLabel(R.string.permission_single_turn_on);
+ return;
+ }
+
+ final int messageId;
+ switch (filterType) {
+ case Calls.INCOMING_TYPE:
+ messageId = R.string.recentIncoming_empty;
+ break;
+ case Calls.OUTGOING_TYPE:
+ messageId = R.string.recentOutgoing_empty;
+ break;
+ case Calls.MISSED_TYPE:
+ messageId = R.string.call_log_missed_empty;
+ break;
+ case Calls.VOICEMAIL_TYPE:
+ messageId = R.string.call_log_voicemail_empty;
+ break;
+ case CallLogQueryHandler.CALL_TYPE_ALL:
+ messageId = R.string.recentCalls_empty;
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected filter type in CallLogFragment: "
+ + filterType);
+ }
+ mEmptyListView.setDescription(messageId);
+ if (mIsRecentsFragment) {
+ mEmptyListView.setActionLabel(R.string.call_log_all_empty_action);
+ } else {
+ mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL);
+ }
+ }
+
+ CallLogAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public void setMenuVisibility(boolean menuVisible) {
+ super.setMenuVisibility(menuVisible);
+ if (mMenuVisible != menuVisible) {
+ mMenuVisible = menuVisible;
+ if (!menuVisible) {
+ updateOnTransition(false /* onEntry */);
+ } else if (isResumed()) {
+ refreshData();
+ }
+ }
+ }
+
+ /** Requests updates to the data to be shown. */
+ protected void refreshData() {
+ // Prevent unnecessary refresh.
+ if (mRefreshDataRequired) {
+ // Mark all entries in the contact info cache as out of date, so they will be looked up
+ // again once being shown.
+ mAdapter.invalidateCache();
+ mAdapter.setLoading(true);
+
+ fetchCalls();
+ mCallLogQueryHandler.fetchVoicemailStatus();
+
+ updateOnTransition(true /* onEntry */);
+ mRefreshDataRequired = false;
+ } else {
+ // Refresh the display of the existing data to update the timestamp text descriptions.
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Updates the call data and notification state on entering or leaving the call log tab.
+ *
+ * If we are leaving the call log tab, mark all the missed calls as read.
+ *
+ * TODO: Move to CallLogActivity
+ */
+ private void updateOnTransition(boolean onEntry) {
+ // We don't want to update any call data when keyguard is on because the user has likely not
+ // seen the new calls yet.
+ // This might be called before onCreate() and thus we need to check null explicitly.
+ if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
+ // On either of the transitions we update the missed call and voicemail notifications.
+ // While exiting we additionally consume all missed calls (by marking them as read).
+ mCallLogQueryHandler.markNewCallsAsOld();
+ if (!onEntry) {
+ mCallLogQueryHandler.markMissedCallsAsRead();
+ }
+ CallLogNotificationsHelper.removeMissedCallNotifications(getActivity());
+ CallLogNotificationsHelper.updateVoicemailNotifications(getActivity());
+ }
+ }
+
+ @Override
+ public void onEmptyViewActionButtonClicked() {
+ final Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ if (!PermissionsUtil.hasPermission(activity, READ_CALL_LOG)) {
+ requestPermissions(new String[] {READ_CALL_LOG}, READ_CALL_LOG_PERMISSION_REQUEST_CODE);
+ } else if (mIsRecentsFragment) {
+ // Show dialpad if we are the recents fragment.
+ ((HostInterface) activity).showDialpad();
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ if (requestCode == READ_CALL_LOG_PERMISSION_REQUEST_CODE) {
+ if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
+ // Force a refresh of the data since we were missing the permission before this.
+ mRefreshDataRequired = true;
+ }
+ }
+ }
+
+ private OnItemSelectedListener mSubSelectedListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Log.i(TAG, "Sub selected, position: " + position);
+ int sub = position - 1;
+ if (sub != mCallSubFilter) {
+ mCallSubFilter = sub;
+ setSelectedSub(sub);
+ fetchCalls();
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing.
+ }
+
+ };
+
+ private OnItemSelectedListener mStatusSelectedListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Log.i(TAG, "Status selected, position: " + position);
+ int type = ((SpinnerContent)parent.getItemAtPosition(position)).value;
+ if (type != mCallTypeFilter) {
+ mCallTypeFilter = type;
+ fetchCalls();
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing.
+ }
+
+ };
+
+ /**
+ * Initialize the filter views content.
+ */
+ private void updateFilterSpinnerViews() {
+ if (mFilterSubSpinnerView == null
+ || mFilterStatusSpinnerView == null) {
+ Log.w(TAG, "The filter spinner view is null!");
+ return;
+ }
+
+ // Update the sub filter's content.
+ final SubscriptionManager subscriptionManager = SubscriptionManager.from(getActivity());
+ if (subscriptionManager.getActiveSubscriptionInfoCount() < 2) {
+ mFilterSubSpinnerView.setVisibility(View.GONE);
+ }else{
+ ArrayAdapter<SpinnerContent> filterSubAdapter = new ArrayAdapter<SpinnerContent>(
+ getActivity(), R.layout.msim_call_log_spinner_item,
+ SpinnerContent.setupSubFilterContent(getActivity()));
+ if (filterSubAdapter.getCount() <= 1) {
+ mFilterSubSpinnerView.setVisibility(View.GONE);
+ }else{
+ mCallSubFilter = getSelectedSub();
+ mFilterSubSpinnerView.setAdapter(filterSubAdapter);
+ mFilterSubSpinnerView.setOnItemSelectedListener(mSubSelectedListener);
+ SpinnerContent.setSpinnerContentValue(mFilterSubSpinnerView, mCallSubFilter);
+ }
+ }
+ // Update the status filter's content.
+ ArrayAdapter<SpinnerContent> filterStatusAdapter = new ArrayAdapter<SpinnerContent>(
+ getActivity(), R.layout.msim_call_log_spinner_item,
+ SpinnerContent.setupStatusFilterContent(getActivity()));
+ mFilterStatusSpinnerView.setAdapter(filterStatusAdapter);
+ mFilterStatusSpinnerView.setOnItemSelectedListener(mStatusSelectedListener);
+ SpinnerContent.setSpinnerContentValue(mFilterStatusSpinnerView, mCallTypeFilter);
+ }
+
+ /**
+ * @return the saved selected subscription.
+ */
+ private int getSelectedSub() {
+ // Get the saved selected sub, and the default value is display all.
+ int sub = PreferenceManager.getDefaultSharedPreferences(this.getActivity()).getInt(
+ PREFERENCE_KEY_CALLLOG_SUB, CallLogQueryHandler.CALL_SUB_ALL);
+ return sub;
+ }
+
+ /**
+ * Save the selected subscription to preference.
+ */
+ private void setSelectedSub(int sub) {
+ // Save the selected sub to the default preference.
+ PreferenceManager.getDefaultSharedPreferences(this.getActivity()).edit()
+ .putInt(PREFERENCE_KEY_CALLLOG_SUB, sub).commit();
+ }
+
+}
diff --git a/src/com/android/dialer/calllog/PhoneAccountUtils.java b/src/com/android/dialer/calllog/PhoneAccountUtils.java
index b3ce18b..2346ca7 100644
--- a/src/com/android/dialer/calllog/PhoneAccountUtils.java
+++ b/src/com/android/dialer/calllog/PhoneAccountUtils.java
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -66,6 +67,17 @@
}
/**
+ *Extract account Icon from PhoneAccount object.
+ */
+ public static Drawable getAccountIcon(Context context, PhoneAccountHandle phoneAccount) {
+ final PhoneAccount account = getAccountOrNull(context, phoneAccount);
+ if (account == null) {
+ return null;
+ }
+ return account.getIcon().loadDrawable(context);
+ }
+
+ /**
* Extract account label from PhoneAccount object.
*/
@Nullable
@@ -107,9 +119,9 @@
* single registered and enabled account, return null.
*/
@Nullable
- private static PhoneAccount getAccountOrNull(Context context,
+ public static PhoneAccount getAccountOrNull(Context context,
@Nullable PhoneAccountHandle accountHandle) {
- if (TelecomUtil.getCallCapablePhoneAccounts(context).size() <= 1) {
+ if (TelecomUtil.getCallCapablePhoneAccounts(context).size() < 1) {
return null;
}
return TelecomUtil.getPhoneAccount(context, accountHandle);
diff --git a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
index 4f1c455..79e1ebb 100644
--- a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
+++ b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
@@ -21,13 +21,17 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.graphics.Typeface;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.support.v4.content.ContextCompat;
import android.telecom.PhoneAccount;
+import android.text.SpannableString;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.text.style.StyleSpan;
import android.view.View;
import android.widget.TextView;
@@ -86,6 +90,10 @@
/** Fills the call details views with content. */
public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details) {
+ setPhoneCallDetails(views, details, null);
+ }
+ public void setPhoneCallDetails(PhoneCallDetailsViews views,
+ PhoneCallDetails details, String filter) {
// Display up to a given number of icons.
views.callTypeIcons.clear();
int count = details.callTypes.length;
@@ -114,6 +122,15 @@
// Set the call count, location, date and if voicemail, set the duration.
setDetailText(views, callCount, details);
+ //set the account icon if it exists.
+ Drawable icon = details.accountIcon;
+ if (icon != null) {
+ views.callAccountIcon.setVisibility(View.VISIBLE);
+ views.callAccountIcon.setImageDrawable(icon);
+ } else {
+ views.callAccountIcon.setVisibility(View.GONE);
+ }
+
// Set the account label if it exists.
String accountLabel = mCallLogCache.getAccountLabel(details.accountHandle);
if (!TextUtils.isEmpty(details.viaNumber)) {
@@ -139,14 +156,34 @@
views.callAccountLabel.setVisibility(View.GONE);
}
- final CharSequence nameText;
- final CharSequence displayNumber = details.displayNumber;
+ CharSequence nameText;
+ CharSequence displayNumber = details.displayNumber;
+ String phoneNum = (String) details.number;
+ if (!TextUtils.isEmpty(filter) && phoneNum.contains(filter)) {
+ int start, end;
+ start = phoneNum.indexOf(filter);
+ end = start + filter.length();
+ SpannableString result = new SpannableString(phoneNum);
+ result.setSpan(new StyleSpan(Typeface.BOLD), start, end,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ displayNumber = result;
+ }
if (TextUtils.isEmpty(details.getPreferredName())) {
nameText = displayNumber;
// We have a real phone number as "nameView" so make it always LTR
views.nameView.setTextDirection(View.TEXT_DIRECTION_LTR);
} else {
nameText = details.getPreferredName();
+ if (!TextUtils.isEmpty(filter) && nameText.toString().toUpperCase()
+ .contains(filter.toUpperCase())) {
+ int start,end;
+ start = nameText.toString().toUpperCase().indexOf(filter.toUpperCase());
+ end = start + filter.length();
+ SpannableString style = new SpannableString(nameText);
+ style.setSpan(new StyleSpan(Typeface.BOLD), start, end,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ nameText = style;
+ }
}
views.nameView.setText(nameText);
diff --git a/src/com/android/dialer/calllog/PhoneCallDetailsViews.java b/src/com/android/dialer/calllog/PhoneCallDetailsViews.java
index 94f4411..4c0d45f 100644
--- a/src/com/android/dialer/calllog/PhoneCallDetailsViews.java
+++ b/src/com/android/dialer/calllog/PhoneCallDetailsViews.java
@@ -32,16 +32,19 @@
public final CallTypeIconsView callTypeIcons;
public final TextView callLocationAndDate;
public final TextView voicemailTranscriptionView;
+ public final ImageView callAccountIcon;
public final TextView callAccountLabel;
private PhoneCallDetailsViews(TextView nameView, View callTypeView,
CallTypeIconsView callTypeIcons, TextView callLocationAndDate,
- TextView voicemailTranscriptionView, TextView callAccountLabel) {
+ TextView voicemailTranscriptionView, ImageView callAccountIcon,
+ TextView callAccountLabel) {
this.nameView = nameView;
this.callTypeView = callTypeView;
this.callTypeIcons = callTypeIcons;
this.callLocationAndDate = callLocationAndDate;
this.voicemailTranscriptionView = voicemailTranscriptionView;
+ this.callAccountIcon = callAccountIcon;
this.callAccountLabel = callAccountLabel;
}
@@ -58,6 +61,7 @@
(CallTypeIconsView) view.findViewById(R.id.call_type_icons),
(TextView) view.findViewById(R.id.call_location_and_date),
(TextView) view.findViewById(R.id.voicemail_transcription),
+ (ImageView) view.findViewById(R.id.call_account_icon),
(TextView) view.findViewById(R.id.call_account_label));
}
@@ -68,6 +72,7 @@
new CallTypeIconsView(context),
new TextView(context),
new TextView(context),
+ new ImageView(context),
new TextView(context));
}
}
diff --git a/src/com/android/dialer/calllog/SpinnerContent.java b/src/com/android/dialer/calllog/SpinnerContent.java
new file mode 100644
index 0000000..43bdbbb
--- /dev/null
+++ b/src/com/android/dialer/calllog/SpinnerContent.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved
+ * Not a Contribution.
+ * Copyright (C) 2014 The CyanogenMod 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.dialer.calllog;
+
+import android.content.Context;
+import android.provider.CallLog;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Spinner;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.contacts.common.MoreContactUtils;
+import com.android.dialer.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * To save the spinner content.
+ */
+public class SpinnerContent {
+ private static String TAG = "SpinnerContent";
+
+ public final int value;
+ private final String label;
+ // The index for call type spinner.
+ private static final int INDEX_CALL_TYPE_ALL = 0;
+ private static final int INDEX_CALL_TYPE_INCOMING = 1;
+ private static final int INDEX_CALL_TYPE_OUTGOING = 2;
+ private static final int INDEX_CALL_TYPE_MISSED = 3;
+
+ public static void setSpinnerContentValue(Spinner spinner, int value) {
+ for (int i = 0, count = spinner.getCount(); i < count; i++) {
+ SpinnerContent sc = (SpinnerContent) spinner.getItemAtPosition(i);
+ if (sc.value == value) {
+ spinner.setSelection(i, true);
+ Log.i(TAG, "Set selection for spinner(" + sc + ") with the value: " + value);
+ return;
+ }
+ }
+ }
+
+ public SpinnerContent(int value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+
+ /**
+ * @return the spinner contents for the different sims (all, sim0, sim1 etc)
+ */
+ public static List<SpinnerContent> setupSubFilterContent(Context context) {
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ int count = telephonyManager.getPhoneCount();
+ // Update the filter sub content.
+ ArrayList<SpinnerContent> values = new ArrayList<SpinnerContent>(count + 1);
+ values.add(new SpinnerContent(CallLogQueryHandler.CALL_SUB_ALL,
+ context.getString(R.string.call_log_show_all_slots)));
+
+ List<PhoneAccountHandle> mPhoneAccountHandle =
+ PhoneAccountUtils.getSubscriptionPhoneAccounts(context);
+ for (int i = 0; i < mPhoneAccountHandle.size(); i++) {
+ String subDisplayName = PhoneAccountUtils.getAccountLabel(context,
+ mPhoneAccountHandle.get(i));
+ if (!TextUtils.isEmpty(subDisplayName) && subDisplayName.indexOf("Unknown") == -1) {
+ values.add(new SpinnerContent(i, subDisplayName));
+ }
+ }
+ return values;
+ }
+
+ /**
+ * @return the spinner contents for the different call types (incoming, outgoing etc)
+ */
+ public static List<SpinnerContent> setupStatusFilterContent(Context context) {
+ int statusCount = 4;
+ ArrayList<SpinnerContent> values = new ArrayList<SpinnerContent>(statusCount);
+ for (int i = 0; i < statusCount; i++) {
+ int value = CallLogQueryHandler.CALL_TYPE_ALL;
+ String label = null;
+ switch (i) {
+ case INDEX_CALL_TYPE_ALL:
+ value = CallLogQueryHandler.CALL_TYPE_ALL;
+ label = context.getString(R.string.call_log_all_calls_header);
+ break;
+ case INDEX_CALL_TYPE_INCOMING:
+ value = CallLog.Calls.INCOMING_TYPE;
+ label = context.getString(R.string.call_log_incoming_header);
+ break;
+ case INDEX_CALL_TYPE_OUTGOING:
+ value = CallLog.Calls.OUTGOING_TYPE;
+ label = context.getString(R.string.call_log_outgoing_header);
+ break;
+ case INDEX_CALL_TYPE_MISSED:
+ value = CallLog.Calls.MISSED_TYPE;
+ label = context.getString(R.string.call_log_missed_header);
+ break;
+ }
+ values.add(new SpinnerContent(value, label));
+ }
+ return values;
+ }
+}
diff --git a/src/com/android/dialer/calllog/calllogcache/CallLogCache.java b/src/com/android/dialer/calllog/calllogcache/CallLogCache.java
index dc1217c..7dda5a1 100644
--- a/src/com/android/dialer/calllog/calllogcache/CallLogCache.java
+++ b/src/com/android/dialer/calllog/calllogcache/CallLogCache.java
@@ -17,6 +17,7 @@
package com.android.dialer.calllog.calllogcache;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.telecom.PhoneAccountHandle;
import com.android.contacts.common.CallUtil;
@@ -93,4 +94,9 @@
* @return {@code true} if calling with a note is supported, {@code false} otherwise.
*/
public abstract boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle);
+
+ /**
+ * Returns the account icon if present, else null.
+ */
+ public abstract Drawable getAccountIcon(PhoneAccountHandle phoneAccount);
}
diff --git a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java
index 770cc9d..040855d 100644
--- a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java
+++ b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java
@@ -17,6 +17,7 @@
package com.android.dialer.calllog.calllogcache;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.PhoneNumberUtils;
@@ -70,4 +71,9 @@
public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
return false;
}
+
+ @Override
+ public Drawable getAccountIcon(PhoneAccountHandle accountHandle) {
+ return null;
+ }
}
diff --git a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java
index d1e3f7b..ccf11da 100644
--- a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java
+++ b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java
@@ -17,7 +17,12 @@
package com.android.dialer.calllog.calllogcache;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Pair;
@@ -37,22 +42,27 @@
class CallLogCacheLollipopMr1 extends CallLogCache {
// Maps from a phone-account/number pair to a boolean because multiple numbers could return true
// for the voicemail number if those numbers are not pre-normalized.
- private final Map<Pair<PhoneAccountHandle, CharSequence>, Boolean> mVoicemailQueryCache =
- new HashMap<>();
private final Map<PhoneAccountHandle, String> mPhoneAccountLabelCache = new HashMap<>();
private final Map<PhoneAccountHandle, Integer> mPhoneAccountColorCache = new HashMap<>();
private final Map<PhoneAccountHandle, Boolean> mPhoneAccountCallWithNoteCache = new HashMap<>();
+ private final Map<PhoneAccountHandle, Drawable> mPhoneAccountCallWithDrawableCache
+ = new HashMap<>();
+ private final Map<PhoneAccountHandle, String> mPhoneAccountCallWithVoiceMailNumberCache
+ = new HashMap<>();
+ private TelephonyManager mTelephonyManager;
/* package */ CallLogCacheLollipopMr1(Context context) {
super(context);
+ mTelephonyManager = TelephonyManager.from(context);
}
@Override
public void reset() {
- mVoicemailQueryCache.clear();
mPhoneAccountLabelCache.clear();
mPhoneAccountColorCache.clear();
mPhoneAccountCallWithNoteCache.clear();
+ mPhoneAccountCallWithDrawableCache.clear();
+ mPhoneAccountCallWithVoiceMailNumberCache.clear();
super.reset();
}
@@ -63,15 +73,28 @@
return false;
}
- Pair<PhoneAccountHandle, CharSequence> key = new Pair<>(accountHandle, number);
- if (mVoicemailQueryCache.containsKey(key)) {
- return mVoicemailQueryCache.get(key);
+ String curNumber = PhoneNumberUtils.extractNetworkPortionAlt(number.toString());
+ if (mPhoneAccountCallWithVoiceMailNumberCache.containsKey(accountHandle)) {
+ String vmNumber = mPhoneAccountCallWithVoiceMailNumberCache.get(accountHandle);
+ return !TextUtils.isEmpty(curNumber) && PhoneNumberUtils.compare(curNumber, vmNumber);
} else {
- Boolean isVoicemail =
- PhoneNumberUtil.isVoicemailNumber(mContext, accountHandle, number.toString());
- mVoicemailQueryCache.put(key, isVoicemail);
- return isVoicemail;
+ PhoneAccount account = PhoneAccountUtils.getAccountOrNull(mContext, accountHandle);
+ if (account != null
+ && account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ try {
+ int subId = Integer.parseInt(accountHandle.getId());
+ String vmNumber = mTelephonyManager.getVoiceMailNumber(subId);
+ mPhoneAccountCallWithVoiceMailNumberCache.put(accountHandle, vmNumber);
+ return !TextUtils.isEmpty(curNumber)
+ && PhoneNumberUtils.compare(curNumber, vmNumber);
+ } catch (NumberFormatException e) {
+ mPhoneAccountCallWithVoiceMailNumberCache.put(accountHandle, null);
+ }
+ } else {
+ mPhoneAccountCallWithVoiceMailNumberCache.put(accountHandle, null);
+ }
}
+ return false;
}
@Override
@@ -107,4 +130,20 @@
return supportsCallWithNote;
}
}
+
+ @Override
+ public Drawable getAccountIcon(PhoneAccountHandle accountHandle) {
+ if (mPhoneAccountCallWithDrawableCache.containsKey(accountHandle)) {
+ return mPhoneAccountCallWithDrawableCache.get(accountHandle);
+ } else {
+ PhoneAccount account = PhoneAccountUtils.getAccountOrNull(mContext, accountHandle);
+ if (account == null) {
+ mPhoneAccountCallWithDrawableCache.put(accountHandle, null);
+ return null;
+ }
+ Drawable drawable = account.getIcon().loadDrawable(mContext);
+ mPhoneAccountCallWithDrawableCache.put(accountHandle, drawable);
+ return drawable;
+ }
+ }
}
diff --git a/src/com/android/dialer/contactinfo/ContactInfoCache.java b/src/com/android/dialer/contactinfo/ContactInfoCache.java
index 1e24579..4fd3013 100644
--- a/src/com/android/dialer/contactinfo/ContactInfoCache.java
+++ b/src/com/android/dialer/contactinfo/ContactInfoCache.java
@@ -76,7 +76,8 @@
if (req != null) {
// Process the request. If the lookup succeeds, schedule a redraw.
- needRedraw |= queryContactInfo(req.number, req.countryIso, req.callLogInfo);
+ needRedraw |= queryContactInfo(req.number, req.postDialString, req.countryIso,
+ req.callLogInfo, req.isConf);
} else {
// Throttle redraw rate by only sending them when there are
// more requests.
@@ -130,6 +131,7 @@
private final LinkedList<ContactInfoRequest> mRequests;
private ExpirableCache<NumberWithCountryIso, ContactInfo> mCache;
+ private ExpirableCache<NumberWithCountryIso, ContactInfo> mCacheFor4gConfCall;
private ContactInfoHelper mContactInfoHelper;
private QueryThread mContactInfoQueryThread;
@@ -142,32 +144,52 @@
mRequests = new LinkedList<ContactInfoRequest>();
mCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
+ mCacheFor4gConfCall = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
}
public ContactInfo getValue(String number, String countryIso, ContactInfo cachedContactInfo) {
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- ExpirableCache.CachedValue<ContactInfo> cachedInfo =
- mCache.getCachedValue(numberCountryIso);
+ return getValue(number, null, countryIso, cachedContactInfo, false);
+ }
+
+ public ContactInfo getValue(String number, String postDialString, String countryIso,
+ ContactInfo cachedContactInfo, boolean isConf) {
+ String phoneNumber = number;
+ if (!isConf && !TextUtils.isEmpty(postDialString)) {
+ phoneNumber += postDialString;
+ }
+ NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(phoneNumber, countryIso);
+ ExpirableCache.CachedValue<ContactInfo> cachedInfo = null;
+ if (isConf) {
+ cachedInfo = mCacheFor4gConfCall.getCachedValue(numberCountryIso);
+ } else {
+ cachedInfo = mCache.getCachedValue(numberCountryIso);
+ }
ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
if (cachedInfo == null) {
- mCache.put(numberCountryIso, ContactInfo.EMPTY);
+ if (isConf) {
+ mCacheFor4gConfCall.put(numberCountryIso, ContactInfo.EMPTY);
+ } else {
+ mCache.put(numberCountryIso, ContactInfo.EMPTY);
+ }
// Use the cached contact info from the call log.
info = cachedContactInfo;
// The db request should happen on a non-UI thread.
// Request the contact details immediately since they are currently missing.
- enqueueRequest(number, countryIso, cachedContactInfo, true);
+ enqueueRequest(number, postDialString, countryIso, cachedContactInfo, true, isConf);
// We will format the phone number when we make the background request.
} else {
if (cachedInfo.isExpired()) {
// The contact info is no longer up to date, we should request it. However, we
// do not need to request them immediately.
- enqueueRequest(number, countryIso, cachedContactInfo, false);
+ enqueueRequest(number, postDialString, countryIso,
+ cachedContactInfo, false, isConf);
} else if (!callLogInfoMatches(cachedContactInfo, info)) {
// The call log information does not match the one we have, look it up again.
// We could simply update the call log directly, but that needs to be done in a
// background thread, so it is easier to simply request a new lookup, which will, as
// a side-effect, update the call log.
- enqueueRequest(number, countryIso, cachedContactInfo, false);
+ enqueueRequest(number, postDialString, countryIso,
+ cachedContactInfo, false, isConf);
}
if (info == ContactInfo.EMPTY) {
@@ -186,11 +208,15 @@
*
* The number might be either a SIP address or a phone number.
*
+ * @param postDialString if required, append into number
+ * @param isConf determine whether it is a 4g conf call log
* It returns true if it updated the content of the cache and we should therefore tell the
* view to update its content.
*/
- private boolean queryContactInfo(String number, String countryIso, ContactInfo callLogInfo) {
- final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
+ private boolean queryContactInfo(String number, String postDialString, String countryIso,
+ ContactInfo callLogInfo, boolean isConf) {
+ final ContactInfo info = mContactInfoHelper.lookupNumber(number, postDialString,
+ countryIso, isConf);
if (info == null) {
// The lookup failed, just return without requesting to update the view.
@@ -199,8 +225,17 @@
// Check the existing entry in the cache: only if it has changed we should update the
// view.
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- ContactInfo existingInfo = mCache.getPossiblyExpired(numberCountryIso);
+ String phoneNumber = number;
+ if (!isConf && !TextUtils.isEmpty(postDialString)) {
+ phoneNumber += postDialString;
+ }
+ NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(phoneNumber, countryIso);
+ ContactInfo existingInfo = null;
+ if (isConf) {
+ existingInfo = mCacheFor4gConfCall.getPossiblyExpired(numberCountryIso);
+ } else {
+ existingInfo = mCache.getPossiblyExpired(numberCountryIso);
+ }
final boolean isRemoteSource = info.sourceType != 0;
@@ -215,11 +250,21 @@
// Store the data in the cache so that the UI thread can use to display it. Store it
// even if it has not changed so that it is marked as not expired.
- mCache.put(numberCountryIso, info);
+ if (isConf) {
+ mCacheFor4gConfCall.put(numberCountryIso, info);
+ } else {
+ mCache.put(numberCountryIso, info);
+ }
// Update the call log even if the cache it is up-to-date: it is possible that the cache
// contains the value from a different call log entry.
- mContactInfoHelper.updateCallLogContactInfo(number, countryIso, info, callLogInfo);
+ if (isConf) {
+ mContactInfoHelper.updateCallLogContactInfo(number, countryIso,
+ info, callLogInfo);
+ } else {
+ mContactInfoHelper.updateCallLogContactInfo(phoneNumber, countryIso,
+ info, callLogInfo);
+ }
return updated;
}
@@ -264,6 +309,7 @@
public void invalidate() {
mCache.expireAll();
+ mCacheFor4gConfCall.expireAll();
stopRequestProcessing();
}
@@ -293,7 +339,24 @@
*/
protected void enqueueRequest(String number, String countryIso, ContactInfo callLogInfo,
boolean immediate) {
- ContactInfoRequest request = new ContactInfoRequest(number, countryIso, callLogInfo);
+ enqueueRequest(number, null, countryIso, callLogInfo, immediate, false);
+ }
+
+ /**
+ * Enqueues a request to look up the contact details for the given phone number.
+ * <p>
+ * It also provides the current contact info stored in the call log for this number.
+ * <p>
+ * If the {@code immediate} parameter is true, it will start immediately the thread that looks
+ * up the contact information (if it has not been already started). Otherwise, it will be
+ * started with a delay. See {@link #START_PROCESSING_REQUESTS_DELAY_MILLIS}.
+ * @param postDialString if required, append into number
+ * @param isConf indicate whether call log is for Conference Url call
+ */
+ protected void enqueueRequest(String number, String postDialString, String countryIso,
+ ContactInfo callLogInfo, boolean immediate, boolean isConf) {
+ ContactInfoRequest request = new ContactInfoRequest(number, postDialString, countryIso,
+ callLogInfo, isConf);
synchronized (mRequests) {
if (!mRequests.contains(request)) {
mRequests.add(request);
diff --git a/src/com/android/dialer/contactinfo/ContactInfoRequest.java b/src/com/android/dialer/contactinfo/ContactInfoRequest.java
index ec5c119..98208bc 100644
--- a/src/com/android/dialer/contactinfo/ContactInfoRequest.java
+++ b/src/com/android/dialer/contactinfo/ContactInfoRequest.java
@@ -31,11 +31,20 @@
public final String countryIso;
/** The cached contact information stored in the call log. */
public final ContactInfo callLogInfo;
+ public final boolean isConf;
+ public final String postDialString;
public ContactInfoRequest(String number, String countryIso, ContactInfo callLogInfo) {
+ this(number, null, countryIso, callLogInfo, false);
+ }
+
+ public ContactInfoRequest(String number, String postDialString, String countryIso,
+ ContactInfo callLogInfo, boolean isConf) {
this.number = number;
+ this.postDialString = postDialString;
this.countryIso = countryIso;
this.callLogInfo = callLogInfo;
+ this.isConf = isConf;
}
@Override
@@ -47,6 +56,7 @@
ContactInfoRequest other = (ContactInfoRequest) obj;
if (!TextUtils.equals(number, other.number)) return false;
+ if (!TextUtils.equals(postDialString, other.postDialString)) return false;
if (!TextUtils.equals(countryIso, other.countryIso)) return false;
if (!Objects.equal(callLogInfo, other.callLogInfo)) return false;
@@ -60,6 +70,7 @@
result = prime * result + ((callLogInfo == null) ? 0 : callLogInfo.hashCode());
result = prime * result + ((countryIso == null) ? 0 : countryIso.hashCode());
result = prime * result + ((number == null) ? 0 : number.hashCode());
+ result = prime * result + ((postDialString == null) ? 0 : postDialString.hashCode());
return result;
}
}
diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java
index 5edfb27..2d9b8aa 100644
--- a/src/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/src/com/android/dialer/database/DialerDatabaseHelper.java
@@ -19,6 +19,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
@@ -33,6 +34,7 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.util.Log;
@@ -49,6 +51,8 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
+import java.io.File;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
@@ -70,6 +74,11 @@
private static final AtomicBoolean sInUpdate = new AtomicBoolean(false);
private final Context mContext;
+ private Class mMultiMatchClass;
+ private Object mMultiMatchObject;
+ private Method mMultiMatchMethod;
+ private Method mMultiGetNameNumberMethod;
+
/**
* SmartDial DB version ranges:
* <pre>
@@ -77,6 +86,8 @@
* </pre>
*/
public static final int DATABASE_VERSION = 9;
+ public static final int DATABASE_SHAREPREF_VERSION = 1;
+ public static final String DATABASE_SHAREPREF_KEY = "database_sharepref_key";
public static final String DATABASE_NAME = "dialer.db";
/**
@@ -86,7 +97,7 @@
private static final String LAST_UPDATED_MILLIS = "last_updated_millis";
private static final String DATABASE_VERSION_PROPERTY = "database_version";
- private static final int MAX_ENTRIES = 20;
+ private static final int MAX_ENTRIES = 40;
public interface Tables {
/** Saves a list of numbers to be blocked.*/
@@ -120,6 +131,8 @@
static final String IS_PRIMARY = "is_primary";
static final String CARRIER_PRESENCE = "carrier_presence";
static final String LAST_SMARTDIAL_UPDATE_TIME = "last_smartdial_update_time";
+ static final String ACCOUNT_TYPE = "account_type";
+ static final String ACCOUNT_NAME = "account_name";
}
public static interface PrefixColumns extends BaseColumns {
@@ -156,6 +169,8 @@
Contacts.IN_VISIBLE_GROUP, // 12
Data.IS_PRIMARY, // 13
Data.CARRIER_PRESENCE, // 14
+ RawContacts.ACCOUNT_TYPE, // 15
+ RawContacts.ACCOUNT_NAME, // 16
};
static final int PHONE_ID = 0;
@@ -173,6 +188,8 @@
static final int PHONE_IN_VISIBLE_GROUP = 12;
static final int PHONE_IS_PRIMARY = 13;
static final int PHONE_CARRIER_PRESENCE = 14;
+ static final int PHONE_ACCOUNT_TYPE = 15;
+ static final int PHONE_ACCOUNT_NAME = 16;
/** Selects only rows that have been updated after a certain time stamp.*/
static final String SELECT_UPDATED_CLAUSE =
@@ -275,6 +292,8 @@
public final String lookupKey;
public final long photoId;
public final int carrierPresence;
+ public final String accountType;
+ public final String accountName;
public ContactNumber(long id, long dataID, String displayName, String phoneNumber,
String lookupKey, long photoId, int carrierPresence) {
@@ -285,6 +304,22 @@
this.lookupKey = lookupKey;
this.photoId = photoId;
this.carrierPresence = carrierPresence;
+ this.accountType = null;
+ this.accountName = null;
+ }
+
+ public ContactNumber(long id, long dataID, String displayName, String phoneNumber,
+ String lookupKey, long photoId, int carrierPresence,
+ String accountType, String accountName) {
+ this.dataId = dataID;
+ this.id = id;
+ this.displayName = displayName;
+ this.phoneNumber = phoneNumber;
+ this.lookupKey = lookupKey;
+ this.photoId = photoId;
+ this.carrierPresence = carrierPresence;
+ this.accountType = accountType;
+ this.accountName = accountName;
}
@Override
@@ -306,7 +341,10 @@
&& Objects.equal(this.phoneNumber, that.phoneNumber)
&& Objects.equal(this.lookupKey, that.lookupKey)
&& Objects.equal(this.photoId, that.photoId)
- && Objects.equal(this.carrierPresence, that.carrierPresence);
+ && Objects.equal(this.carrierPresence, that.carrierPresence)
+ && Objects.equal(this.accountType, that.accountType)
+ && Objects.equal(this.accountName, that.accountName);
+
}
return false;
}
@@ -382,6 +420,39 @@
mContext = Preconditions.checkNotNull(context, "Context must not be null");
}
+ private void initMultiLanguageSearch() {
+ try {
+ if (mMultiMatchClass == null) {
+ mMultiMatchClass = Class.forName("com.qualcomm.qti.smartsearch.SmartMatch");
+ Log.d(TAG, "create multi match success");
+ }
+ if (mMultiMatchClass != null) {
+ if (mMultiMatchObject == null) {
+ mMultiMatchObject = mMultiMatchClass.newInstance();
+ }
+ if (mMultiMatchMethod == null) {
+ mMultiMatchMethod = mMultiMatchClass.getDeclaredMethod(
+ "getMatchStringIndex", String.class, String.class,
+ int.class);
+ }
+ if (mMultiGetNameNumberMethod == null) {
+ mMultiGetNameNumberMethod = mMultiMatchClass.getDeclaredMethod(
+ "getNameNumber", String.class, int.class);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public Object getMultiMatchObject() {
+ return mMultiMatchObject;
+ }
+
+ public Method getMultiMatchMethod() {
+ return mMultiMatchMethod;
+ }
+
/**
* Creates tables in the database when database is created for the first time.
*
@@ -392,6 +463,11 @@
setupTables(db);
}
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ upgradeSmartSearchDatabase(db);
+ }
+
private void setupTables(SQLiteDatabase db) {
dropTables(db);
db.execSQL("CREATE TABLE " + Tables.SMARTDIAL_TABLE + " ("
@@ -409,7 +485,9 @@
+ SmartDialDbColumns.IS_SUPER_PRIMARY + " INTEGER, "
+ SmartDialDbColumns.IN_VISIBLE_GROUP + " INTEGER, "
+ SmartDialDbColumns.IS_PRIMARY + " INTEGER, "
- + SmartDialDbColumns.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0"
+ + SmartDialDbColumns.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0,"
+ + SmartDialDbColumns.ACCOUNT_TYPE + " TEXT, "
+ + SmartDialDbColumns.ACCOUNT_NAME + " TEXT "
+ ");");
db.execSQL("CREATE TABLE " + Tables.PREFIX_TABLE + " ("
@@ -445,6 +523,49 @@
}
}
+ private boolean isNeedUpgradeForSmartSearch() {
+ String FILENAME = "upgradeSmartSearchTable";
+
+ Log.d(TAG, "Shared Preference Created with name: " + FILENAME);
+ SharedPreferences pref = mContext.getSharedPreferences(FILENAME,
+ mContext.MODE_PRIVATE);
+ if (pref != null) {
+ int mSharePrefVersion = pref.getInt(DATABASE_SHAREPREF_KEY,0);
+ if(mSharePrefVersion < DATABASE_SHAREPREF_VERSION) {
+ Editor editor;
+ editor = pref.edit();
+ editor.putInt(DATABASE_SHAREPREF_KEY, DATABASE_SHAREPREF_VERSION);
+ editor.commit();
+ return true;
+ }
+ return false;
+ } else {
+ Log.d(TAG, "fail to get SharedPreferences !");
+ return false;
+ }
+ }
+
+ private void upgradeSmartSearchDatabase(SQLiteDatabase db) {
+ if (isNeedUpgradeForSmartSearch()) {
+ db.beginTransaction();
+ try {
+ upgradeDatabaseSmartSearch(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
+ }
+
+ private void upgradeDatabaseSmartSearch(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE " + Tables.SMARTDIAL_TABLE + " ADD COLUMN " +
+ SmartDialDbColumns.ACCOUNT_TYPE + " TEXT;");
+ db.execSQL("ALTER TABLE " + Tables.SMARTDIAL_TABLE + " ADD COLUMN " +
+ SmartDialDbColumns.ACCOUNT_NAME + " TEXT;");
+ }
+
public void dropTables(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + Tables.PREFIX_TABLE);
db.execSQL("DROP TABLE IF EXISTS " + Tables.SMARTDIAL_TABLE);
@@ -770,8 +891,10 @@
SmartDialDbColumns.IN_VISIBLE_GROUP+ ", " +
SmartDialDbColumns.IS_PRIMARY + ", " +
SmartDialDbColumns.CARRIER_PRESENCE + ", " +
- SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + ") " +
- " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + ", " +
+ SmartDialDbColumns.ACCOUNT_TYPE + ", " +
+ SmartDialDbColumns.ACCOUNT_NAME + ") " +
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
final SQLiteStatement insert = db.compileStatement(sqlInsert);
final String numberSqlInsert = "INSERT INTO " + Tables.PREFIX_TABLE + " (" +
@@ -820,6 +943,24 @@
insert.bindLong(12, updatedContactCursor.getInt(PhoneQuery.PHONE_IS_PRIMARY));
insert.bindLong(13, updatedContactCursor.getInt(PhoneQuery.PHONE_CARRIER_PRESENCE));
insert.bindLong(14, currentMillis);
+
+ final String accountType = updatedContactCursor.getString(
+ PhoneQuery.PHONE_ACCOUNT_TYPE);
+ if (accountType == null) {
+ insert.bindString(15, mContext.getResources().getString(
+ R.string.missing_account_type));
+ } else {
+ insert.bindString(15, accountType);
+ }
+
+ final String accountName = updatedContactCursor.getString(
+ PhoneQuery.PHONE_ACCOUNT_NAME);
+ if (accountName == null) {
+ insert.bindString(16, mContext.getResources().getString(
+ R.string.missing_account_name));
+ } else {
+ insert.bindString(16, accountName);
+ }
insert.executeInsert();
final String contactPhoneNumber =
updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER);
@@ -863,14 +1004,27 @@
while (nameCursor.moveToNext()) {
/** Computes a list of prefixes of a given contact name. */
- final ArrayList<String> namePrefixes =
- SmartDialPrefix.generateNamePrefixes(nameCursor.getString(columnIndexName));
-
- for (String namePrefix : namePrefixes) {
- insert.bindLong(1, nameCursor.getLong(columnIndexContactId));
- insert.bindString(2, namePrefix);
- insert.executeInsert();
- insert.clearBindings();
+ if (mMultiGetNameNumberMethod != null) {
+ try {
+ String nameNumber = (String) mMultiGetNameNumberMethod.invoke(
+ mMultiMatchObject,nameCursor.getString(columnIndexName), 0);
+ nameNumber = nameNumber.replaceAll("[\\[\\.\\]]", "");
+ insert.bindLong(1,nameCursor.getLong(columnIndexContactId));
+ insert.bindString(2, nameNumber);
+ insert.executeInsert();
+ insert.clearBindings();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ final ArrayList<String> namePrefixes = SmartDialPrefix
+ .generateNamePrefixes(nameCursor.getString(columnIndexName));
+ for (String namePrefix : namePrefixes) {
+ insert.bindLong(1, nameCursor.getLong(columnIndexContactId));
+ insert.bindString(2, namePrefix);
+ insert.executeInsert();
+ insert.clearBindings();
+ }
}
}
@@ -888,6 +1042,8 @@
* update.
*/
public void updateSmartDialDatabase() {
+ initMultiLanguageSearch();
+
final SQLiteDatabase db = getWritableDatabase();
synchronized(mLock) {
@@ -1064,14 +1220,19 @@
public ArrayList<ContactNumber> getLooseMatches(String query,
SmartDialNameMatcher nameMatcher) {
final boolean inUpdate = sInUpdate.get();
- if (inUpdate) {
+ if (inUpdate || query.length() == 0) {
return Lists.newArrayList();
}
final SQLiteDatabase db = getReadableDatabase();
/** Uses SQL query wildcard '%' to represent prefix matching.*/
- final String looseQuery = query + "%";
+ StringBuilder looseQuery = new StringBuilder(query);
+ for (int i = 0; i < looseQuery.toString().length();) {
+ looseQuery.insert(i, "%");
+ i = i + 2;
+ }
+ looseQuery.append("%");
final ArrayList<ContactNumber> result = Lists.newArrayList();
@@ -1087,10 +1248,12 @@
SmartDialDbColumns.NUMBER + ", " +
SmartDialDbColumns.CONTACT_ID + ", " +
SmartDialDbColumns.LOOKUP_KEY + ", " +
- SmartDialDbColumns.CARRIER_PRESENCE +
- " FROM " + Tables.SMARTDIAL_TABLE + " WHERE " +
- SmartDialDbColumns.CONTACT_ID + " IN " +
- " (SELECT " + PrefixColumns.CONTACT_ID +
+ SmartDialDbColumns.CARRIER_PRESENCE + ", " +
+ SmartDialDbColumns.ACCOUNT_TYPE + ", " +
+ SmartDialDbColumns.ACCOUNT_NAME +
+ " FROM " + Tables.SMARTDIAL_TABLE +
+ " WHERE " + SmartDialDbColumns.CONTACT_ID + " IN " +
+ " (SELECT " + PrefixColumns.CONTACT_ID +
" FROM " + Tables.PREFIX_TABLE +
" WHERE " + Tables.PREFIX_TABLE + "." + PrefixColumns.PREFIX +
" LIKE '" + looseQuery + "')" +
@@ -1112,6 +1275,8 @@
final int columnId = 4;
final int columnLookupKey = 5;
final int columnCarrierPresence = 6;
+ final int columnAccountType = 7;
+ final int columnAccountName = 8;
if (DEBUG) {
stopWatch.lap("Found column IDs");
}
@@ -1130,6 +1295,8 @@
final long photoId = cursor.getLong(columnPhotoId);
final String lookupKey = cursor.getString(columnLookupKey);
final int carrierPresence = cursor.getInt(columnCarrierPresence);
+ final String accountType = cursor.getString(columnAccountType);
+ final String accountName = cursor.getString(columnAccountName);
/** If a contact already exists and another phone number of the contact is being
* processed, skip the second instance.
@@ -1150,7 +1317,7 @@
/** If a contact has not been added, add it to the result and the hash set.*/
duplicates.add(contactMatch);
result.add(new ContactNumber(id, dataID, displayName, phoneNumber, lookupKey,
- photoId, carrierPresence));
+ photoId, carrierPresence, accountType, accountName));
counter++;
if (DEBUG) {
stopWatch.lap("Added one result: Name: " + displayName);
diff --git a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
index 68a2e85..8d8f874 100644
--- a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
+++ b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
@@ -130,7 +130,13 @@
new Listener() {
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- listener.onHasBlockedNumbers(cursor != null && cursor.getCount() > 0);
+ try {
+ listener.onHasBlockedNumbers(cursor != null && cursor.getCount() > 0);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
}
},
FilteredNumberCompat.getContentUri(null),
@@ -164,20 +170,26 @@
* example, both '16502530000' and '6502530000' can exist at the same time
* and will be returned by this query.
*/
- if (cursor == null || cursor.getCount() == 0) {
- listener.onCheckComplete(null);
- return;
+ try {
+ if (cursor == null || cursor.getCount() == 0) {
+ listener.onCheckComplete(null);
+ return;
+ }
+ cursor.moveToFirst();
+ // New filtering doesn't have a concept of type
+ if (!FilteredNumberCompat.useNewFiltering()
+ && cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns.TYPE))
+ != FilteredNumberTypes.BLOCKED_NUMBER) {
+ listener.onCheckComplete(null);
+ return;
+ }
+ listener.onCheckComplete(
+ cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns._ID)));
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
}
- cursor.moveToFirst();
- // New filtering doesn't have a concept of type
- if (!FilteredNumberCompat.useNewFiltering()
- && cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns.TYPE))
- != FilteredNumberTypes.BLOCKED_NUMBER) {
- listener.onCheckComplete(null);
- return;
- }
- listener.onCheckComplete(
- cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns._ID)));
}
},
FilteredNumberCompat.getContentUri(null),
@@ -248,25 +260,31 @@
startQuery(NO_TOKEN, new Listener() {
@Override
public void onQueryComplete(int token, Object cookie, Cursor cursor) {
- int rowsReturned = cursor == null ? 0 : cursor.getCount();
- if (rowsReturned != 1) {
- throw new SQLiteDatabaseCorruptException
- ("Returned " + rowsReturned + " rows for uri "
- + uri + "where 1 expected.");
- }
- cursor.moveToFirst();
- final ContentValues values = new ContentValues();
- DatabaseUtils.cursorRowToContentValues(cursor, values);
- values.remove(FilteredNumberCompat.getIdColumnName());
-
- startDelete(NO_TOKEN, new Listener() {
- @Override
- public void onDeleteComplete(int token, Object cookie, int result) {
- if (listener != null) {
- listener.onUnblockComplete(result, values);
- }
+ try {
+ int rowsReturned = cursor == null ? 0 : cursor.getCount();
+ if (rowsReturned != 1) {
+ throw new SQLiteDatabaseCorruptException
+ ("Returned " + rowsReturned + " rows for uri "
+ + uri + "where 1 expected.");
}
- }, uri, null, null);
+ cursor.moveToFirst();
+ final ContentValues values = new ContentValues();
+ DatabaseUtils.cursorRowToContentValues(cursor, values);
+ values.remove(FilteredNumberCompat.getIdColumnName());
+
+ startDelete(NO_TOKEN, new Listener() {
+ @Override
+ public void onDeleteComplete(int token, Object cookie, int result) {
+ if (listener != null) {
+ listener.onUnblockComplete(result, values);
+ }
+ }
+ }, uri, null, null);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
}
}, uri, null, null, null, null);
}
diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java
index 55d5346..b4070cc 100644
--- a/src/com/android/dialer/dialpad/DialpadFragment.java
+++ b/src/com/android/dialer/dialpad/DialpadFragment.java
@@ -29,21 +29,27 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.media.ToneGenerator;
+import android.Manifest;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
+import android.os.SystemProperties;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.provider.Contacts.PhonesColumns;
import android.provider.Settings;
+import android.support.v4.app.ActivityCompat;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.TextUtils;
@@ -71,6 +77,7 @@
import com.android.contacts.common.CallUtil;
import com.android.contacts.common.GeoUtil;
import com.android.contacts.common.dialog.CallSubjectDialog;
+import com.android.contacts.common.MoreContactUtils;
import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.util.PhoneNumberFormatter;
import com.android.contacts.common.util.StopWatch;
@@ -79,16 +86,23 @@
import com.android.dialer.NeededForReflection;
import com.android.dialer.R;
import com.android.dialer.SpecialCharSequenceMgr;
+import com.android.dialer.SpeedDialListActivity;
+import com.android.dialer.SpeedDialUtils;
import com.android.dialer.calllog.PhoneAccountUtils;
import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.IntentUtil;
import com.android.dialer.util.IntentUtil.CallIntentBuilder;
import com.android.dialer.util.TelecomUtil;
import com.android.incallui.Call.LogState;
+import com.android.dialer.util.WifiCallUtils;
import com.android.phone.common.CallLogAsync;
import com.android.phone.common.animation.AnimUtils;
import com.android.phone.common.dialpad.DialpadKeyButton;
import com.android.phone.common.dialpad.DialpadView;
+import static android.Manifest.permission.READ_CALL_LOG;
+import static android.Manifest.permission.WRITE_CALL_LOG;
+
import java.util.HashSet;
import java.util.List;
@@ -103,6 +117,9 @@
DialpadKeyButton.OnPressedListener {
private static final String TAG = "DialpadFragment";
+ /* define for Activity permission request for Android >= 6.0 */
+ private static final int PERMISSION_REQUEST_CODE_LOCATION = 2;
+ private static final int READ_WRITE_CALL_LOG_PERMISSION_REQUEST_CODE = 3;
/**
* LinearLayout with getter and setter methods for the translationY property using floats,
* for animation purposes.
@@ -145,6 +162,20 @@
* be dismissed, unless there happens to be content showing.
*/
boolean onDialpadSpacerTouchWithEmptyQuery();
+
+ /**
+ * This interface allows the DialpadFragment to tell its hosting Activity when and when not
+ * to display the "dial" button. While this is logically part of the DialpadFragment, the
+ * need to have a particular kind of slick animation puts the "dial" button in the parent.
+ *
+ * The parent calls dialButtonPressed() and optionsMenuInvoked() on the dialpad fragment
+ * when appropriate.
+ *
+ * TODO: Refactor the app so this interchange is a bit cleaner.
+ */
+ void setConferenceDialButtonVisibility(boolean enabled);
+ void setConferenceDialButtonImage(boolean setAddParticipantButton);
+
}
private static final boolean DEBUG = DialtactsActivity.DEBUG;
@@ -166,11 +197,16 @@
/** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF;
+ private static final String PROPERTY_RADIO_ATEL_CARRIER = "persist.radio.atel.carrier";
+ private static final String CARRIER_ONE_DEFAULT_MCC_MNC = "405854";
private OnDialpadQueryChangedListener mDialpadQueryListener;
private DialpadView mDialpadView;
private EditText mDigits;
+ private EditText mRecipients;
+ private View mDigitsContainer;
+ private View mDialpad;
private int mDialpadSlideInDuration;
/** Remembers if we need to clear digits field when the screen is completely gone. */
@@ -212,6 +248,13 @@
/** Identifier for the "Add Call" intent extra. */
private static final String ADD_CALL_MODE_KEY = "add_call_mode";
+ /** Identifier for the "Add Participant" intent extra. */
+ private static final String ADD_PARTICIPANT_KEY = "add_participant";
+
+ private static final String EXTRA_DIAL_CONFERENCE_URI = "org.codeaurora.extra.DIAL_CONFERENCE_URI";
+
+ private boolean mAddParticipant = false;
+
/**
* Identifier for intent extra for sending an empty Flash message for
* CDMA networks. This message is used by the network to simulate a
@@ -229,6 +272,8 @@
private CallStateReceiver mCallStateReceiver;
+ private boolean mHasReadAndWriteCallLogPermission = false;
+
private class CallStateReceiver extends BroadcastReceiver {
/**
* Receive call state changes so that we can take down the
@@ -251,6 +296,12 @@
// onscreen, but useless...)
showDialpadChooser(false);
}
+ if (state == TelephonyManager.EXTRA_STATE_IDLE) {
+ final Activity activity = getActivity();
+ if (activity != null) {
+ ((HostInterface) activity).setConferenceDialButtonVisibility(true);
+ }
+ }
}
}
@@ -266,9 +317,17 @@
private boolean mStartedFromNewIntent = false;
private boolean mFirstLaunch = false;
private boolean mAnimate = false;
+ private TextView mOperator;
private static final String PREF_DIGITS_FILLED_BY_INTENT = "pref_digits_filled_by_intent";
+ private static final String ACTION_WIFI_CALL_READY_STATUS_CHANGE
+ = "com.android.wificall.READY";
+ private static final String ACTION_WIFI_CALL_READY_EXTRA
+ = "com.android.wificall.ready.extra";
+ private BroadcastReceiver mWifiCallReadyReceiver;
+ private boolean isConfigAvailableNetwork = false;
+
private TelephonyManager getTelephonyManager() {
return (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
}
@@ -320,6 +379,20 @@
updateDeleteButtonEnabledState();
}
+ private void changeDialpadButton(boolean ready) {
+ Log.d(TAG, "changeDialpadButton, ready = " + ready);
+ ImageView floatingActionButton =
+ (ImageButton) getView().findViewById(R.id.dialpad_floating_action_button);
+ if (floatingActionButton == null) {
+ return;
+ }
+ if (ready) {
+ floatingActionButton.setImageResource(R.drawable.fab_ic_wificall);
+ } else {
+ floatingActionButton.setImageResource(R.drawable.fab_ic_call);
+ }
+ }
+
@Override
public void onCreate(Bundle state) {
Trace.beginSection(TAG + " onCreate");
@@ -344,6 +417,8 @@
mCallStateReceiver = new CallStateReceiver();
((Context) getActivity()).registerReceiver(mCallStateReceiver, callStateIntentFilter);
}
+ isConfigAvailableNetwork = getActivity().getResources().getBoolean(
+ R.bool.config_regional_pup_no_available_network);
Trace.endSection();
}
@@ -363,6 +438,13 @@
mDialpadView = (DialpadView) fragmentView.findViewById(R.id.dialpad_view);
mDialpadView.setCanDigitsBeEdited(true);
mDigits = mDialpadView.getDigits();
+ mRecipients = (EditText) fragmentView.findViewById(R.id.recipients);
+ mDigitsContainer = fragmentView.findViewById(R.id.digits_container);
+ mDialpad = fragmentView.findViewById(R.id.dialpad);
+ if (mRecipients != null) {
+ mRecipients.setVisibility(View.GONE);
+ mRecipients.addTextChangedListener(this);
+ }
mDigits.setKeyListener(UnicodeDialerKeyListener.INSTANCE);
mDigits.setOnClickListener(this);
mDigits.setOnKeyListener(this);
@@ -410,6 +492,7 @@
floatingActionButton.setOnClickListener(this);
mFloatingActionButtonController = new FloatingActionButtonController(getActivity(),
floatingActionButtonContainer, floatingActionButton);
+ mOperator = (TextView)fragmentView.findViewById(R.id.dialpad_floating_operator);
Trace.endSection();
Trace.endSection();
return fragmentView;
@@ -545,6 +628,9 @@
}
}
+ } else {
+ mAddParticipant = intent.getBooleanExtra(ADD_PARTICIPANT_KEY, false);
+ ((HostInterface) getActivity()).setConferenceDialButtonVisibility(true);
}
showDialpadChooser(needToShowDialpadChooser);
setStartedFromNewIntent(false);
@@ -568,11 +654,16 @@
private void setFormattedDigits(String data, String normalizedNumber) {
final String formatted = getFormattedDigits(data, normalizedNumber, mCurrentCountryIso);
if (!TextUtils.isEmpty(formatted)) {
- Editable digits = mDigits.getText();
- digits.replace(0, digits.length(), formatted);
- // for some reason this isn't getting called in the digits.replace call above..
- // but in any case, this will make sure the background drawable looks right
- afterTextChanged(digits);
+ if (isRecipientsShown()) {
+ mRecipients.setText(formatted);
+ afterTextChanged(mRecipients.getText());
+ } else {
+ Editable digits = mDigits.getText();
+ digits.replace(0, digits.length(), formatted);
+ // for some reason this isn't getting called in the digits.replace call above..
+ // but in any case, this will make sure the background drawable looks right
+ afterTextChanged(digits);
+ }
}
}
@@ -617,6 +708,10 @@
for (int i = 0; i < buttonIds.length; i++) {
dialpadKey = (DialpadKeyButton) fragmentView.findViewById(buttonIds[i]);
dialpadKey.setOnPressedListener(this);
+ // Long-pressing button from two to nine will set up speed key dial.
+ if (i > 0 && i < buttonIds.length - 3) {
+ dialpadKey.setOnLongClickListener(this);
+ }
}
// Long-pressing one button will initiate Voicemail.
@@ -626,6 +721,14 @@
// Long-pressing zero button will enter '+' instead.
final DialpadKeyButton zero = (DialpadKeyButton) fragmentView.findViewById(R.id.zero);
zero.setOnLongClickListener(this);
+
+ // Long-pressing star button will enter ','(pause) instead.
+ final DialpadKeyButton star = (DialpadKeyButton) fragmentView.findViewById(R.id.star);
+ star.setOnLongClickListener(this);
+
+ // Long-pressing pound button will enter ';'(wait) instead.
+ final DialpadKeyButton pound = (DialpadKeyButton) fragmentView.findViewById(R.id.pound);
+ pound.setOnLongClickListener(this);
}
@Override
@@ -663,8 +766,16 @@
final StopWatch stopWatch = StopWatch.start("Dialpad.onResume");
// Query the last dialed number. Do it first because hitting
+ mHasReadAndWriteCallLogPermission =
+ PermissionsUtil.hasPermission(getActivity(), READ_CALL_LOG) &&
+ PermissionsUtil.hasPermission(getActivity(), WRITE_CALL_LOG);
// the DB is 'slow'. This call is asynchronous.
- queryLastOutgoingCall();
+ if (mHasReadAndWriteCallLogPermission){
+ queryLastOutgoingCall();
+ } else {
+ ActivityCompat.requestPermissions(getActivity(), new String[] {READ_CALL_LOG,
+ WRITE_CALL_LOG}, READ_WRITE_CALL_LOG_PERMISSION_REQUEST_CODE);
+ }
stopWatch.lap("qloc");
@@ -713,6 +824,28 @@
}
mFirstLaunch = false;
+
+ if (MoreContactUtils.shouldShowOperator(getContext())) {
+ mOperator.setVisibility(View.VISIBLE);
+ mOperator.setText(MoreContactUtils.getNetworkSpnName(getContext(),
+ SubscriptionManager.getDefaultVoiceSubscriptionId()));
+ } else {
+ mOperator.setVisibility(View.GONE);
+ }
+
+ if (isConfigAvailableNetwork) {
+ Context context = getActivity();
+ mWifiCallReadyReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ changeDialpadButton(
+ intent.getBooleanExtra(ACTION_WIFI_CALL_READY_EXTRA, false));
+ }
+ };
+ IntentFilter filter = new IntentFilter(ACTION_WIFI_CALL_READY_STATUS_CHANGE);
+ context.registerReceiver(mWifiCallReadyReceiver, filter);
+ changeDialpadButton(WifiCallUtils.isWifiCallReadyEnabled(context));
+ }
Trace.endSection();
}
@@ -729,6 +862,10 @@
mLastNumberDialed = EMPTY_NUMBER; // Since we are going to query again, free stale number.
SpecialCharSequenceMgr.cleanup();
+
+ if (isConfigAvailableNetwork) {
+ (getActivity()).unregisterReceiver(mWifiCallReadyReceiver);
+ }
}
@Override
@@ -891,6 +1028,10 @@
public void show() {
final Menu menu = getMenu();
+ final MenuItem conferDialerOption
+ = menu.findItem(R.id.menu_add_to_4g_conference_call);
+ conferDialerOption.setVisible(IntentUtil.isConferDialerEnabled(getActivity()));
+
boolean enable = !isDigitsEmpty();
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
@@ -907,12 +1048,88 @@
return popupMenu;
}
+ /**
+ * Called by the containing Activity to tell this Fragment that the dial button has been
+ * pressed.
+ */
+ public void dialButtonPressed() {
+ handleDialButtonPressed();
+ }
+
+ public void dialConferenceButtonPressed() {
+ // show dial conference screen if it is not shown
+ // If it is already shown, show normal dial screen
+ boolean show = (mRecipients != null) && !mRecipients.isShown();
+ Log.d(TAG, "dialConferenceButtonPressed show " + show);
+ if (show) {
+ showDialConference(show);
+ } else {
+ handleDialButtonPressed();
+ showDialConference(!show);
+ }
+ }
+
+ public void showDialConference(boolean enabled) {
+ // Check if onCreateView() is already called by checking one of View
+ // objects.
+ if (!isLayoutReady()) {
+ return;
+ }
+ Log.d(TAG, "showDialConference " + enabled);
+ /*
+ * if enabled is true then pick child views that should be
+ * visible/invisible when dialpad is choosen from conference dial button
+ * if enabled is false then pick child views that should be
+ * visible/invisible when dialpad is choosen from other buttons
+ */
+
+ // viewable when choosen through conference button
+ int conferenceButtonVisibility = (enabled ? View.VISIBLE : View.GONE);
+ // not viewable when choosen through conference button
+ int nonConferenceButtonVisibility = (enabled ? View.GONE : View.VISIBLE);
+
+ // change the image visibility of the button
+ if (mRecipients != null)
+ mRecipients.setVisibility(conferenceButtonVisibility);
+ if (mDigits != null)
+ mDigits.setVisibility(nonConferenceButtonVisibility);
+ if (mDelete != null)
+ mDelete.setVisibility(nonConferenceButtonVisibility);
+ if (mDialpad != null)
+ mDialpad.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
+
+ if (enabled && (HostInterface)getActivity() != null) {
+ ((HostInterface)getActivity()).setConferenceDialButtonImage(enabled);
+ }
+ }
+
+ public void hideAndClearDialConference() {
+ // hide the image visibility of the button
+ if (mRecipients != null)
+ mRecipients.setVisibility(View.GONE);
+ if (mDigits != null)
+ mDigits.setVisibility(View.GONE);
+ if (mDelete != null)
+ mDelete.setVisibility(View.GONE);
+ if (mDialpad != null)
+ mDialpad.setVisibility(View.GONE);
+ ((DialtactsActivity) getActivity()).commitDialpadFragmentHide();
+ }
+
+ public boolean isRecipientsShown() {
+ return mRecipients != null && mRecipients.isShown();
+ }
+
@Override
public void onClick(View view) {
int resId = view.getId();
if (resId == R.id.dialpad_floating_action_button) {
- view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
- handleDialButtonPressed();
+ if (isConfigAvailableNetwork) {
+ dialAfterNetworkCheck();
+ } else {
+ view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ handleDialButtonPressed();
+ }
} else if (resId == R.id.deleteButton) {
keyPressed(KeyEvent.KEYCODE_DEL);
} else if (resId == R.id.digits) {
@@ -927,65 +1144,152 @@
}
}
+ private int getNumberfromId(int id) {
+ int number = -1;
+ switch(id) {
+ case R.id.zero: number = 0; break;
+ case R.id.one: number = 1; break;
+ case R.id.two: number = 2; break;
+ case R.id.three: number = 3; break;
+ case R.id.four: number = 4; break;
+ case R.id.five: number = 5; break;
+ case R.id.six: number = 6; break;
+ case R.id.seven: number = 7; break;
+ case R.id.eight: number = 8; break;
+ case R.id.nine: number = 9; break;
+ }
+ return number;
+ }
+
+ private void placeEmergencyCall() {
+ Resources resources = getContext().getResources();
+ String emergencyNumber = resources.getString(
+ com.android.internal.R.string.power_key_emergency_number);
+ Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY);
+ intent.setData(Uri.fromParts(PhoneAccount.SCHEME_TEL, emergencyNumber, null));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
@Override
public boolean onLongClick(View view) {
final Editable digits = mDigits.getText();
final int id = view.getId();
- if (id == R.id.deleteButton) {
- digits.clear();
- return true;
- } else if (id == R.id.one) {
- if (isDigitsEmpty() || TextUtils.equals(mDigits.getText(), "1")) {
- // We'll try to initiate voicemail and thus we want to remove irrelevant string.
- removePreviousDigitIfPossible('1');
+ switch (id) {
+ case R.id.deleteButton: {
+ digits.clear();
+ return true;
+ }
+ case R.id.one: {
+ if (isDigitsEmpty() || TextUtils.equals(mDigits.getText(), "1")) {
+ // We'll try to initiate voicemail and thus we want to remove irrelevant string.
+ removePreviousDigitIfPossible('1');
- List<PhoneAccountHandle> subscriptionAccountHandles =
- PhoneAccountUtils.getSubscriptionPhoneAccounts(getActivity());
- boolean hasUserSelectedDefault = subscriptionAccountHandles.contains(
- TelecomUtil.getDefaultOutgoingPhoneAccount(getActivity(),
- PhoneAccount.SCHEME_VOICEMAIL));
- boolean needsAccountDisambiguation = subscriptionAccountHandles.size() > 1
- && !hasUserSelectedDefault;
+ List<PhoneAccountHandle> subscriptionAccountHandles =
+ PhoneAccountUtils.getSubscriptionPhoneAccounts(getActivity());
+ boolean hasUserSelectedDefault = subscriptionAccountHandles.contains(
+ TelecomUtil.getDefaultOutgoingPhoneAccount(getActivity(),
+ PhoneAccount.SCHEME_VOICEMAIL));
+ boolean needsAccountDisambiguation = subscriptionAccountHandles.size() > 1
+ && !hasUserSelectedDefault;
- if (needsAccountDisambiguation || isVoicemailAvailable()) {
- // On a multi-SIM phone, if the user has not selected a default
- // subscription, initiate a call to voicemail so they can select an account
- // from the "Call with" dialog.
- callVoicemail();
- } else if (getActivity() != null) {
- // Voicemail is unavailable maybe because Airplane mode is turned on.
- // Check the current status and show the most appropriate error message.
+ if (needsAccountDisambiguation || isVoicemailAvailable()) {
+ // On a multi-SIM phone, if the user has not selected a default
+ // subscription, initiate a call to voicemail so they can select an account
+ // from the "Call with" dialog.
+ callVoicemail();
+ } else if (getActivity() != null) {
+ // Voicemail is unavailable maybe because Airplane mode is turned on.
+ // Check the current status and show the most appropriate error message.
+ final boolean isAirplaneModeOn =
+ Settings.System.getInt(getActivity().getContentResolver(),
+ Settings.System.AIRPLANE_MODE_ON, 0) != 0;
+ if (isAirplaneModeOn) {
+ DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
+ R.string.dialog_voicemail_airplane_mode_message);
+ dialogFragment.show(getFragmentManager(),
+ "voicemail_request_during_airplane_mode");
+ } else {
+ DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
+ R.string.dialog_voicemail_not_ready_message);
+ dialogFragment.show(getFragmentManager(), "voicemail_not_ready");
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ case R.id.zero: {
+ if (mPressedDialpadKeys.contains(view)) {
+ // If the zero key is currently pressed, then the long press occurred by touch
+ // (and not via other means like certain accessibility input methods).
+ // Remove the '0' that was input when the key was first pressed.
+ removePreviousDigitIfPossible('0');
+ }
+ keyPressed(KeyEvent.KEYCODE_PLUS);
+ stopTone();
+ mPressedDialpadKeys.remove(view);
+ return true;
+ }
+ case R.id.digits: {
+ mDigits.setCursorVisible(true);
+ return false;
+ }
+ case R.id.star: {
+ if (mDigits.length() > 1) {
+ // Remove tentative input ('*') done by onTouch().
+ removePreviousDigitIfPossible('*');
+ keyPressed(KeyEvent.KEYCODE_COMMA);
+ stopTone();
+ mPressedDialpadKeys.remove(view);
+ return true;
+ }
+ return false;
+ }
+ case R.id.pound: {
+ if (mDigits.length() > 1) {
+ // Remove tentative input ('#') done by onTouch().
+ removePreviousDigitIfPossible('#');
+ keyPressed(KeyEvent.KEYCODE_SEMICOLON);
+ stopTone();
+ mPressedDialpadKeys.remove(view);
+ return true;
+ }
+ return false;
+ }
+ case R.id.two:
+ case R.id.three:
+ case R.id.four:
+ case R.id.five:
+ case R.id.six:
+ case R.id.seven:
+ case R.id.eight:
+ case R.id.nine: {
+ if (mDigits.length() == 1) {
+ //removePreviousDigitIfPossible();
+ String property = SystemProperties.get(PROPERTY_RADIO_ATEL_CARRIER);
+ boolean isCarrierOneSupported = CARRIER_ONE_DEFAULT_MCC_MNC.equals(property);
+ if (isCarrierOneSupported &&
+ (getNumberfromId(id) == getContext().getResources().getInteger(
+ R.integer.speed_dial_emergency_number_assigned_key))) {
+ placeEmergencyCall();
+ return true;
+ }
final boolean isAirplaneModeOn =
Settings.System.getInt(getActivity().getContentResolver(),
Settings.System.AIRPLANE_MODE_ON, 0) != 0;
if (isAirplaneModeOn) {
DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
- R.string.dialog_voicemail_airplane_mode_message);
+ R.string.dialog_speed_dial_airplane_mode_message);
dialogFragment.show(getFragmentManager(),
- "voicemail_request_during_airplane_mode");
+ "speed_dial_request_during_airplane_mode");
} else {
- DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
- R.string.dialog_voicemail_not_ready_message);
- dialogFragment.show(getFragmentManager(), "voicemail_not_ready");
+ callSpeedNumber(id);
}
+ return true;
}
- return true;
+ return false;
}
- return false;
- } else if (id == R.id.zero) {
- if (mPressedDialpadKeys.contains(view)) {
- // If the zero key is currently pressed, then the long press occurred by touch
- // (and not via other means like certain accessibility input methods).
- // Remove the '0' that was input when the key was first pressed.
- removePreviousDigitIfPossible('0');
- }
- keyPressed(KeyEvent.KEYCODE_PLUS);
- stopTone();
- mPressedDialpadKeys.remove(view);
- return true;
- } else if (id == R.id.digits) {
- mDigits.setCursorVisible(true);
- return false;
}
return false;
}
@@ -1085,32 +1389,50 @@
* case described above).
*/
private void handleDialButtonPressed() {
- if (isDigitsEmpty()) { // No number entered.
+ if (isDigitsEmpty() && (mRecipients == null || !mRecipients.isShown())) {
+ // No number entered.
handleDialButtonClickWithEmptyDigits();
} else {
- final String number = mDigits.getText().toString();
-
- // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
- // test equipment.
- // TODO: clean it up.
- if (number != null
- && !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
- && number.matches(mProhibitedPhoneNumberRegexp)) {
- Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
- if (getActivity() != null) {
- DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
- R.string.dialog_phone_call_prohibited_message);
- dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
- }
-
- // Clear the digits just in case.
- clearDialpad();
+ boolean isDigitsShown = mDigits.isShown();
+ final String number = isDigitsShown ? mDigits.getText().toString() :
+ mRecipients.getText().toString().trim();
+ if (isDigitsShown && isDigitsEmpty()) {
+ handleDialButtonClickWithEmptyDigits();
+ } else if (mAddParticipant && isPhoneInUse() && isDigitsEmpty()
+ && mRecipients.isShown() && isRecipientEmpty()) {
+ // mRecipients must be empty
+ // TODO add support for conference URI in last number dialed
+ // use ErrorDialogFragment instead? also see
+ // android.app.AlertDialog
+ android.widget.Toast.makeText(getActivity(),
+ "Error: Cannot dial. Please provide conference recipients.",
+ android.widget.Toast.LENGTH_SHORT).show();
} else {
- final Intent intent = new CallIntentBuilder(number).
- setCallInitiationType(LogState.INITIATION_DIALPAD)
- .build();
- DialerUtils.startActivityWithErrorToast(getActivity(), intent);
- hideAndClearDialpad(false);
+ // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
+ // test equipment.
+ // TODO: clean it up.
+ if (number != null
+ && !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
+ && number.matches(mProhibitedPhoneNumberRegexp)) {
+ Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
+ if (getActivity() != null) {
+ DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
+ R.string.dialog_phone_call_prohibited_message);
+ dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
+ }
+
+ // Clear the digits just in case.
+ clearDialpad();
+ } else {
+ final Intent intent = CallUtil.getCallIntent(number);
+ if (!isDigitsShown) {
+ // must be dial conference add extra
+ intent.putExtra(EXTRA_DIAL_CONFERENCE_URI, true);
+ }
+ intent.putExtra(ADD_PARTICIPANT_KEY, mAddParticipant && isPhoneInUse());
+ DialerUtils.startActivityWithErrorToast(getActivity(), intent);
+ hideAndClearDialpad(false);
+ }
}
}
}
@@ -1457,6 +1779,10 @@
CallSubjectDialog.start(getActivity(), mDigits.getText().toString());
hideAndClearDialpad(false);
return true;
+ } else if (resId == R.id.menu_add_to_4g_conference_call){
+ getActivity().startActivity(IntentUtil.getConferenceDialerIntent(
+ mDigits.getText().toString()));
+ return true;
} else {
return false;
}
@@ -1594,6 +1920,13 @@
}
/**
+ * @return true if the widget with the mRecipients is empty.
+ */
+ private boolean isRecipientEmpty() {
+ return (mRecipients == null) || (mRecipients.length() == 0);
+ }
+
+ /**
* Starts the asyn query to get the last dialed/outgoing
* number. When the background query finishes, mLastNumberDialed
* is set to the last dialed number or an empty string if none
@@ -1692,4 +2025,95 @@
}
}
+ private void callSpeedNumber(int id) {
+ int number;
+
+ switch(id) {
+ case R.id.two: number = 2; break;
+ case R.id.three: number = 3; break;
+ case R.id.four: number = 4; break;
+ case R.id.five: number = 5; break;
+ case R.id.six: number = 6; break;
+ case R.id.seven: number = 7; break;
+ case R.id.eight: number = 8; break;
+ case R.id.nine: number = 9; break;
+ default: return;
+ }
+
+ String phoneNumber = SpeedDialUtils.getNumber(getActivity(), number);
+ if (phoneNumber == null) {
+ showNoSpeedNumberDialog(number);
+ } else {
+ final DialtactsActivity activity = getActivity() instanceof DialtactsActivity
+ ? (DialtactsActivity) getActivity() : null;
+ final Intent intent = CallUtil.getCallIntent(phoneNumber);
+ DialerUtils.startActivityWithErrorToast(getActivity(), intent);
+ hideAndClearDialpad(false);
+ }
+ }
+
+ private void showNoSpeedNumberDialog(final int number) {
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.speed_dial_unassigned_dialog_title)
+ .setMessage(getString(R.string.speed_dial_unassigned_dialog_message, number))
+ .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // go to speed dial setting screen to set speed dial number.
+ Intent intent = new Intent(getActivity(), SpeedDialListActivity.class);
+ startActivity(intent);
+ }
+ })
+ .setNegativeButton(R.string.no, null)
+ .show();
+ }
+
+ private void dialAfterNetworkCheck() {
+ if (ActivityCompat.checkSelfPermission(getContext(),
+ Manifest.permission.ACCESS_COARSE_LOCATION) !=
+ PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[] {Manifest.permission.ACCESS_COARSE_LOCATION},
+ PERMISSION_REQUEST_CODE_LOCATION);
+ } else {
+ if(WifiCallUtils.shallShowWifiCallDialog(getActivity())) {
+ WifiCallUtils.showWifiCallDialog(getActivity());
+ WifiCallUtils.showWifiCallNotification(getActivity());
+ } else {
+ getView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ handleDialButtonPressed();
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ switch (requestCode) {
+ case PERMISSION_REQUEST_CODE_LOCATION:
+ if ( grantResults.length > 0 && grantResults[0] ==
+ PackageManager.PERMISSION_GRANTED) {
+ if(WifiCallUtils.shallShowWifiCallDialog(getActivity())) {
+ WifiCallUtils.showWifiCallDialog(getActivity());
+ } else {
+ getView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ handleDialButtonPressed();
+ }
+ } else {
+ getView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ handleDialButtonPressed();
+ }
+ break;
+ case READ_WRITE_CALL_LOG_PERMISSION_REQUEST_CODE:
+ for (int i = 0; i < grantResults.length; i++) {
+ mHasReadAndWriteCallLogPermission &=
+ PackageManager.PERMISSION_GRANTED == grantResults[i];
+ }
+ if (mHasReadAndWriteCallLogPermission) {
+ queryLastOutgoingCall();
+ }
+ break;
+ default:
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
}
diff --git a/src/com/android/dialer/dialpad/SmartDialCursorLoader.java b/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
index 93b649b..16776d4 100644
--- a/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
+++ b/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
@@ -65,7 +65,7 @@
mQuery = SmartDialNameMatcher.normalizeNumber(query, SmartDialPrefix.getMap());
/** Constructs a name matcher object for matching names. */
- mNameMatcher = new SmartDialNameMatcher(mQuery, SmartDialPrefix.getMap());
+ mNameMatcher = new SmartDialNameMatcher(mQuery, SmartDialPrefix.getMap(), mContext);
}
/**
@@ -103,6 +103,8 @@
row[PhoneQuery.PHOTO_ID] = contact.photoId;
row[PhoneQuery.DISPLAY_NAME] = contact.displayName;
row[PhoneQuery.CARRIER_PRESENCE] = contact.carrierPresence;
+ row[PhoneQuery.PHONE_ACCOUNT_TYPE] = contact.accountType;
+ row[PhoneQuery.PHONE_ACCOUNT_NAME] = contact.accountName;
cursor.addRow(row);
}
return cursor;
diff --git a/src/com/android/dialer/dialpad/SmartDialNameMatcher.java b/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
index a54fe16..f82dbf8 100644
--- a/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
+++ b/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
@@ -17,13 +17,17 @@
package com.android.dialer.dialpad;
import android.support.annotation.Nullable;
+import android.content.Context;
import android.text.TextUtils;
+import android.util.Log;
+import com.android.dialer.database.DialerDatabaseHelper;
import com.android.dialer.dialpad.SmartDialPrefix.PhoneNumberTokens;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
+import java.lang.reflect.Method;
import java.util.ArrayList;
/**
@@ -35,6 +39,8 @@
*/
public class SmartDialNameMatcher {
+ private final static String TAG = "SmartDialNameMatcher";
+
private String mQuery;
// Whether or not we allow matches like 57 - (J)ohn (S)mith
@@ -53,14 +59,24 @@
private String mNameMatchMask = "";
private String mPhoneNumberMatchMask = "";
+ private Context mContext;
+ private String mSchar = "+*#-.(,)/ ";
+ private Object mMultiMatchObject;
+ private Method mMultiMatchMethod;
+
@VisibleForTesting
- public SmartDialNameMatcher(String query) {
- this(query, LATIN_SMART_DIAL_MAP);
+ public SmartDialNameMatcher(String query, Context context) {
+ this(query, LATIN_SMART_DIAL_MAP, context);
}
- public SmartDialNameMatcher(String query, SmartDialMap map) {
+ public SmartDialNameMatcher(String query, SmartDialMap map, Context context) {
mQuery = query;
mMap = map;
+ mContext = context;
+ mMultiMatchObject = DialerDatabaseHelper.getInstance(mContext)
+ .getMultiMatchObject();
+ mMultiMatchMethod = DialerDatabaseHelper.getInstance(mContext)
+ .getMultiMatchMethod();
}
/**
@@ -135,22 +151,6 @@
// Try matching the number as is
SmartDialMatchPosition matchPos = matchesNumberWithOffset(phoneNumber, query, 0);
- if (matchPos == null) {
- final PhoneNumberTokens phoneNumberTokens =
- SmartDialPrefix.parsePhoneNumber(phoneNumber);
-
- if (phoneNumberTokens == null) {
- return matchPos;
- }
- if (phoneNumberTokens.countryCodeOffset != 0) {
- matchPos = matchesNumberWithOffset(phoneNumber, query,
- phoneNumberTokens.countryCodeOffset);
- }
- if (matchPos == null && phoneNumberTokens.nanpCodeOffset != 0 && useNanp) {
- matchPos = matchesNumberWithOffset(phoneNumber, query,
- phoneNumberTokens.nanpCodeOffset);
- }
- }
if (matchPos != null) {
replaceBitInMask(builder, matchPos);
mPhoneNumberMatchMask = builder.toString();
@@ -195,40 +195,47 @@
*/
private SmartDialMatchPosition matchesNumberWithOffset(String phoneNumber, String query,
int offset) {
- if (TextUtils.isEmpty(phoneNumber) || TextUtils.isEmpty(query)) {
+ if (TextUtils.isEmpty(phoneNumber) || TextUtils.isEmpty(query)
+ || query.length() > phoneNumber.length()) {
return null;
}
- int queryAt = 0;
- int numberAt = offset;
- for (int i = offset; i < phoneNumber.length(); i++) {
- if (queryAt == query.length()) {
- break;
- }
- char ch = phoneNumber.charAt(i);
- if (mMap.isValidDialpadNumericChar(ch)) {
- if (ch != query.charAt(queryAt)) {
- return null;
+
+ String phoneNum = phoneNumber.replaceAll("[\\+\\*\\#\\-\\.\\(\\,\\)\\/ ]", "");
+ if (!TextUtils.isEmpty(phoneNum) && phoneNum.contains(query)) {
+ // firstly, find the start position in original phone number.
+ int start = phoneNum.indexOf(query);
+ int length = phoneNumber.length();
+ for (int i = start; i < length; i++) {
+ char ch = phoneNumber.charAt(i);
+ if (ch != phoneNum.charAt(start)) {
+ continue;
}
- queryAt++;
- } else {
- if (queryAt == 0) {
- // Found a separator before any part of the query was matched, so advance the
- // offset to avoid prematurely highlighting separators before the rest of the
- // query.
- // E.g. don't highlight the first '-' if we're matching 1-510-111-1111 with
- // '510'.
- // However, if the current offset is 0, just include the beginning separators
- // anyway, otherwise the highlighting ends up looking weird.
- // E.g. if we're matching (510)-111-1111 with '510', we should include the
- // first '('.
- if (offset != 0) {
- offset++;
- }
+ if (phoneNumber.substring(i).replaceAll("[\\+\\*\\#\\-\\.\\(\\,\\)\\/ ]", "")
+ .indexOf(query) == 0) {
+ start = i;
+ break;
}
}
- numberAt++;
+ // secondly, find the end position in original phone number.
+ int specialCount = 0;
+ int queryLength = query.length();
+ int end = start + queryLength;
+ for (int i = start; i < length; i++) {
+ char ch = phoneNumber.charAt(i);
+ if (mSchar.indexOf(ch) != -1) {
+ specialCount++;
+ continue;
+ }
+
+ if (i - start + 1 - specialCount == queryLength) {
+ end = i + 1;
+ break;
+ }
+ }
+ return new SmartDialMatchPosition(start, end);
+ } else {
+ return null;
}
- return new SmartDialMatchPosition(0 + offset, numberAt);
}
/**
@@ -412,7 +419,11 @@
public boolean matches(String displayName) {
mMatchPositions.clear();
- return matchesCombination(displayName, mQuery, mMatchPositions);
+ if (mMultiMatchObject != null && mMultiMatchMethod != null) {
+ return matchesMultiLanguage(displayName, mQuery, mMatchPositions);
+ } else {
+ return matchesCombination(displayName, mQuery, mMatchPositions);
+ }
}
public ArrayList<SmartDialMatchPosition> getMatchPositions() {
@@ -436,4 +447,47 @@
public String getQuery() {
return mQuery;
}
+
+ boolean matchesMultiLanguage(String displayName, String query,
+ ArrayList<SmartDialMatchPosition> matchList) {
+ StringBuilder builder = new StringBuilder();
+ constructEmptyMask(builder, displayName.length());
+ mNameMatchMask = builder.toString();
+ final int nameLength = displayName.length();
+ final int queryLength = query.length();
+
+ if (queryLength == 0) {
+ return false;
+ }
+ // contains the start, not the end poing
+ int[] indexs = null;
+ try {
+ indexs = (int[]) mMultiMatchMethod.invoke(mMultiMatchObject,
+ query, displayName, 0);
+ // mMultimatch.getMatchStringIndex(query, displayName, 0);
+ if (indexs == null) {
+ return false;
+ }
+ } catch (Exception e) {
+ Log.d(TAG, "Exception:" + e);
+ return false;
+ }
+
+ for (int i = 0; i < indexs.length; i = i + 2) {
+ int start = indexs[i];
+ int end = indexs[i + 1];
+ if (start >= 0 && end >= 0) {
+ matchList.add(new SmartDialMatchPosition(start, end + 1));
+ } else {
+ Log.d(TAG, "Invalid index, start is:" + start + " end is:"
+ + end + " for name:" + displayName);
+ }
+ }
+
+ for (SmartDialMatchPosition match : matchList) {
+ replaceBitInMask(builder, match);
+ }
+ mNameMatchMask = builder.toString();
+ return true;
+ }
}
diff --git a/src/com/android/dialer/filterednumber/BlockNumberDialogFragment.java b/src/com/android/dialer/filterednumber/BlockNumberDialogFragment.java
index 3c60a96..6255dec 100644
--- a/src/com/android/dialer/filterednumber/BlockNumberDialogFragment.java
+++ b/src/com/android/dialer/filterednumber/BlockNumberDialogFragment.java
@@ -238,7 +238,9 @@
final OnUnblockNumberListener onUndoListener = new OnUnblockNumberListener() {
@Override
public void onUnblockComplete(int rows, ContentValues values) {
- Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
+ if (mParentView != null) {
+ Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
+ }
if (callback != null) {
callback.onChangeFilteredNumberUndo();
}
@@ -256,12 +258,12 @@
mHandler.unblock(onUndoListener, uri);
}
};
-
- Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
- .setAction(R.string.block_number_undo, undoListener)
- .setActionTextColor(actionTextColor)
- .show();
-
+ if (mParentView != null) {
+ Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
+ .setAction(R.string.block_number_undo, undoListener)
+ .setActionTextColor(actionTextColor)
+ .show();
+ }
if (callback != null) {
callback.onFilterNumberSuccess();
}
@@ -287,7 +289,9 @@
final OnBlockNumberListener onUndoListener = new OnBlockNumberListener() {
@Override
public void onBlockComplete(final Uri uri) {
- Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
+ if (mParentView != null) {
+ Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
+ }
if (callback != null) {
callback.onChangeFilteredNumberUndo();
}
@@ -305,12 +309,12 @@
mHandler.blockNumber(onUndoListener, values);
}
};
-
- Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
- .setAction(R.string.block_number_undo, undoListener)
- .setActionTextColor(actionTextColor)
- .show();
-
+ if (mParentView != null) {
+ Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
+ .setAction(R.string.block_number_undo, undoListener)
+ .setActionTextColor(actionTextColor)
+ .show();
+ }
if (callback != null) {
callback.onUnfilterNumberSuccess();
}
diff --git a/src/com/android/dialer/list/SmartDialNumberListAdapter.java b/src/com/android/dialer/list/SmartDialNumberListAdapter.java
index fe27a25..ed1f825 100644
--- a/src/com/android/dialer/list/SmartDialNumberListAdapter.java
+++ b/src/com/android/dialer/list/SmartDialNumberListAdapter.java
@@ -45,7 +45,7 @@
public SmartDialNumberListAdapter(Context context) {
super(context);
- mNameMatcher = new SmartDialNameMatcher("", SmartDialPrefix.getMap());
+ mNameMatcher = new SmartDialNameMatcher("", SmartDialPrefix.getMap(), context);
setShortcutEnabled(SmartDialNumberListAdapter.SHORTCUT_DIRECT_CALL, false);
if (DEBUG) {
diff --git a/src/com/android/dialer/settings/DialerSettingsActivity.java b/src/com/android/dialer/settings/DialerSettingsActivity.java
index dc1e214..303c73a 100644
--- a/src/com/android/dialer/settings/DialerSettingsActivity.java
+++ b/src/com/android/dialer/settings/DialerSettingsActivity.java
@@ -30,6 +30,8 @@
import com.android.contacts.common.compat.CompatUtils;
import com.android.contacts.common.compat.TelephonyManagerCompat;
+import com.android.dialer.SpeedDialListActivity;
+import com.android.contacts.common.CallUtil;
import com.android.dialer.R;
import com.android.dialer.compat.FilteredNumberCompat;
import com.android.dialer.compat.SettingsCompat;
@@ -83,6 +85,14 @@
target.add(quickResponseSettingsHeader);
}
+ Header speedDialSettingsHeader = new Header();
+ Intent speedDialSettingsIntent = new Intent(this, SpeedDialListActivity.class);
+ speedDialSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ speedDialSettingsHeader.titleRes = R.string.speed_dial_settings;
+ speedDialSettingsHeader.intent = speedDialSettingsIntent;
+ target.add(speedDialSettingsHeader);
+
TelephonyManager telephonyManager =
(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
@@ -127,6 +137,27 @@
accessibilitySettingsHeader.intent = accessibilitySettingsIntent;
target.add(accessibilitySettingsHeader);
}
+ //video calling
+ boolean enablePresence = this.getResources().getBoolean(
+ R.bool.config_regional_presence_enable);
+ if(enablePresence){
+ Header videocallingHeader = new Header();
+ videocallingHeader.titleRes = R.string.video_call;
+ videocallingHeader.fragment = VideoCallingSettingsFragment.class.getName();
+ target.add(videocallingHeader);
+ }
+
+ boolean usageEnable = getResources().getBoolean(
+ R.bool.config_regional_call_data_usage_enable);
+ if (usageEnable) {
+ final Header historyInfoHeader = new Header();
+ historyInfoHeader.titleRes = R.string.call_data_info_label;
+ historyInfoHeader.summaryRes = R.string.call_data_info_description;
+ historyInfoHeader.intent = new Intent(Intent.ACTION_MAIN);
+ historyInfoHeader.intent
+ .setAction("android.intent.action.SHOW_TIMERINFO");
+ target.add(historyInfoHeader);
+ }
}
/**
diff --git a/src/com/android/dialer/settings/VideoCallingSettingsFragment.java b/src/com/android/dialer/settings/VideoCallingSettingsFragment.java
new file mode 100644
index 0000000..7a25901
--- /dev/null
+++ b/src/com/android/dialer/settings/VideoCallingSettingsFragment.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+package com.android.dialer.settings;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.SwitchPreference;
+import android.util.Log;
+
+import java.lang.Object;
+import java.lang.Override;
+import java.lang.String;
+
+import com.android.contacts.common.CallUtil;
+import com.android.dialer.R;
+
+public class VideoCallingSettingsFragment extends PreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+
+ private final static String KEY_VIDEO_CALL = "video_calling_preference";
+ private SwitchPreference mVideoCallingPreference;
+ private Context mContext;
+ private static final String TAG = "VideoCallingSettingsFragment";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.video_calling_settings);
+
+ mContext = getActivity();
+ mVideoCallingPreference = (SwitchPreference)findPreference(KEY_VIDEO_CALL);
+ mVideoCallingPreference.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ int enable = Settings.System.getInt(mContext.getContentResolver(),
+ CallUtil.DIALOG_VIDEO_CALLING,CallUtil.DISABLE_VIDEO_CALLING);
+ if(mVideoCallingPreference != null)
+ mVideoCallingPreference.setChecked(enable == CallUtil.ENABLE_VIDEO_CALLING);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ if (preference == mVideoCallingPreference) {
+ boolean isCheck = mVideoCallingPreference.isChecked();
+ CallUtil.createVideoCallingDialog(isCheck , mContext);
+ boolean isSaved = CallUtil.saveVideoCallConfig(mContext,isCheck);
+ Log.d(TAG, "onPreferenceChange isSaved = " + isSaved);
+ }
+ return true;
+ }
+
+}
diff --git a/src/com/android/dialer/util/AppCompatConstants.java b/src/com/android/dialer/util/AppCompatConstants.java
index 1d52eee..dd6c024 100644
--- a/src/com/android/dialer/util/AppCompatConstants.java
+++ b/src/com/android/dialer/util/AppCompatConstants.java
@@ -27,4 +27,10 @@
public static final int CALLS_REJECTED_TYPE = 5;
// Added to android.provider.CallLog.Calls in N+.
public static final int CALLS_BLOCKED_TYPE = 6;
+ public static final int INCOMING_IMS_TYPE = 8;
+ public static final int OUTGOING_IMS_TYPE = 9;
+ public static final int MISSED_IMS_TYPE = 10;
+ public static final int INCOMING_WIFI_TYPE = Calls.INCOMING_WIFI_TYPE;
+ public static final int OUTGOING_WIFI_TYPE = Calls.OUTGOING_WIFI_TYPE;
+ public static final int MISSED_WIFI_TYPE = Calls.MISSED_WIFI_TYPE;
}
diff --git a/src/com/android/dialer/util/DialerUtils.java b/src/com/android/dialer/util/DialerUtils.java
index 95d6a81..f2b04f9 100644
--- a/src/com/android/dialer/util/DialerUtils.java
+++ b/src/com/android/dialer/util/DialerUtils.java
@@ -23,6 +23,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
+import android.content.SharedPreferences;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
@@ -48,6 +49,9 @@
*/
public class DialerUtils {
+ private static final String PREFS_MESSAGE = "video_call_welcome";
+ private static final String KEY_STATE = "message-repeat";
+ private static final String KEY_FIRST_LAUNCH = "first-launch";
/**
* Attempts to start an activity and displays a toast with the default error message if the
* activity is not found, instead of throwing an exception.
@@ -192,4 +196,49 @@
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
+
+
+ /**
+ * @return true if it is the first launch.
+ */
+ public static boolean isFirstLaunch(Context context) {
+ final SharedPreferences prefs = context.getSharedPreferences(
+ PREFS_MESSAGE, Context.MODE_PRIVATE);
+ boolean isFirstLaunch = prefs.getBoolean(KEY_FIRST_LAUNCH, true);
+ if (isFirstLaunch) {
+ prefs.edit().putBoolean(KEY_FIRST_LAUNCH, false).apply();
+ }
+ return isFirstLaunch;
+ }
+
+ /**
+ * @return true if the Welcome Screen shall be presented to the user, false otherwise.
+ */
+ public static boolean canShowWelcomeScreen(Context context) {
+ final SharedPreferences prefs = context.getSharedPreferences(
+ PREFS_MESSAGE, Context.MODE_PRIVATE);
+ return prefs.getBoolean(KEY_STATE, false);
+ }
+
+
+ /**
+ * Save the state of Welcome Screen.
+ *
+ *@param context
+ *@param show if the Welcome Screen should be presented
+ */
+ public static void setShowingState(Context context, boolean show) {
+ final SharedPreferences prefs = context.getSharedPreferences(
+ PREFS_MESSAGE, Context.MODE_PRIVATE);
+ prefs.edit().putBoolean(KEY_STATE, show).apply();
+ }
+
+ /**
+ * @return true if calllog inserted earlier when dial a ConfURI call.
+ */
+ public static boolean isConferenceURICallLog(String number, String postDialDigits) {
+ return (number == null || number.contains(";") || number.contains(",")) &&
+ (postDialDigits == null || postDialDigits.equals(""));
+ }
+
}
diff --git a/src/com/android/dialer/util/IntentUtil.java b/src/com/android/dialer/util/IntentUtil.java
index 5a4a80b..bb87a49 100644
--- a/src/com/android/dialer/util/IntentUtil.java
+++ b/src/com/android/dialer/util/IntentUtil.java
@@ -17,14 +17,21 @@
package com.android.dialer.util;
import android.content.Intent;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
+import android.content.Context;
+import com.android.dialer.R;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
import com.android.contacts.common.CallUtil;
+import java.util.List;
/**
* Utilities for creation of intents in Dialer, such as {@link Intent#ACTION_CALL}.
@@ -155,4 +162,51 @@
intent.putExtra(ContactsContract.Intents.Insert.PHONE_TYPE, phoneNumberType);
}
}
+ /**
+ * if true, conference dialer is enabled.
+ */
+ public static boolean isConferDialerEnabled(Context context) {
+ boolean isEnabled = false;
+ List<SubscriptionInfo> subInfos = SubscriptionManager.from(context)
+ .getActiveSubscriptionInfoList();
+ if (subInfos != null) {
+ for (SubscriptionInfo subInfo : subInfos ) {
+ if (SubscriptionManager.isValidSubscriptionId(subInfo.getSubscriptionId())) {
+ Resources subRes = SubscriptionManager.getResourcesForSubId(context,
+ subInfo.getSubscriptionId());
+ if (subRes.getBoolean(R.bool.config_enable_conference_dialer)) {
+ TelephonyManager telephonyMgr = (TelephonyManager) context.
+ getSystemService(Context.TELEPHONY_SERVICE);
+ isEnabled = telephonyMgr.isImsRegisteredForSubscriber(subInfo
+ .getSubscriptionId());
+ if (isEnabled) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return isEnabled;
+ }
+
+ /**
+ * get intent to start conference dialer
+ * with this intent, we can originate an conference call
+ */
+ public static Intent getConferenceDialerIntent(String number) {
+ Intent intent = new Intent("android.intent.action.ADDPARTICIPANT");
+ intent.putExtra("confernece_number_key", number);
+ return intent;
+ }
+
+ /**
+ * used to get intent to start conference dialer
+ * with this intent, we can add participants to an existing conference call
+ */
+ public static Intent getAddParticipantsIntent(String number) {
+ Intent intent = new Intent("android.intent.action.ADDPARTICIPANT");
+ intent.putExtra("add_participant", true);
+ intent.putExtra("current_participant_list", number);
+ return intent;
+ }
}
diff --git a/src/com/android/dialer/util/PresenceHelper.java b/src/com/android/dialer/util/PresenceHelper.java
new file mode 100644
index 0000000..cf94df0
--- /dev/null
+++ b/src/com/android/dialer/util/PresenceHelper.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+package com.android.dialer.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.incallui.Log;
+
+import org.codeaurora.presenceserv.IPresenceService;
+import org.codeaurora.presenceserv.IPresenceServiceCB;
+
+/**
+ * General presnece service utility methods for the Dialer.
+ */
+public class PresenceHelper {
+
+ private static final String TAG = "PresenceHelper";
+ private static volatile IPresenceService mService;
+ private static boolean mIsBound;
+
+ private static ServiceConnection mConnection = new ServiceConnection() {
+
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "PresenceService connected");
+ mService = IPresenceService.Stub.asInterface(service);
+ try {
+ mService.registerCallback(mCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "PresenceService registerCallback error " + e);
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "PresenceService disconnected");
+ mService = null;
+ }
+ };
+
+ private static IPresenceServiceCB mCallback = new IPresenceServiceCB.Stub() {
+
+ public void setIMSEnabledCB() {
+ Log.d(TAG, "PresenceService setIMSEnabled callback");
+ }
+
+ };
+
+ public static void bindService(Context context) {
+ Log.d(TAG, "PresenceService BindService ");
+ Intent intent = new Intent(IPresenceService.class.getName());
+ intent.setClassName("com.qualcomm.qti.presenceserv",
+ "com.qualcomm.qti.presenceserv.PresenceService");
+ mIsBound = context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ public static void unbindService(Context context) {
+ Log.d(TAG, "PresenceService unbindService");
+ if (mService == null) {
+ Log.d(TAG, "PresenceService unbindService: mService is null");
+ return;
+ }
+ try {
+ mService.unregisterCallback(mCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "PresenceService unregister error " + e);
+ }
+ if (mIsBound) {
+ Log.d(TAG, "PresenceService unbind");
+ context.unbindService(mConnection);
+ mIsBound = false;
+ }
+ }
+
+ public static boolean isBound() {
+ return mIsBound;
+ }
+
+ public static boolean startAvailabilityFetch(String number){
+ Log.d(TAG, "startAvailabilityFetch number " + number);
+ if (mService == null) {
+ Log.d(TAG, "startAvailabilityFetch mService is null");
+ return false;
+ }
+ try {
+ return mService.invokeAvailabilityFetch(number);
+ } catch (Exception e) {
+ Log.d(TAG, "getVTCapOfContact ERROR " + e);
+ }
+ return false;
+ }
+
+ public static boolean getVTCapability(String number) {
+ Log.d(TAG, "getVTCapability number " + number);
+ if (null == mService) {
+ Log.d(TAG, "getVTCapability mService is null");
+ return false;
+ }
+ try {
+ return mService.hasVTCapability(number);
+ } catch (Exception e) {
+ Log.d(TAG, "getVTCapability ERROR " + e);
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/dialer/util/WifiCallUtils.java b/src/com/android/dialer/util/WifiCallUtils.java
new file mode 100644
index 0000000..5e789ed
--- /dev/null
+++ b/src/com/android/dialer/util/WifiCallUtils.java
@@ -0,0 +1,225 @@
+/*
+* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are
+* met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above
+* copyright notice, this list of conditions and the following
+* disclaimer in the documentation and/or other materials provided
+* with the distribution.
+* * Neither the name of The Linux Foundation nor the names of its
+* contributors may be used to endorse or promote products derived
+* from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.android.dialer.util;
+
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.provider.Settings;
+import android.telephony.CellInfo;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.dialer.R;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class WifiCallUtils {
+
+ private static final String TAG = WifiCallUtils.class.getSimpleName();
+ private static final int DELAYED_TIME = 5 * 1000;
+ private static final int NOTIFICATION_WIFI_CALL_ID = 1;
+ private WindowManager mWindowManager;
+ private TextView mTextView;
+ private boolean mViewRemoved = true;
+
+ private static final String WIFI_CALL_READY = "wifi_call_ready";
+ private static final String WIFI_CALL_TURNON = "wifi_call_turnon";
+ private static final int WIFI_CALLING_DISABLED = 0;
+ private static final int WIFI_CALLING_ENABLED = 1;
+
+ public void addWifiCallReadyMarqueeMessage(Context context) {
+ if (mViewRemoved && isWifiCallReadyEnabled(context)) {
+ if (mWindowManager == null) mWindowManager =
+ (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ if(mTextView == null){
+ mTextView = new TextView(context);
+ Log.d(TAG, "mTextView is null, new mTextView = " + mTextView);
+ mTextView.setText(
+ com.android.dialer.R.string.alert_call_over_wifi);
+ mTextView.setSingleLine(true);
+ mTextView.setEllipsize(android.text.TextUtils.TruncateAt.MARQUEE);
+ mTextView.setMarqueeRepeatLimit(-1);
+ mTextView.setFocusableInTouchMode(true);
+ } else {
+ Log.d(TAG, "mTextView is not null, mTextView = " + mTextView);
+ }
+
+ WindowManager.LayoutParams windowParam = new WindowManager.LayoutParams();
+ windowParam.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+ windowParam.format= 1;
+ windowParam.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ windowParam.alpha = 1.0f;
+ windowParam.x = 0;
+ windowParam.y = -500;
+ windowParam.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ windowParam.width = (mWindowManager.getDefaultDisplay().getWidth()
+ < mWindowManager.getDefaultDisplay().getHeight()
+ ? mWindowManager.getDefaultDisplay().getWidth()
+ : mWindowManager.getDefaultDisplay().getHeight()) - 64;
+ mWindowManager.addView(mTextView, windowParam);
+ mViewRemoved = false;
+ Log.d(TAG, "addWifiCallReadyMarqueeMessage, mWindowManager:" + mWindowManager
+ + " addView, mTextView:" + mTextView
+ + " addWifiCallReadyMarqueeMessage, mViewRemoved = " + mViewRemoved);
+
+ scheduleRemoveWifiCallReadyMarqueeMessageTimer();
+ }
+ }
+
+ public void removeWifiCallReadyMarqueeMessage() {
+ if (!mViewRemoved) {
+ mWindowManager.removeView(mTextView);
+ mViewRemoved = true;
+ Log.d(TAG, "removeWifiCallReadyMarqueeMessage, mWindowManager:" + mWindowManager
+ + " removeView, mTextView:" + mTextView
+ + " removeWifiCallReadyMarqueeMessage, mViewRemoved = " + mViewRemoved);
+ }
+ }
+
+ private void scheduleRemoveWifiCallReadyMarqueeMessageTimer() {
+ // Schedule a timer, 5s later, remove the message
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "Handler is running");
+ removeWifiCallReadyMarqueeMessage();
+ }
+ }, DELAYED_TIME);
+ Log.d(TAG, "schedule timerTask");
+ }
+
+ private static boolean isCellularNetworkAvailable(Context context) {
+ boolean available = false;
+
+ TelephonyManager tm = (TelephonyManager) context.
+ getSystemService(Context.TELEPHONY_SERVICE);
+ List<CellInfo> cellInfoList = tm.getAllCellInfo();
+
+ if (cellInfoList != null) {
+ for (CellInfo cellinfo : cellInfoList) {
+ if (cellinfo.isRegistered()) {
+ available = true;
+ }
+ }
+ }
+
+ return available;
+ }
+
+ public static void showWifiCallDialog(final Context context) {
+ String promptMessage = context.getString(com.android.dialer.R.string
+ .alert_call_no_cellular_coverage);
+ AlertDialog.Builder diaBuilder = new AlertDialog.Builder(context);
+ diaBuilder.setMessage(promptMessage);
+ diaBuilder.setPositiveButton(com.android.internal.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS);
+ context.startActivity(intent);
+ }
+ });
+ diaBuilder.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ }
+ });
+ diaBuilder.create().show();
+ }
+
+ public static boolean isWifiCallReadyEnabled(final Context context) {
+ return (Settings.Global.getInt(context.getContentResolver(),
+ WIFI_CALL_READY, WIFI_CALLING_DISABLED) == WIFI_CALLING_ENABLED);
+ }
+
+ public static boolean isWifiCallTurnOnEnabled(final Context context){
+ return (Settings.Global.getInt(context.getContentResolver(),
+ WIFI_CALL_TURNON, WIFI_CALLING_DISABLED) == WIFI_CALLING_ENABLED);
+ }
+
+ public static boolean shallShowWifiCallDialog(final Context context) {
+ boolean wifiCallTurnOn = isWifiCallTurnOnEnabled(context);
+
+ ConnectivityManager conManager = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo wifiNetworkInfo = conManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ boolean wifiAvailableNotConnected =
+ wifiNetworkInfo.isAvailable() && !wifiNetworkInfo.isConnected();
+
+ return wifiCallTurnOn && wifiAvailableNotConnected && !isCellularNetworkAvailable(context);
+ }
+
+ public static void showWifiCallNotification(final Context context) {
+ if (shallShowWifiCallDialog(context)) {
+ final NotificationManager notiManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ Intent intent = new Intent();
+ intent.setAction(android.provider.Settings.ACTION_WIFI_SETTINGS);
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(
+ context, NOTIFICATION_WIFI_CALL_ID,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ Notification.Builder builder = new Notification.Builder(context);
+ builder.setOngoing(false);
+ builder.setWhen(0);
+ builder.setContentIntent(pendingIntent);
+ builder.setAutoCancel(true);
+ builder.setSmallIcon(R.drawable.wifi_calling_on_notification);
+ builder.setContentTitle(
+ context.getResources().getString(
+ R.string.alert_user_connect_to_wifi_for_call));
+ builder.setContentText(
+ context.getResources().getString(
+ R.string.alert_user_connect_to_wifi_for_call_text));
+ notiManager.notify(NOTIFICATION_WIFI_CALL_ID, builder.build());
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ notiManager.cancel(NOTIFICATION_WIFI_CALL_ID);
+ }
+ }, 5000);
+ }
+ }
+}
diff --git a/src/com/android/dialer/widget/EmptyContentView.java b/src/com/android/dialer/widget/EmptyContentView.java
index 719fd3f..70f0e5a 100644
--- a/src/com/android/dialer/widget/EmptyContentView.java
+++ b/src/com/android/dialer/widget/EmptyContentView.java
@@ -74,6 +74,11 @@
mDescriptionView.setText(null);
mDescriptionView.setVisibility(View.GONE);
} else {
+ if (resourceId == R.string.no_call_log) {
+ mDescriptionView.setText(resourceId);
+ mDescriptionView.setVisibility(View.VISIBLE);
+ mDescriptionView.setPadding(0, 0, 0, 700);
+ }
mDescriptionView.setText(resourceId);
mDescriptionView.setVisibility(View.VISIBLE);
}
diff --git a/src/com/android/dialer/widget/SearchEditTextLayout.java b/src/com/android/dialer/widget/SearchEditTextLayout.java
index 4f100dc..abc5c04 100644
--- a/src/com/android/dialer/widget/SearchEditTextLayout.java
+++ b/src/com/android/dialer/widget/SearchEditTextLayout.java
@@ -27,6 +27,11 @@
import android.view.View;
import android.widget.EditText;
import android.widget.FrameLayout;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.speech.RecognizerIntent;
+import java.util.List;
import com.android.dialer.R;
import com.android.dialer.util.DialerUtils;
@@ -267,7 +272,11 @@
mSearchIcon.setVisibility(collapsedViewVisibility);
mCollapsedSearchBox.setVisibility(collapsedViewVisibility);
- mVoiceSearchButtonView.setVisibility(collapsedViewVisibility);
+ if (!isExpand && canIntentBeHandled()) {
+ mVoiceSearchButtonView.setVisibility(collapsedViewVisibility);
+ } else {
+ mVoiceSearchButtonView.setVisibility(View.GONE);
+ }
mOverflowButtonView.setVisibility(collapsedViewVisibility);
mBackButtonView.setVisibility(expandedViewVisibility);
// TODO: Prevents keyboard from jumping up in landscape mode after exiting the
@@ -318,4 +327,12 @@
params.rightMargin = (int) (mRightMargin * fraction);
requestLayout();
}
+
+ private boolean canIntentBeHandled() {
+ final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ final PackageManager packageManager = getContext().getPackageManager();
+ final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(voiceIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return resolveInfo != null && resolveInfo.size() > 0;
+ }
}
diff --git a/src/org/codeaurora/presenceserv/IPresenceService.aidl b/src/org/codeaurora/presenceserv/IPresenceService.aidl
new file mode 100644
index 0000000..edd0ac1
--- /dev/null
+++ b/src/org/codeaurora/presenceserv/IPresenceService.aidl
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+
+package org.codeaurora.presenceserv;
+
+import org.codeaurora.presenceserv.IPresenceServiceCB;
+
+/**
+ * Presence service interface.
+ */
+interface IPresenceService {
+
+ String getImsEnablerState();
+ boolean hasVTCapability(String number);
+ void invokPublish();
+ boolean invokeAvailabilityFetch(String number);
+ void invokeCapabilityPolling(String number);
+ void invokeListAvailabilityFetch();
+ void invokeListCapabilityPolling();
+ void registerCallback(IPresenceServiceCB cb);
+ void unregisterCallback(IPresenceServiceCB cb);
+
+}
+
diff --git a/src/org/codeaurora/presenceserv/IPresenceServiceCB.aidl b/src/org/codeaurora/presenceserv/IPresenceServiceCB.aidl
new file mode 100644
index 0000000..05ad7d7
--- /dev/null
+++ b/src/org/codeaurora/presenceserv/IPresenceServiceCB.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **/
+package org.codeaurora.presenceserv;
+
+/**
+ * Presence service callback interface.
+ */
+interface IPresenceServiceCB {
+
+ void setIMSEnabledCB();
+}
+
diff --git a/tests/Android.mk b/tests/Android.mk
index 07f4f00..1b39526 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -5,7 +5,8 @@
LOCAL_MODULE_TAGS := tests
LOCAL_CERTIFICATE := shared
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test \
+ ims-ext-common
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/src/com/android/dialer/SpecialCharSequenceMgrTest.java b/tests/src/com/android/dialer/SpecialCharSequenceMgrTest.java
new file mode 100644
index 0000000..79179ae
--- /dev/null
+++ b/tests/src/com/android/dialer/SpecialCharSequenceMgrTest.java
@@ -0,0 +1,64 @@
+/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package com.android.dialer;
+
+import android.content.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.AndroidTestCase;
+import java.lang.reflect.*;
+import junit.framework.Assert;
+import junit.framework.TestCase;
+import com.android.dialer.SpecialCharSequenceMgr;
+
+@SmallTest
+public class SpecialCharSequenceMgrTest extends AndroidTestCase {
+
+ Context context;
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ context = getContext();
+ }
+
+ public void testHandlePRLVersion(){
+ Class<SpecialCharSequenceMgr> scMgrClass = SpecialCharSequenceMgr.class;
+
+ try {
+ Method handlePRLVersion = scMgrClass.getDeclaredMethod("handlePRLVersion",
+ Context.class, String.class);
+ handlePRLVersion.setAccessible(true);
+ boolean result1 = (boolean)handlePRLVersion.invoke(context, "*#0000#");
+ boolean result2 = (boolean)handlePRLVersion.invoke(context, "*#BAD#");
+ assertTrue(result1);
+ assertFalse(result2);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java
index daba428..7108841 100644
--- a/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java
+++ b/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java
@@ -109,14 +109,14 @@
public void testSetPhoneCallDetails_ReadVoicemail() {
PhoneCallDetails details =
getPhoneCallDetailsWithTypes(AppCompatConstants.CALLS_VOICEMAIL_TYPE);
- mHelper.setPhoneCallDetails(mViewHolder, details);
+ mHelper.setPhoneCallDetails(mViewHolder, details, null);
assertEquals(View.VISIBLE, mViewHolder.voicemailPlaybackView.getVisibility());
}
public void testSetPhoneCallDetails_UnreadVoicemail() {
PhoneCallDetails details =
getPhoneCallDetailsWithTypes(AppCompatConstants.CALLS_VOICEMAIL_TYPE);
- mHelper.setPhoneCallDetails(mViewHolder, details);
+ mHelper.setPhoneCallDetails(mViewHolder, details, null);
assertEquals(View.VISIBLE, mViewHolder.voicemailPlaybackView.getVisibility());
}
@@ -264,7 +264,7 @@
PhoneCallDetails details = getPhoneCallDetails(
number, postDialDigits, presentation, formattedNumber);
details.callTypes = new int[] {callType};
- mHelper.setPhoneCallDetails(mViewHolder, details);
+ mHelper.setPhoneCallDetails(mViewHolder, details, null);
}
private PhoneCallDetails getPhoneCallDetails(
diff --git a/tests/src/com/android/dialer/calllog/calllogcache/TestTelecomCallLogCache.java b/tests/src/com/android/dialer/calllog/calllogcache/TestTelecomCallLogCache.java
index 270019a..a7f7988 100644
--- a/tests/src/com/android/dialer/calllog/calllogcache/TestTelecomCallLogCache.java
+++ b/tests/src/com/android/dialer/calllog/calllogcache/TestTelecomCallLogCache.java
@@ -17,6 +17,7 @@
package com.android.dialer.calllog.calllogcache;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -62,4 +63,9 @@
public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
return false;
}
+
+ @Override
+ public Drawable getAccountIcon(PhoneAccountHandle accountHandle) {
+ return null;
+ }
}
diff --git a/tests/src/com/android/dialer/database/DialerDatabaseHelperTest.java b/tests/src/com/android/dialer/database/DialerDatabaseHelperTest.java
index a95a79e..14a0bfd 100644
--- a/tests/src/com/android/dialer/database/DialerDatabaseHelperTest.java
+++ b/tests/src/com/android/dialer/database/DialerDatabaseHelperTest.java
@@ -148,7 +148,7 @@
private ArrayList<ContactNumber> getMatchesFromDb(String query) {
final SmartDialNameMatcher nameMatcher = new SmartDialNameMatcher(query,
- SmartDialPrefix.getMap());
+ SmartDialPrefix.getMap(), getContext());
return mTestHelper.getLooseMatches(query, nameMatcher);
}
}
diff --git a/tests/src/com/android/dialer/database/SmartDialPrefixTest.java b/tests/src/com/android/dialer/database/SmartDialPrefixTest.java
index 78962e3..7fde707 100644
--- a/tests/src/com/android/dialer/database/SmartDialPrefixTest.java
+++ b/tests/src/com/android/dialer/database/SmartDialPrefixTest.java
@@ -89,7 +89,7 @@
private ArrayList<ContactNumber> getLooseMatchesFromDb(String query) {
final SmartDialNameMatcher nameMatcher = new SmartDialNameMatcher(query,
- SmartDialPrefix.getMap());
+ SmartDialPrefix.getMap(), getContext());
return mTestHelper.getLooseMatches(query, nameMatcher);
}
diff --git a/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java b/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
index c1365f5..b0dc91d 100644
--- a/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
+++ b/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
@@ -30,7 +30,7 @@
import junit.framework.TestCase;
@SmallTest
-public class SmartDialNameMatcherTest extends TestCase {
+public class SmartDialNameMatcherTest extends AndroidTestCase {
private static final String TAG = "SmartDialNameMatcherTest";
public void testMatches() {
@@ -239,7 +239,7 @@
private void checkMatchesNumber(String number, String query, boolean expectedMatches,
boolean matchNanp, int matchStart, int matchEnd) {
- final SmartDialNameMatcher matcher = new SmartDialNameMatcher(query);
+ final SmartDialNameMatcher matcher = new SmartDialNameMatcher(query, getContext());
final SmartDialMatchPosition pos = matcher.matchesNumber(number, query, matchNanp);
assertEquals(expectedMatches, pos != null);
if (expectedMatches) {
@@ -250,7 +250,7 @@
private void checkMatches(String displayName, String query, boolean expectedMatches,
int... expectedMatchPositions) {
- final SmartDialNameMatcher matcher = new SmartDialNameMatcher(query);
+ final SmartDialNameMatcher matcher = new SmartDialNameMatcher(query, getContext());
final ArrayList<SmartDialMatchPosition> matchPositions =
new ArrayList<SmartDialMatchPosition>();
final boolean matches = matcher.matchesCombination(