summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRekha Kumar <rekhak@codeaurora.org>2015-03-18 09:55:55 -0700
committerRekha Kumar <rekhak@quicinc.com>2015-03-26 22:56:33 +0000
commit18c0feda76fe333f3db1bf7bd307458a9e6b6005 (patch)
tree61c6fcc9baa269a3c81298ae13c8e4a9ac909144
parent22875bb1d3fa4f023e6ca127a77159ab42bbbabc (diff)
IMS-VT: Add support for video calls
IMS-VT: Upgrade downgrade and hold resume video calls -Add support for upgrade downgrade video calls. -Add support for hold resume and call waiting IMS-VT: Fixed surface caching. Fixed surface caching. IMS-VT: Fixes InCallUI/Dialer crash when there is a VT call. -Fixes InCallUI/Dialer crash when UE is rotated. -Fixes InCallUI/Dialer crash when UE VoLTE call is upgraded to VT. Don't default to speaker phone for VT when speaker is disabled - The adb property persist.radio.ims.audio.output indicates whether speaker is disabled explicitly or not. - If the above property is set to 1, don't enable speaker phone by default in VT call. IMS-VT: Fix CVO, surface and resource related issues. - Send initial orientation to VT Service. - Detect if Activity is being destroyed due to confugration changes. - Close the camera when InCallUI is pushed to background. - Fix surface and VT service related issues when Fragment and Presenter gets destroyed. IMS-VT: Peer Resolution Feature Implementation Change display video size based on peer resolution values received from far end. IMS-VT: Answering calls and upgrade requests as VT_TX and VT_RX -Support for showing one way options for incoming upgrade request -Fix issue where incoming video popup stays on screen even after rejecting the request. -Fix issue where incoming video popup stays on even after it has been timed out by lower layers. - Answer with different calltypes support. Notify listeners of video quality changed event and display message on UI - Add methods to notify listeners of type VideoEventListener when video quality changes. - Display a notification on the UI when video quality changes. IMS-VT: Enable SIP based video multitasking. Enable SIP based video multitasking. IMS-VT: Call data usage feature - Add support for call data usage callback - Request for call data usage stats - Log call data usage stats when IMS layers send update Change-Id: I3f0dde0d82698085fa5d3f110720f10326eca768 IMS-VT: When TTY is ON, do not allow upgrade to VT call When TTY mode is ON, the user SHOULD NOT be allowed to upgrade a call from VOLTE to VT/VT-TX/VT-RX and an UI alert message will be displayed explaining upgrade to VT call cannot be initiated. IMS-VT: Add null check in call upgrade fail scenario Crash is observed if call is ended in the interval wherein handler is started to change state from REQUEST_FAILED to NO_REQUEST after an interval. Added null check to change state only when call exists Propagate call substate message and display a notification on the UI Ims: Reject upgrade request 1. If there is waiting call that is pending user action and 2. Before offering a waiting call IMS-VT: Provide Player State indication to user. Change to display "player started/stopped" toast message whenever video starts/stops flowing IMS-VT: Cleanup video views when not required - Hide video views when not required. Set display size as per current TextureView size - Using TextureView height and width to calculate the display size. - When ever there is change in display make sure that center the display. IMS-VT: Exit VT call full screen mode. If the call is no longer a VT call, then exit full screen mode Change-Id: Ibc4ad8f9a4c38e467820028cdc2c7e68d65fd93c CRs-Fixed: 760925 IMS-VT: Upgrade button fix -Show upgrade/downgrade button only when call is in ACTIVE or ONHOLD state IMS-VT: Show correct call types during video pause -In paused state, upgrade downgrade button does not list the calltypes dropdown box -Remove the paused bit to calculate call type during video paused state. IMS-VT: Move strings for video quality changed indication to resource files IMS-VT: Clean up the showCallSubstateChanged API - Make the code more readable. IMS-VT: Set audio route to Speaker if current route is not bluetooth or headset - We always set the audio route to speaker when we enter video mode. - That is not correct. We should check if headset or bluetooth is connected before defaulting to speaker. IMS-VT: Turn speaker on/off for video calls based on call state changes - Turn speaker on only when video call is active or dialing - Switch back to the previous audio mode when there are no more calls - Make the previous audio mode static so that information persists when the class is recreated (e.g. when UE is rotated or multitasking happens) IMS-VT: Use back camera instead of front camera for VT-TX Set back camera for below cases 1) VOLTE to VT-TX 2) VT to VT-TX 3) Waiting call over Vt-TX call. IMS-VT: Fix upgrade in call waiting scenarios -When one VoLTE call is active and another VoLTE call is on hold and upgrade button is hit, call ends. Fix this upgrade issue. IMS-VT: Enter video mode when the primary video call changes - We only enter video mode when we have any video state changes - Enter video mode should also be called when the video call changes. e.g. When we have a second call with the same video state, we don't enter video mode which is incorrect. This change fixes the issue Add null check for InCallActivity in setInCallAllowsOrientationChange - In some cases, this API is called when InCallActivity is null. This causes a null pointer exception. Fix is to add a null check. IMS-VT: Fix check for VOLTE call (AND to OR) when turning speaker on IMS-VT: Check if call is video before enabling speaker in updateAudioMode - Moved the video call check to the correct block where we are enabling speaker IMS-VT: Get call substate values correctly from the bit mask - We were getting the call substate incorrectly as an int. - Fixed that by getting it from the bit mask and using the possible multiple values to display the call substate message. IMS-VT: Keep the screen on during video calls. UI screens times out after some time. During video calls the screen must be kept on. Change-Id: Icaa8662210b2dd323b29f4a472869a9ed1e01d00 IMS-VT: Open front facing camera for VT calls. Open front facing camera for VT calls. IMS-VT: Show manage conference button for Video Call - We show the manage conference in a separate section for VOLTE Calls. - In the case of Video Call, the entire screen is occupied by the far end video. So we display manage conference in the overflow section of the call button fragment. IMS-VT: Fix camera preview freeze for CONF video calls. Fix camera preview freeze for CONF video calls. IMS-VT: Move persist.radio.ims.audio.output to frameworks Move persist.radio.ims.audio.output and related constants to TelephonyProperties and PhoneConstants so that these can be accessed from multiple git projects instead of redefining. IMS-VT: Multiple video call fixes/optimizations. -Current implementation clears primary call cache upon exiting video mode. This will remove video call provider interface as well, which will prevent further communication with the backend. Don't clear primary call cache upon exiting video mode. -Unregister call details listener when UI transitions into unready state -Send surface of incoming video to video call provider before opening camera. IMS: Cleanup all listener objects Clean all stale listener objects to avoid the memory leak. IMS: Fix speaker icon display issue in Call UI. In few devices, InCall UI can show upto five buttons in a row. Modify code to display at most five buttons in a row. Show overflow menu if number of buttons is more than five. IMS-VT: Show glowpad view with accept/reject for most video upgrade cases - Show the glowpad view with all options only when upgrading from Volte to VT - For all other cases, show the glowpad with accept/reject only Change-Id: I41ecbda40db7c3c69428fc4272f8bfbd258e2980
-rw-r--r--InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml30
-rw-r--r--InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml28
-rw-r--r--InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml36
-rw-r--r--InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml30
-rw-r--r--InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml28
-rw-r--r--InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml36
-rw-r--r--InCallUI/res/layout-land/call_card_content.xml7
-rw-r--r--InCallUI/res/layout-land/video_call_views.xml4
-rw-r--r--InCallUI/res/layout/call_button_fragment.xml7
-rw-r--r--InCallUI/res/layout/call_card_content.xml7
-rw-r--r--InCallUI/res/layout/video_call_views.xml4
-rw-r--r--InCallUI/res/menu/incall_overflow_menu.xml3
-rw-r--r--InCallUI/res/values/array.xml43
-rw-r--r--InCallUI/res/values/strings.xml60
-rw-r--r--InCallUI/src/com/android/incallui/AnswerFragment.java35
-rw-r--r--InCallUI/src/com/android/incallui/AnswerPresenter.java116
-rw-r--r--InCallUI/src/com/android/incallui/Call.java143
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonFragment.java119
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonPresenter.java100
-rw-r--r--InCallUI/src/com/android/incallui/CallCardFragment.java5
-rw-r--r--InCallUI/src/com/android/incallui/CallCardPresenter.java3
-rw-r--r--InCallUI/src/com/android/incallui/CallList.java16
-rw-r--r--InCallUI/src/com/android/incallui/CallUtils.java90
-rw-r--r--InCallUI/src/com/android/incallui/GlowPadWrapper.java14
-rw-r--r--InCallUI/src/com/android/incallui/InCallActivity.java34
-rw-r--r--InCallUI/src/com/android/incallui/InCallApp.java4
-rw-r--r--InCallUI/src/com/android/incallui/InCallCameraManager.java25
-rw-r--r--InCallUI/src/com/android/incallui/InCallPresenter.java109
-rw-r--r--InCallUI/src/com/android/incallui/InCallVideoCallListener.java79
-rw-r--r--InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java75
-rw-r--r--InCallUI/src/com/android/incallui/Log.java2
-rw-r--r--InCallUI/src/com/android/incallui/VideoCallFragment.java441
-rw-r--r--InCallUI/src/com/android/incallui/VideoCallPresenter.java715
-rw-r--r--InCallUI/src/com/android/incallui/VideoPauseController.java389
34 files changed, 2538 insertions, 299 deletions
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml
new file mode 100644
index 000000000..c5a41d814
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014, 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/ic_lockscreen_answer_rx_video_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/ic_lockscreen_answer_rx_video_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/ic_lockscreen_answer_rx_video_activated_layer" />
+</selector>
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml
new file mode 100644
index 000000000..750ef5e26
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014, 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/ic_lockscreen_answer_rx_video_normal_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml
new file mode 100644
index 000000000..5efd3d142
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014, 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/ic_lockscreen_answer_tx_video.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml
new file mode 100644
index 000000000..15d11978e
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014, 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/ic_lockscreen_answer_tx_video_normal_layer"/>
+ <item
+ android:state_enabled="true" android:state_active="true" android:state_focused="false"
+ android:drawable="@drawable/ic_lockscreen_answer_tx_video_activated_layer" />
+ <item
+ android:state_enabled="true" android:state_active="false" android:state_focused="true"
+ android:drawable="@drawable/ic_lockscreen_answer_tx_video_activated_layer" />
+</selector>
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml
new file mode 100644
index 000000000..c1dca4d06
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014, 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/ic_lockscreen_answer_tx_video_normal_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml
new file mode 100644
index 000000000..b0ad943dc
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2014, 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/layout-land/call_card_content.xml b/InCallUI/res/layout-land/call_card_content.xml
index 496b6b399..0bb45a244 100644
--- a/InCallUI/res/layout-land/call_card_content.xml
+++ b/InCallUI/res/layout-land/call_card_content.xml
@@ -71,6 +71,13 @@
android:layout_height="wrap_content"
android:layout_alignTop="@id/photo" />
+ <fragment android:name="com.android.incallui.VideoCallFragment"
+ android:layout_alignParentStart="true"
+ android:layout_gravity="start|center_vertical"
+ android:id="@+id/videoCallFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<!-- Progress spinner, useful for indicating pending operations such as upgrade to video. -->
<FrameLayout
android:id="@+id/progressSpinner"
diff --git a/InCallUI/res/layout-land/video_call_views.xml b/InCallUI/res/layout-land/video_call_views.xml
index 7065d4515..8961ea4bf 100644
--- a/InCallUI/res/layout-land/video_call_views.xml
+++ b/InCallUI/res/layout-land/video_call_views.xml
@@ -22,7 +22,7 @@
<TextureView
android:id="@+id/incomingVideo"
- android:layout_gravity="center_horizontal"
+ android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- The width and height are replaced at runtime based on the selected camera. -->
@@ -32,4 +32,4 @@
android:layout_margin="@dimen/video_preview_margin"
android:layout_width="70dp"
android:layout_height="120dp" />
-</FrameLayout> \ No newline at end of file
+</FrameLayout>
diff --git a/InCallUI/res/layout/call_button_fragment.xml b/InCallUI/res/layout/call_button_fragment.xml
index e8feca68a..69d0ee3a0 100644
--- a/InCallUI/res/layout/call_button_fragment.xml
+++ b/InCallUI/res/layout/call_button_fragment.xml
@@ -160,6 +160,13 @@
android:contentDescription="@string/onscreenOverflowText"
android:visibility="gone" />
+ <!-- "Manage conference button (Video Call) " -->
+ <ImageButton android:id="@+id/manageVideoCallConferenceButton"
+ style="@style/InCallButton"
+ android:background="@drawable/ic_group_white_24dp"
+ android:contentDescription="@string/onscreenManageConferenceText"
+ android:visibility="gone" />
+
</LinearLayout>
</LinearLayout>
diff --git a/InCallUI/res/layout/call_card_content.xml b/InCallUI/res/layout/call_card_content.xml
index 7a42586ea..a5fd8f72a 100644
--- a/InCallUI/res/layout/call_card_content.xml
+++ b/InCallUI/res/layout/call_card_content.xml
@@ -70,6 +70,13 @@
android:background="@android:color/white"
android:src="@drawable/img_no_image_automirrored" />
+ <fragment android:name="com.android.incallui.VideoCallFragment"
+ android:id="@+id/videoCallFragment"
+ android:layout_alignParentTop="true"
+ android:layout_gravity="top|center_horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<!-- Progress spinner, useful for indicating pending operations such as upgrade to video. -->
<FrameLayout
android:id="@+id/progressSpinner"
diff --git a/InCallUI/res/layout/video_call_views.xml b/InCallUI/res/layout/video_call_views.xml
index ab03aa358..8961ea4bf 100644
--- a/InCallUI/res/layout/video_call_views.xml
+++ b/InCallUI/res/layout/video_call_views.xml
@@ -22,7 +22,7 @@
<TextureView
android:id="@+id/incomingVideo"
- android:layout_gravity="center_vertical"
+ android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- The width and height are replaced at runtime based on the selected camera. -->
@@ -32,4 +32,4 @@
android:layout_margin="@dimen/video_preview_margin"
android:layout_width="70dp"
android:layout_height="120dp" />
-</FrameLayout> \ No newline at end of file
+</FrameLayout>
diff --git a/InCallUI/res/menu/incall_overflow_menu.xml b/InCallUI/res/menu/incall_overflow_menu.xml
index 06208ebd8..2de858711 100644
--- a/InCallUI/res/menu/incall_overflow_menu.xml
+++ b/InCallUI/res/menu/incall_overflow_menu.xml
@@ -30,4 +30,7 @@
<item android:id="@+id/overflow_swap_menu_item"
android:title="@string/overflowSwapMenuItemText" />
+
+ <item android:id="@+id/overflow_manage_conference_menu_item"
+ android:title="@string/overflowManageConferenceMenuItemText" />
</menu>
diff --git a/InCallUI/res/values/array.xml b/InCallUI/res/values/array.xml
index 5270de1ac..46592e126 100644
--- a/InCallUI/res/values/array.xml
+++ b/InCallUI/res/values/array.xml
@@ -74,6 +74,8 @@
<item>@null</item>
<item>@drawable/ic_lockscreen_decline</item>
<item>@drawable/ic_lockscreen_answer_video</item>
+ <item>@drawable/ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/ic_lockscreen_answer_rx_video</item>
</array>
<array name="incoming_call_widget_video_without_sms_target_descriptions">
<item>@string/description_target_answer_video_call</item>
@@ -98,6 +100,8 @@
<item>@drawable/ic_lockscreen_text</item>
<item>@drawable/ic_lockscreen_decline</item>
<item>@drawable/ic_lockscreen_answer</item>
+ <item>@drawable/ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/ic_lockscreen_answer_rx_video</item>
</array>
<array name="incoming_call_widget_video_with_sms_target_descriptions">
<item>@string/description_target_answer_video_call</item>
@@ -113,7 +117,7 @@
</array>
- <!-- For upgrade to video in an active video call.
+ <!-- For upgrade to video from VOLTE to VT (Tx/Rx/Bidirectional) in an active video call.
- Accept upgrade to video request (drag right)
- Decline upgrade to video request (drag left)
- Answer as audio call (drag down) -->
@@ -121,7 +125,9 @@
<item>@drawable/ic_lockscreen_answer_video</item>
<item>@null</item>
<item>@drawable/ic_lockscreen_decline</item>
- <item>@null</item>"
+ <item>@drawable/ic_lockscreen_answer</item>
+ <item>@drawable/ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/ic_lockscreen_answer_rx_video</item>
</array>
<array name="incoming_call_widget_video_upgrade_request_target_descriptions">
<item>@string/description_target_accept_upgrade_to_video_request</item>
@@ -135,4 +141,37 @@
<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="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="incoming_call_widget_video_transmit_accept_reject_request_targets">
+ <item>@drawable/ic_lockscreen_answer_tx_video</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ </array>
+ <array name="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="incoming_call_widget_video_receive_accept_reject_request_targets">
+ <item>@drawable/ic_lockscreen_answer_rx_video</item>
+ <item>@drawable/ic_lockscreen_decline</item>
+ </array>
+ <array name="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>
+
</resources>
diff --git a/InCallUI/res/values/strings.xml b/InCallUI/res/values/strings.xml
index 944eff6fc..99b8111bb 100644
--- a/InCallUI/res/values/strings.xml
+++ b/InCallUI/res/values/strings.xml
@@ -114,6 +114,8 @@
<string name="card_title_video_call_requesting">Requesting video</string>
<!-- In-call screen: status label when there is a problem connecting a video call. -->
<string name="card_title_video_call_error">Can\'t connect video call</string>
+ <!-- In-call screen: status label when in a paused video call. -->
+ <string name="card_title_video_call_paused">Video call (Paused)</string>
<!-- In-call screen: string shown to the user when their outgoing number is different than the
number reported by TelephonyManager#getLine1Number() -->
@@ -263,6 +265,8 @@
<string name="overflowMergeMenuItemText">Merge calls</string>
<!-- Text for the onscreen "Swap calls" menu item. -->
<string name="overflowSwapMenuItemText">Swap calls</string>
+ <!-- Text for the overflow "Manage Conference Video Call" menu item. -->
+ <string name="overflowManageConferenceMenuItemText">Manage Conference</string>
<!-- Text for the onscreen "Hold" button -->
<string name="onscreenHoldText">Hold</string>
@@ -299,6 +303,28 @@
<!-- Text for the onscreen overflow button, to see additional actions which can be done. -->
<string name="onscreenOverflowText">More options</string>
+ <!-- STOPSHIP - For test only - In-call screen: Modify Call Options for IMS call -->
+ <string name="modify_call_option_title" translatable="false">Which type of call?</string>
+ <string name="modify_call_option_vt" translatable="false">Video bidirectional</string>
+ <string name="modify_call_option_vt_tx" translatable="false">Video transmit</string>
+ <string name="modify_call_option_vt_rx" translatable="false">Video receive</string>
+ <string name="modify_call_option_voice" translatable="false">Voice Only</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>
+
+ <!-- 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>
+
<!-- For incoming calls, this is a string we can get from a CDMA network instead of
the actual phone number, to indicate there's no number present. DO NOT TRANSLATE. -->
<string-array name="absent_num" translatable="false">
@@ -359,6 +385,18 @@
<!-- Description of the target to decline a request to upgrade from an audio call to a video call.
[CHAR LIMIT=NONE] -->
<string name="description_target_decline_upgrade_to_video_request">Decline video request</string>
+ <!-- Description of the target to accept a request to upgrade from any call to a video transmit call.
+ [CHAR LIMIT=NONE] -->
+ <string name="description_target_accept_upgrade_to_video_transmit_request">Accept video transmit request</string>
+ <!-- Description of the target to decline a request to upgrade from any call to a video transmit call.
+ [CHAR LIMIT=NONE] -->
+ <string name="description_target_decline_upgrade_to_video_transmit_request">Decline video transmit request</string>
+ <!-- Description of the target to accept a request to upgrade from any call to a video receive call.
+ [CHAR LIMIT=NONE] -->
+ <string name="description_target_accept_upgrade_to_video_receive_request">Accept video receive request</string>
+ <!-- Description of the target to decline a request to upgrade from any call to a video receive call.
+ [CHAR LIMIT=NONE] -->
+ <string name="description_target_decline_upgrade_to_video_receive_request">Decline video receive request</string>
<!-- Description of the up direction in which one can to slide the handle in the phone answer screen. [CHAR LIMIT=NONE] -->
<string name="description_direction_up">Slide up for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
@@ -417,4 +455,26 @@
<!-- This can be used in any application wanting to disable the text "Emergency number" -->
<string name="emergency_call_dialog_number_for_display">Emergency number</string>
+
+ <!-- STOPSHIP These strings are for debugging only -->
+ <!-- Call substate label -->
+ <string name="call_substate_label" translatable="false">Call substate - \u000a</string>
+ <!-- Call substate label for call resumed -->
+ <string name="call_substate_call_resumed" translatable="false">Resumed \u000a</string>
+ <!-- Call substate label for call connected suspended (audio) -->
+ <string name="call_substate_connected_suspended_audio" translatable="false">Connected Suspended (Audio) \u000a</string>
+ <!-- Call substate label for call connected suspended (video) -->
+ <string name="call_substate_connected_suspended_video" translatable="false">Connected Suspended (Video) \u000a</string>
+ <!-- Call substate label for avp retry -->
+ <string name="call_substate_avp_retry" translatable="false">Avp Retry \u000a</string>
+ <!-- Video quality changed message -->
+ <string name="video_quality_changed" translatable="false">Video quality changed to \u0020</string>
+ <!-- Video quality High -->
+ <string name="video_quality_high" translatable="false">High</string>
+ <!-- Video quality Medium -->
+ <string name="video_quality_medium" translatable="false">Medium</string>
+ <!-- Video quality Low -->
+ <string name="video_quality_low" translatable="false">Low</string>
+ <!-- Video quality Unknown -->
+ <string name="video_quality_unknown" translatable="false">Unknown</string>
</resources>
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java
index 29747dabd..a066396c9 100644
--- a/InCallUI/src/com/android/incallui/AnswerFragment.java
+++ b/InCallUI/src/com/android/incallui/AnswerFragment.java
@@ -50,6 +50,9 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
public static final int TARGET_SET_FOR_VIDEO_WITHOUT_SMS = 2;
public static final int TARGET_SET_FOR_VIDEO_WITH_SMS = 3;
public static final int TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST = 4;
+ public static final int TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST = 5;
+ public static final int TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST = 6;
+ public static final int TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST = 7;
/**
* The popup showing the list of canned responses.
@@ -163,6 +166,33 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
.incoming_call_widget_video_upgrade_request_target_direction_descriptions;
handleDrawableResourceId = R.drawable.ic_incall_video_handle;
break;
+ case TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST:
+ targetResourceId =
+ R.array.incoming_call_widget_bidirectional_video_accept_reject_request_targets;
+ targetDescriptionsResourceId =
+ R.array.incoming_call_widget_video_upgrade_request_target_descriptions;
+ directionDescriptionsResourceId = R.array
+ .incoming_call_widget_video_upgrade_request_target_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST:
+ targetResourceId =
+ R.array.incoming_call_widget_video_transmit_accept_reject_request_targets;
+ targetDescriptionsResourceId =
+ R.array.incoming_call_widget_video_transmit_request_target_descriptions;
+ directionDescriptionsResourceId = R.array
+ .incoming_call_widget_video_upgrade_request_target_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
+ case TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST:
+ targetResourceId =
+ R.array.incoming_call_widget_video_receive_accept_reject_request_targets;
+ targetDescriptionsResourceId =
+ R.array.incoming_call_widget_video_receive_request_target_descriptions;
+ directionDescriptionsResourceId = R.array
+ .incoming_call_widget_video_upgrade_request_target_direction_descriptions;
+ handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+ break;
case TARGET_SET_FOR_AUDIO_WITHOUT_SMS:
default:
targetResourceId = R.array.incoming_call_widget_audio_without_sms_targets;
@@ -338,12 +368,13 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
@Override
public void onAnswer(int videoState, Context context) {
+ Log.d(this, "onAnswer videoState=" + videoState + " context=" + context);
getPresenter().onAnswer(videoState, context);
}
@Override
- public void onDecline() {
- getPresenter().onDecline();
+ public void onDecline(Context context) {
+ getPresenter().onDecline(context);
}
@Override
diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java
index e579d643a..208532572 100644
--- a/InCallUI/src/com/android/incallui/AnswerPresenter.java
+++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java
@@ -18,6 +18,7 @@ package com.android.incallui;
import android.content.Context;
import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
import java.util.List;
@@ -35,6 +36,7 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
@Override
public void onUiReady(AnswerUi ui) {
+ Log.d(this, "onUiReady ui=" + ui);
super.onUiReady(ui);
final CallList calls = CallList.getInstance();
@@ -43,9 +45,12 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
if (call != null) {
processIncomingCall(call);
}
- call = calls.getVideoUpgradeRequestCall();
- if (call != null) {
- processVideoUpgradeRequestCall(call);
+
+ Call videoCall = calls.getVideoUpgradeRequestCall();
+ Log.d(this, "getVideoUpgradeRequestCall call =" + call);
+
+ if (videoCall != null && call == null) {
+ processVideoUpgradeRequestCall(videoCall);
}
// Listen for incoming calls.
@@ -67,6 +72,7 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
@Override
public void onCallListChange(CallList callList) {
+ Log.d(this, "onCallListChange callList=" + callList);
// no-op
}
@@ -81,6 +87,13 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
// getting updates here.
Log.d(this, "onIncomingCall: " + this);
if (getUi() != null) {
+ Call modifyCall = CallList.getInstance().getVideoUpgradeRequestCall();
+ if (modifyCall != null) {
+ getUi().showAnswerUi(false);
+ Log.d(this, "declining upgrade request id: ");
+ CallList.getInstance().removeCallUpdateListener(mCallId, this);
+ InCallPresenter.getInstance().declineUpgradeRequest(getUi().getContext());
+ }
if (!call.getId().equals(mCallId)) {
// A new call is coming in.
processIncomingCall(call);
@@ -88,6 +101,31 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
}
}
+ private boolean isVideoUpgradePending(Call call) {
+ return call.getSessionModificationState()
+ == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
+ }
+
+ @Override
+ public void onUpgradeToVideo(Call call) {
+ Log.d(this, "onUpgradeToVideo: " + this + " call=" + call);
+ if (getUi() == null) {
+ Log.d(this, "onUpgradeToVideo ui is null");
+ return;
+ }
+ boolean isUpgradePending = isVideoUpgradePending(call);
+ InCallPresenter inCallPresenter = InCallPresenter.getInstance();
+ if (isUpgradePending
+ && inCallPresenter.getInCallState() == InCallPresenter.InCallState.INCOMING) {
+ Log.d(this, "declining upgrade request");
+ //If there is incoming call reject upgrade request
+ inCallPresenter.declineUpgradeRequest(getUi().getContext());
+ } else if (isUpgradePending) {
+ Log.d(this, "process upgrade request as no MT call");
+ processVideoUpgradeRequestCall(call);
+ }
+ }
+
private void processIncomingCall(Call call) {
mCallId = call.getId();
mCall = call;
@@ -102,28 +140,71 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
}
private void processVideoUpgradeRequestCall(Call call) {
+ Log.d(this, " processVideoUpgradeRequestCall call=" + call);
mCallId = call.getId();
mCall = call;
// Listen for call updates for the current call.
CallList.getInstance().addCallUpdateListener(mCallId, this);
- getUi().showAnswerUi(true);
- getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST);
+ final int currentVideoState = call.getVideoState();
+ final int modifyToVideoState = call.getModifyToVideoState();
+
+ if (currentVideoState == modifyToVideoState) {
+ Log.w(this, "processVideoUpgradeRequestCall: Video states are same. Return.");
+ return;
+ }
+
+ AnswerUi ui = getUi();
+
+ if (ui == null) {
+ Log.e(this, "Ui is null. Can't process upgrade request");
+ return;
+ }
+ ui.showAnswerUi(true);
+ ui.showTargets(getUiTarget(currentVideoState, modifyToVideoState));
+
+ }
+
+ private int getUiTarget(int currentVideoState, int modifyToVideoState) {
+ if (showVideoUpgradeOptions(currentVideoState, modifyToVideoState)) {
+ return AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST;
+ } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.BIDIRECTIONAL)) {
+ return AnswerFragment.TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST;
+ } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.TX_ENABLED)) {
+ return AnswerFragment.TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST;
+ } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.RX_ENABLED)) {
+ return AnswerFragment.TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST;
+ }
+ return AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST;
+ }
+
+ private boolean showVideoUpgradeOptions(int currentVideoState, int modifyToVideoState) {
+ return currentVideoState == VideoProfile.VideoState.AUDIO_ONLY &&
+ isEnabled(modifyToVideoState, VideoProfile.VideoState.BIDIRECTIONAL);
+ }
+
+ private boolean isEnabled(int videoState, int mask) {
+ return (videoState & mask) == mask;
}
@Override
public void onCallChanged(Call call) {
Log.d(this, "onCallStateChange() " + call + " " + this);
if (call.getState() != Call.State.INCOMING) {
- // Stop listening for updates.
- CallList.getInstance().removeCallUpdateListener(mCallId, this);
+ boolean isUpgradePending = isVideoUpgradePending(call);
+ if (!isUpgradePending) {
+ // Stop listening for updates.
+ CallList.getInstance().removeCallUpdateListener(mCallId, this);
+ }
- getUi().showAnswerUi(false);
+ final Call incall = CallList.getInstance().getIncomingCall();
+ if (incall != null || isUpgradePending) {
+ getUi().showAnswerUi(true);
+ } else {
+ getUi().showAnswerUi(false);
+ }
- // mCallId will hold the state of the call. We don't clear the mCall variable here as
- // it may be useful for sending text messages after phone disconnects.
- mCallId = null;
mHasTextMessages = false;
} else if (!mHasTextMessages) {
final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId());
@@ -134,14 +215,14 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
}
public void onAnswer(int videoState, Context context) {
+ Log.d(this, "onAnswer mCallId=" + mCallId + " videoState=" + videoState);
if (mCallId == null) {
return;
}
- Log.d(this, "onAnswer " + mCallId);
if (mCall.getSessionModificationState()
== Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
- InCallPresenter.getInstance().acceptUpgradeRequest(context);
+ InCallPresenter.getInstance().acceptUpgradeRequest(videoState, context);
} else {
TelecomAdapter.getInstance().answerCall(mCall.getId(), videoState);
}
@@ -151,9 +232,14 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
* TODO: We are using reject and decline interchangeably. We should settle on
* reject since it seems to be more prevalent.
*/
- public void onDecline() {
+ public void onDecline(Context context) {
Log.d(this, "onDecline " + mCallId);
- TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null);
+ if (mCall.getSessionModificationState()
+ == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
+ InCallPresenter.getInstance().declineUpgradeRequest(context);
+ } else {
+ TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null);
+ }
}
public void onText() {
diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java
index fd06e53b3..3d6f2032e 100644
--- a/InCallUI/src/com/android/incallui/Call.java
+++ b/InCallUI/src/com/android/incallui/Call.java
@@ -17,8 +17,10 @@
package com.android.incallui;
import com.android.contacts.common.CallUtil;
+import com.android.incallui.CallList.Listener;
import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
import android.net.Uri;
import android.telecom.CallProperties;
import android.telecom.DisconnectCause;
@@ -118,8 +120,51 @@ public final class Call {
public static final int WAITING_FOR_RESPONSE = 1;
public static final int REQUEST_FAILED = 2;
public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3;
+ public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4;
+ }
+
+ public static class VideoSettings {
+ public static final int CAMERA_DIRECTION_UNKNOWN = -1;
+ public static final int CAMERA_DIRECTION_FRONT_FACING =
+ CameraCharacteristics.LENS_FACING_FRONT;
+ public static final int CAMERA_DIRECTION_BACK_FACING =
+ CameraCharacteristics.LENS_FACING_BACK;
+
+ private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
+
+ /**
+ * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
+ * the video state of the call should be used to infer the camera direction.
+ *
+ * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
+ * @see {@link CameraCharacteristics#LENS_FACING_BACK}
+ */
+ public void setCameraDir(int cameraDirection) {
+ if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING
+ || cameraDirection == CAMERA_DIRECTION_BACK_FACING) {
+ mCameraDirection = cameraDirection;
+ } else {
+ mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
+ }
+ }
+
+ /**
+ * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
+ * the video state of the call should be used to infer the camera direction.
+ *
+ * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
+ * @see {@link CameraCharacteristics#LENS_FACING_BACK}
+ */
+ public int getCameraDir() {
+ return mCameraDirection;
+ }
+
+ public String toString() {
+ return "(CameraDir:" + getCameraDir() + ")";
+ }
}
+
private static final String ID_PREFIX = Call.class.getSimpleName() + "_";
private static int sIdCounter = 0;
@@ -127,12 +172,16 @@ public final class Call {
new android.telecom.Call.Listener() {
@Override
public void onStateChanged(android.telecom.Call call, int newState) {
+ Log.d(this, "TelecommCallListener onStateChanged call=" + call + " newState="
+ + newState);
update();
}
@Override
public void onParentChanged(android.telecom.Call call,
android.telecom.Call newParent) {
+ Log.d(this, "TelecommCallListener onParentChanged call=" + call + " newParent="
+ + newParent);
update();
}
@@ -145,29 +194,38 @@ public final class Call {
@Override
public void onDetailsChanged(android.telecom.Call call,
android.telecom.Call.Details details) {
+ Log.d(this, "TelecommCallListener onStateChanged call=" + call + " details="
+ + details);
update();
}
@Override
public void onCannedTextResponsesLoaded(android.telecom.Call call,
List<String> cannedTextResponses) {
+ Log.d(this, "TelecommCallListener onStateChanged call=" + call
+ + " cannedTextResponses=" + cannedTextResponses);
update();
}
@Override
public void onPostDialWait(android.telecom.Call call,
String remainingPostDialSequence) {
+ Log.d(this, "TelecommCallListener onStateChanged call=" + call
+ + " remainingPostDialSequence=" + remainingPostDialSequence);
update();
}
@Override
public void onVideoCallChanged(android.telecom.Call call,
VideoCall videoCall) {
+ Log.d(this, "TelecommCallListener onStateChanged call=" + call + " videoCall="
+ + videoCall);
update();
}
@Override
public void onCallDestroyed(android.telecom.Call call) {
+ Log.d(this, "TelecommCallListener onStateChanged call=" + call);
call.removeListener(mTelecommCallListener);
}
@@ -184,6 +242,11 @@ public final class Call {
private DisconnectCause mDisconnectCause;
private int mSessionModificationState;
private final List<String> mChildCallIds = new ArrayList<>();
+ private final VideoSettings mVideoSettings = new VideoSettings();
+ /**
+ * mModifyToVideoState is used to store requested upgrade / downgrade video state
+ */
+ private int mModifyToVideoState = VideoProfile.VideoState.AUDIO_ONLY;
private InCallVideoCallListener mVideoCallListener;
@@ -198,6 +261,14 @@ public final class Call {
return mTelecommCall;
}
+ /**
+ * @return video settings of the call, null if the call is not a video call.
+ * @see VideoProfile
+ */
+ public VideoSettings getVideoSettings() {
+ return mVideoSettings;
+ }
+
private void update() {
int oldState = getState();
updateFromTelecommCall();
@@ -209,7 +280,7 @@ public final class Call {
}
private void updateFromTelecommCall() {
- Log.d(this, "updateFromTelecommCall: " + mTelecommCall);
+ Log.d(this, "updateFromTelecommCall: " + mTelecommCall.toString());
setState(translateState(mTelecommCall.getState()));
setDisconnectCause(mTelecommCall.getDetails().getDisconnectCause());
@@ -370,24 +441,69 @@ public final class Call {
return mTelecommCall.getDetails().getVideoState();
}
+ public int getCallSubstate() {
+ return mTelecommCall.getDetails().getCallSubstate();
+ }
+
public boolean isVideoCall(Context context) {
- // We want to show Video call buttons even if only one direction is enabled
- // (That is what is happening when we receive a video call for example)
- return CallUtil.isVideoEnabled(context) && (
- VideoProfile.VideoState.isBidirectional(getVideoState()) ||
- VideoProfile.VideoState.isReceptionEnabled(getVideoState()) ||
- VideoProfile.VideoState.isTransmissionEnabled(getVideoState()));
+ return CallUtil.isVideoEnabled(context) &&
+ VideoProfile.VideoState.isVideo(getVideoState());
+ }
+
+ /**
+ * This method is called when we request for a video upgrade or downgrade. This handles the
+ * session modification state RECEIVED_UPGRADE_TO_VIDEO_REQUEST and sets the video state we
+ * want to upgrade/downgrade to.
+ */
+ public void setSessionModificationTo(int videoState) {
+ Log.d(this, "setSessionModificationTo - video state= " + videoState);
+ if (videoState == getVideoState()) {
+ mSessionModificationState = Call.SessionModificationState.NO_REQUEST;
+ Log.w(this,"setSessionModificationTo - Clearing session modification state");
+ } else {
+ mSessionModificationState =
+ Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
+ setModifyToVideoState(videoState);
+ CallList.getInstance().onUpgradeToVideo(this);
+ }
+
+ Log.d(this, "setSessionModificationTo - mSessionModificationState="
+ + mSessionModificationState + " video state= " + videoState);
+ update();
}
+ /**
+ * This method is called to handle any other session modification states other than
+ * RECEIVED_UPGRADE_TO_VIDEO_REQUEST. We set the modification state and reset the video state
+ * when an upgrade request has been completed or failed.
+ */
public void setSessionModificationState(int state) {
+ if (state == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
+ Log.e(this,
+ "setSessionModificationState not to be called for RECEIVED_UPGRADE_TO_VIDEO_REQUEST");
+ return;
+ }
+
boolean hasChanged = mSessionModificationState != state;
mSessionModificationState = state;
-
+ Log.d(this, "setSessionModificationState " + state + " mSessionModificationState="
+ + mSessionModificationState);
+ if (state != Call.SessionModificationState.WAITING_FOR_RESPONSE) {
+ setModifyToVideoState(VideoProfile.VideoState.AUDIO_ONLY);
+ }
if (hasChanged) {
update();
}
}
+ private void setModifyToVideoState(int newVideoState) {
+ mModifyToVideoState = newVideoState;
+ }
+
+ public int getModifyToVideoState() {
+ return mModifyToVideoState;
+ }
+
public static boolean areSame(Call call1, Call call2) {
if (call1 == null && call2 == null) {
return true;
@@ -406,7 +522,7 @@ public final class Call {
@Override
public String toString() {
return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " +
- "videoState:%d]",
+ "videoState:%d, callSubState:%d, mSessionModificationState:%d, VideoSettings:%s]",
mId,
State.toString(getState()),
android.telecom.Call.Details
@@ -414,6 +530,13 @@ public final class Call {
mChildCallIds,
getParentId(),
this.mTelecommCall.getConferenceableCalls(),
- mTelecommCall.getDetails().getVideoState());
+ mTelecommCall.getDetails().getVideoState(),
+ mTelecommCall.getDetails().getCallSubstate(),
+ mSessionModificationState,
+ getVideoSettings());
+ }
+
+ public String toSimpleString() {
+ return super.toString();
}
}
diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java
index b2d109a6a..8682d65e5 100644
--- a/InCallUI/src/com/android/incallui/CallButtonFragment.java
+++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java
@@ -16,7 +16,9 @@
package com.android.incallui;
+import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -26,6 +28,8 @@ import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Bundle;
import android.telecom.AudioState;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
import android.view.ContextThemeWrapper;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
@@ -36,11 +40,13 @@ import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageButton;
import android.widget.PopupMenu;
+import android.widget.Toast;
import android.widget.PopupMenu.OnDismissListener;
import android.widget.PopupMenu.OnMenuItemClickListener;
import com.android.contacts.common.util.MaterialColorMapUtils;
import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+import java.util.ArrayList;
/**
* Fragment for call control buttons
@@ -50,6 +56,7 @@ public class CallButtonFragment
implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener,
View.OnClickListener {
private CompoundButton mAudioButton;
+ private static final int INVALID_INDEX = -1;
private ImageButton mChangeToVoiceButton;
private CompoundButton mMuteButton;
private CompoundButton mShowDialpadButton;
@@ -61,6 +68,7 @@ public class CallButtonFragment
private ImageButton mMergeButton;
private CompoundButton mPauseVideoButton;
private ImageButton mOverflowButton;
+ private ImageButton mManageVideoCallConferenceButton;
private PopupMenu mAudioModePopup;
private boolean mAudioModePopupVisible;
@@ -120,7 +128,9 @@ public class CallButtonFragment
mPauseVideoButton.setOnClickListener(this);
mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton);
mOverflowButton.setOnClickListener(this);
-
+ mManageVideoCallConferenceButton = (ImageButton) parent.findViewById(
+ R.id.manageVideoCallConferenceButton);
+ mManageVideoCallConferenceButton.setOnClickListener(this);
return parent;
}
@@ -156,7 +166,8 @@ public class CallButtonFragment
getPresenter().addCallClicked();
break;
case R.id.changeToVoiceButton:
- getPresenter().changeToVoiceClicked();
+ // STOPSHIP One way video options
+ getPresenter().displayModifyCallOptions();
break;
case R.id.muteButton: {
getPresenter().muteClicked(!mMuteButton.isSelected());
@@ -177,7 +188,8 @@ public class CallButtonFragment
getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected());
break;
case R.id.changeToVideoButton:
- getPresenter().changeToVideoClicked();
+ // STOPSHIP One way video options
+ getPresenter().displayModifyCallOptions();
break;
case R.id.switchCameraButton:
getPresenter().switchCameraClicked(
@@ -190,6 +202,9 @@ public class CallButtonFragment
case R.id.overflowButton:
mOverflowPopup.show();
break;
+ case R.id.manageVideoCallConferenceButton:
+ onManageVideoCallConferenceClicked();
+ break;
default:
isClickHandled = false;
Log.wtf(this, "onClick: unexpected");
@@ -329,6 +344,7 @@ public class CallButtonFragment
mMergeButton.setEnabled(isEnabled);
mPauseVideoButton.setEnabled(isEnabled);
mOverflowButton.setEnabled(isEnabled);
+ mManageVideoCallConferenceButton.setEnabled(isEnabled);
}
@Override
@@ -406,6 +422,10 @@ public class CallButtonFragment
mAddCallButton.setVisibility(show ? View.VISIBLE : View.GONE);
}
+ public void showManageConferenceVideoCallButton(boolean show) {
+ mManageVideoCallConferenceButton.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
@Override
public void showMergeButton(boolean show) {
mMergeButton.setVisibility(show ? View.VISIBLE : View.GONE);
@@ -431,9 +451,83 @@ public class CallButtonFragment
mOverflowButton.setVisibility(show ? View.VISIBLE : View.GONE);
}
+ /**The function is called when Modify Call button gets pressed. The function creates and
+ * displays modify call options.
+ */
+ public void displayModifyCallOptions() {
+ CallButtonPresenter.CallButtonUi ui = getUi();
+ if (ui == null) {
+ Log.e(this, "Cannot display ModifyCallOptions as ui is null");
+ return;
+ }
+
+ Context context = getContext();
+ if (isTtyModeEnabled()) {
+ Toast.makeText(context, context.getResources().getString(
+ R.string.video_call_not_allowed_if_tty_enabled),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ final ArrayList<CharSequence> items = new ArrayList<CharSequence>();
+ final ArrayList<Integer> itemToCallType = new ArrayList<Integer>();
+ final Resources res = ui.getContext().getResources();
+ // Prepare the string array and mapping.
+ items.add(res.getText(R.string.modify_call_option_voice));
+ itemToCallType.add(VideoProfile.VideoState.AUDIO_ONLY);
+
+ items.add(res.getText(R.string.modify_call_option_vt_rx));
+ itemToCallType.add(VideoProfile.VideoState.RX_ENABLED);
+
+ items.add(res.getText(R.string.modify_call_option_vt_tx));
+ itemToCallType.add(VideoProfile.VideoState.TX_ENABLED);
+
+ items.add(res.getText(R.string.modify_call_option_vt));
+ itemToCallType.add(VideoProfile.VideoState.BIDIRECTIONAL);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getUi().getContext());
+ 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(getUi().getContext(), items.get(item), Toast.LENGTH_SHORT).show();
+ final int selCallType = itemToCallType.get(item);
+ Log.v(this, "Videocall: ModifyCall: upgrade/downgrade to "
+ + fromCallType(selCallType));
+ VideoProfile videoProfile = new VideoProfile(selCallType);
+ getPresenter().changeToVideoClicked(videoProfile);
+ dialog.dismiss();
+ }
+ };
+ int currVideoState = getPresenter().getCurrentVideoState();
+ int currUnpausedVideoState = CallUtils.getUnPausedVideoState(currVideoState);
+ int index = itemToCallType.indexOf(currUnpausedVideoState);
+ if (index == INVALID_INDEX) {
+ return;
+ }
+ builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), index, listener);
+ alert = builder.create();
+ alert.show();
+ }
+
+ public static String fromCallType(int callType) {
+ switch (callType) {
+ case VideoProfile.VideoState.BIDIRECTIONAL:
+ return "VT";
+ case VideoProfile.VideoState.TX_ENABLED:
+ return "VT_TX";
+ case VideoProfile.VideoState.RX_ENABLED:
+ return "VT_RX";
+ }
+ return "";
+ }
+
@Override
public void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption,
- boolean showHoldMenuOption, boolean showSwapMenuOption) {
+ boolean showHoldMenuOption, boolean showSwapMenuOption,
+ boolean showManageConferenceVideoCallOption) {
if (mOverflowPopup == null) {
final ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(),
R.style.InCallPopupMenuStyle);
@@ -459,6 +553,9 @@ public class CallButtonFragment
case R.id.overflow_swap_menu_item:
getPresenter().addCallClicked();
break;
+ case R.id.overflow_manage_conference_menu_item:
+ onManageVideoCallConferenceClicked();
+ break;
default:
Log.wtf(this, "onMenuItemClick: unexpected overflow menu click");
break;
@@ -482,6 +579,8 @@ public class CallButtonFragment
menu.findItem(R.id.overflow_resume_menu_item).setVisible(
showHoldMenuOption && mHoldButton.isSelected());
menu.findItem(R.id.overflow_swap_menu_item).setVisible(showSwapMenuOption);
+ menu.findItem(R.id.overflow_manage_conference_menu_item).setVisible(
+ showManageConferenceVideoCallOption);
mOverflowButton.setEnabled(menu.hasVisibleItems());
}
@@ -561,6 +660,11 @@ public class CallButtonFragment
}
}
+ private void onManageVideoCallConferenceClicked() {
+ Log.d(this, "onManageVideoCallConferenceClicked");
+ InCallPresenter.getInstance().showConferenceCallManager(true);
+ }
+
/**
* Refreshes the "Audio mode" popup if it's visible. This is useful
* (for example) when a wired headset is plugged or unplugged,
@@ -789,4 +893,11 @@ public class CallButtonFragment
public Context getContext() {
return getActivity();
}
+
+ private boolean isTtyModeEnabled() {
+ return (android.provider.Settings.Secure.getInt(
+ getContext().getContentResolver(),
+ android.provider.Settings.Secure.PREFERRED_TTY_MODE,
+ TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
+ }
}
diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
index 9fedc95b1..4e840be9f 100644
--- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
@@ -16,6 +16,7 @@
package com.android.incallui;
+import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.telecom.AudioState;
@@ -23,6 +24,7 @@ import android.telecom.InCallService.VideoCall;
import android.telecom.VideoProfile;
import com.android.incallui.AudioModeProvider.AudioModeListener;
+import com.android.incallui.InCallCameraManager.Listener;
import com.android.incallui.InCallPresenter.CanAddCallListener;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
@@ -36,7 +38,7 @@ import java.util.Objects;
*/
public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
implements InCallStateListener, AudioModeListener, IncomingCallListener,
- InCallDetailsListener, CanAddCallListener {
+ InCallDetailsListener, CanAddCallListener, 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";
@@ -44,6 +46,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
private Call mCall;
private boolean mAutomaticallyMuted = false;
private boolean mPreviousMuteState = false;
+ private static final int BUTTON_THRESOLD_TO_DISPLAY_OVERFLOW_MENU = 5;
public CallButtonPresenter() {
}
@@ -59,6 +62,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
InCallPresenter.getInstance().addIncomingCallListener(this);
InCallPresenter.getInstance().addDetailsListener(this);
InCallPresenter.getInstance().addCanAddCallListener(this);
+ InCallPresenter.getInstance().getInCallCameraManager().addCameraSelectionListener(this);
}
@Override
@@ -69,6 +73,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
AudioModeProvider.getInstance().removeListener(this);
InCallPresenter.getInstance().removeIncomingCallListener(this);
InCallPresenter.getInstance().removeDetailsListener(this);
+ InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this);
}
@Override
@@ -248,16 +253,21 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
getUi().displayDialpad(checked /* show */, true /* animate */);
}
- public void changeToVideoClicked() {
+ public void displayModifyCallOptions() {
+ getUi().displayModifyCallOptions();
+ }
+
+ public int getCurrentVideoState() {
+ return mCall.getVideoState();
+ }
+
+ public void changeToVideoClicked(VideoProfile videoProfile) {
VideoCall videoCall = mCall.getVideoCall();
if (videoCall == null) {
return;
}
- VideoProfile videoProfile =
- new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL);
videoCall.sendSessionModifyRequest(videoProfile);
-
mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE);
}
@@ -277,12 +287,16 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
String cameraId = cameraManager.getActiveCameraId();
if (cameraId != null) {
+ final int cameraDir = cameraManager.isUsingFrontFacingCamera()
+ ? Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING
+ : Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING;
+ mCall.getVideoSettings().setCameraDir(cameraDir);
videoCall.setCamera(cameraId);
videoCall.requestCameraCapabilities();
}
- getUi().setSwitchCameraButton(!useFrontFacingCamera);
}
+
/**
* Stop or start client's video transmission.
* @param pause True if pausing the local user's video, or false if starting the local user's
@@ -331,6 +345,10 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
ui.enableMute(call.can(android.telecom.Call.Details.CAPABILITY_MUTE));
}
+ private static int toInteger(boolean b) {
+ return b ? 1 : 0;
+ }
+
/**
* Updates the buttons applicable for the UI.
*
@@ -338,29 +356,17 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
* @param context The context.
*/
private void updateCallButtons(Call call, Context context) {
- if (call.isVideoCall(context)) {
+ if (CallUtils.isVideoCall(call)) {
updateVideoCallButtons(call);
- } else {
- updateVoiceCallButtons(call);
}
+ updateVoiceCallButtons(call);
}
private void updateVideoCallButtons(Call call) {
Log.v(this, "Showing buttons for video call.");
final CallButtonUi ui = getUi();
- // Hide all voice-call-related buttons.
- ui.showAudioButton(false);
- ui.showDialpadButton(false);
- ui.showHoldButton(false);
- ui.showSwapButton(false);
- ui.showChangeToVideoButton(false);
- ui.showAddCallButton(false);
- ui.showMergeButton(false);
- ui.showOverflowButton(false);
-
// Show all video-call-related buttons.
- ui.showChangeToVoiceButton(true);
ui.showSwitchCameraButton(true);
ui.showPauseVideoButton(true);
@@ -392,6 +398,10 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE));
Log.v(this, "Show add call ", TelecomAdapter.getInstance().canAddCall());
Log.v(this, "Show mute ", call.can(android.telecom.Call.Details.CAPABILITY_MUTE));
+ Log.v(this, "Show video call local:",
+ call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL)
+ + " remote: "
+ + call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE));
final boolean canAdd = TelecomAdapter.getInstance().canAddCall();
final boolean enableHoldOption = call.can(android.telecom.Call.Details.CAPABILITY_HOLD);
@@ -401,11 +411,13 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
boolean canVideoCall = call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL)
&& call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE);
ui.showChangeToVideoButton(canVideoCall);
- ui.enableChangeToVideoButton(!isCallOnHold);
final boolean showMergeOption = call.can(
android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
final boolean showAddCallOption = canAdd;
+ final boolean showManageVideoCallConferenceOption = call.can(
+ android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
+ && CallUtils.isVideoCall(call);
// Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available:
// (1) If the device normally can hold, show HOLD in a disabled state.
@@ -415,38 +427,55 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
final boolean showHoldOption = !showSwapOption && (enableHoldOption || supportHold);
ui.setHold(isCallOnHold);
- // If we show video upgrade and add/merge and hold/swap, the overflow menu is needed.
- final boolean isVideoOverflowScenario = canVideoCall
- && (showAddCallOption || showMergeOption) && (showHoldOption || showSwapOption);
- // If we show hold/swap, add, and merge simultaneously, the overflow menu is needed.
- final boolean isOverflowScenario =
- (showHoldOption || showSwapOption) && showMergeOption && showAddCallOption;
+ //Initialize buttonCount = 2. Because speaker and dialpad these two always show in Call UI.
+ int buttonCount = 2;
+ buttonCount += toInteger(canVideoCall);
+ buttonCount += toInteger(showAddCallOption);
+ buttonCount += toInteger(showMergeOption);
+ buttonCount += toInteger(showHoldOption);
+ buttonCount += toInteger(showSwapOption);
+ buttonCount += toInteger(call.can(android.telecom.Call.Details.CAPABILITY_MUTE));
+ buttonCount += toInteger(showManageVideoCallConferenceOption);
+
+ Log.v(this, "show ManageVideoCallConference: " + showManageVideoCallConferenceOption);
+ Log.v(this, "No of InCall buttons: " + buttonCount + " canVideoCall: " + canVideoCall);
+
+ // Show overflow menu if number of buttons is greater than 5.
+ final boolean showOverflowMenu =
+ buttonCount > BUTTON_THRESOLD_TO_DISPLAY_OVERFLOW_MENU;
+ final boolean isVideoOverflowScenario = canVideoCall && showOverflowMenu;
+ final boolean isOverflowScenario = !canVideoCall && showOverflowMenu;
if (isVideoOverflowScenario) {
ui.showHoldButton(false);
ui.showSwapButton(false);
ui.showAddCallButton(false);
ui.showMergeButton(false);
+ ui.showManageConferenceVideoCallButton(false);
ui.configureOverflowMenu(
showMergeOption,
showAddCallOption /* showAddMenuOption */,
showHoldOption && enableHoldOption /* showHoldMenuOption */,
- showSwapOption);
+ showSwapOption,
+ showManageVideoCallConferenceOption);
ui.showOverflowButton(true);
} else {
if (isOverflowScenario) {
ui.showAddCallButton(false);
ui.showMergeButton(false);
+ ui.showManageConferenceVideoCallButton(false);
ui.configureOverflowMenu(
showMergeOption,
showAddCallOption /* showAddMenuOption */,
false /* showHoldMenuOption */,
- false /* showSwapMenuOption */);
+ false /* showSwapMenuOption */,
+ showManageVideoCallConferenceOption);
} else {
ui.showMergeButton(showMergeOption);
ui.showAddCallButton(showAddCallOption);
+ ui.showManageConferenceVideoCallButton(showManageVideoCallConferenceOption);
}
ui.showOverflowButton(isOverflowScenario);
@@ -500,16 +529,27 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
void showSwitchCameraButton(boolean show);
void setSwitchCameraButton(boolean isBackFacingCamera);
void showAddCallButton(boolean show);
+ void showManageConferenceVideoCallButton(boolean show);
void showMergeButton(boolean show);
void showPauseVideoButton(boolean show);
void setPauseVideoButton(boolean isPaused);
void showOverflowButton(boolean show);
void displayDialpad(boolean on, boolean animate);
+ void displayModifyCallOptions();
boolean isDialpadVisible();
void setAudio(int mode);
void setSupportedAudio(int mask);
void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption,
- boolean showHoldMenuOption, boolean showSwapMenuOption);
+ boolean showHoldMenuOption, boolean showSwapMenuOption,
+ boolean showManageConferenceVideoCallOption);
Context getContext();
}
+
+ @Override
+ public void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera) {
+ if (getUi() == null) {
+ return;
+ }
+ getUi().setSwitchCameraButton(!isUsingFrontFacingCamera);
+ }
}
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 6550eecdc..9f43502e0 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -557,7 +557,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
mCallStateIcon.setVisibility(View.GONE);
}
- if (VideoProfile.VideoState.isBidirectional(videoState)
+ if (VideoProfile.VideoState.isVideo(videoState)
|| (state == Call.State.ACTIVE && sessionModificationState
== Call.SessionModificationState.WAITING_FOR_RESPONSE)) {
mCallStateVideoCallIcon.setVisibility(View.VISIBLE);
@@ -683,6 +683,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
} else if (sessionModificationState
== Call.SessionModificationState.WAITING_FOR_RESPONSE) {
callStateLabel = context.getString(R.string.card_title_video_call_requesting);
+ } else if (VideoProfile.VideoState.isVideo(videoState) &&
+ VideoProfile.VideoState.isPaused(videoState)) {
+ callStateLabel = context.getString(R.string.card_title_video_call_paused);
} else if (VideoProfile.VideoState.isBidirectional(videoState)) {
callStateLabel = context.getString(R.string.card_title_video_call);
}
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index 3a4c46a60..7eae5c602 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -320,7 +320,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
return false;
}
- return mPrimary.can(Details.CAPABILITY_MANAGE_CONFERENCE);
+ return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
+ && !mPrimary.isVideoCall(mContext);
}
private void setCallbackNumber() {
diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java
index db43b1657..f2d04cf11 100644
--- a/InCallUI/src/com/android/incallui/CallList.java
+++ b/InCallUI/src/com/android/incallui/CallList.java
@@ -72,7 +72,9 @@ public class CallList implements InCallPhoneListener {
@Override
public void onCallAdded(Phone phone, android.telecom.Call telecommCall) {
Call call = new Call(telecommCall);
- if (call.getState() == Call.State.INCOMING) {
+ Log.d(this, "onCallAdded: callState=" + call.getState());
+ if (call.getState() == Call.State.INCOMING ||
+ call.getState() == Call.State.CALL_WAITING) {
onIncoming(call, call.getCannedSmsResponses());
} else {
onUpdate(call);
@@ -135,6 +137,12 @@ public class CallList implements InCallPhoneListener {
}
}
+ public void onUpgradeToVideo(Call call){
+ Log.d(this, "onUpgradeToVideo call=" + call);
+ for (Listener listener : mListeners) {
+ listener.onUpgradeToVideo(call);
+ }
+ }
/**
* Called when a single call has changed.
*/
@@ -539,7 +547,11 @@ public class CallList implements InCallPhoneListener {
* incoming calls.
*/
public void onIncomingCall(Call call);
-
+ /**
+ * Called when a new modify call request comes in
+ * This is the only method that gets called for modify requests.
+ */
+ public void onUpgradeToVideo(Call call);
/**
* Called anytime there are changes to the call list. The change can be switching call
* states, updating information, etc. This method will NOT be called for new incoming
diff --git a/InCallUI/src/com/android/incallui/CallUtils.java b/InCallUI/src/com/android/incallui/CallUtils.java
new file mode 100644
index 000000000..80b553aa7
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/CallUtils.java
@@ -0,0 +1,90 @@
+/* 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.
+ */
+
+package com.android.incallui;
+
+import android.telecom.VideoProfile;
+
+import com.google.common.base.Preconditions;
+
+public class CallUtils {
+
+ public static boolean isVideoCall(Call call) {
+ return call != null && VideoProfile.VideoState.isVideo(call.getVideoState());
+ }
+
+ public static boolean isIncomingVideoCall(Call call) {
+ if (!CallUtils.isVideoCall(call)) {
+ return false;
+ }
+ final int state = call.getState();
+ return (state == Call.State.INCOMING) || (state == Call.State.CALL_WAITING);
+ }
+
+ public static boolean isActiveVideoCall(Call call) {
+ return CallUtils.isVideoCall(call) && call.getState() == Call.State.ACTIVE;
+ }
+
+ public static boolean isOutgoingVideoCall(Call call) {
+ if (!CallUtils.isVideoCall(call)) {
+ return false;
+ }
+ final int state = call.getState();
+ return Call.State.isDialing(state) || state == Call.State.CONNECTING
+ || state == Call.State.PRE_DIAL_WAIT;
+ }
+
+ public static boolean isAudioCall(Call call) {
+ return call != null && VideoProfile.VideoState.isAudioOnly(call.getVideoState());
+ }
+
+ // TODO (ims-vt) Check if special handling is needed for CONF calls.
+ public static boolean canVideoPause(Call call) {
+ return isVideoCall(call) && call.getState() == Call.State.ACTIVE;
+ }
+
+ public static VideoProfile makeVideoPauseProfile(Call call) {
+ Preconditions.checkNotNull(call);
+ Preconditions.checkState(!VideoProfile.VideoState.isAudioOnly(call.getVideoState()));
+ return new VideoProfile(getPausedVideoState(call.getVideoState()));
+ }
+
+ public static VideoProfile makeVideoUnPauseProfile(Call call) {
+ Preconditions.checkNotNull(call);
+ return new VideoProfile(getUnPausedVideoState(call.getVideoState()));
+ }
+
+ public static int getUnPausedVideoState(int videoState) {
+ return videoState & (~VideoProfile.VideoState.PAUSED);
+ }
+
+ public static int getPausedVideoState(int videoState) {
+ return videoState | VideoProfile.VideoState.PAUSED;
+ }
+
+}
diff --git a/InCallUI/src/com/android/incallui/GlowPadWrapper.java b/InCallUI/src/com/android/incallui/GlowPadWrapper.java
index b50fdd8c2..584ce65de 100644
--- a/InCallUI/src/com/android/incallui/GlowPadWrapper.java
+++ b/InCallUI/src/com/android/incallui/GlowPadWrapper.java
@@ -108,7 +108,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger
@Override
public void onTrigger(View v, int target) {
- Log.d(this, "onTrigger()");
+ Log.d(this, "onTrigger() view=" + v + " target=" + target);
final int resId = getResourceIdForTarget(target);
switch (resId) {
case R.drawable.ic_lockscreen_answer:
@@ -116,7 +116,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger
mTargetTriggered = true;
break;
case R.drawable.ic_lockscreen_decline:
- mAnswerListener.onDecline();
+ mAnswerListener.onDecline(getContext());
mTargetTriggered = true;
break;
case R.drawable.ic_lockscreen_text:
@@ -128,6 +128,14 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger
mAnswerListener.onAnswer(VideoProfile.VideoState.BIDIRECTIONAL, getContext());
mTargetTriggered = true;
break;
+ case R.drawable.ic_lockscreen_answer_tx_video:
+ mAnswerListener.onAnswer(VideoProfile.VideoState.TX_ENABLED, getContext());
+ mTargetTriggered = true;
+ break;
+ case R.drawable.ic_lockscreen_answer_rx_video:
+ mAnswerListener.onAnswer(VideoProfile.VideoState.RX_ENABLED, getContext());
+ mTargetTriggered = true;
+ break;
case R.drawable.ic_toolbar_video_off:
InCallPresenter.getInstance().declineUpgradeRequest(getContext());
mTargetTriggered = true;
@@ -154,7 +162,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger
public interface AnswerListener {
void onAnswer(int videoState, Context context);
- void onDecline();
+ void onDecline(Context context);
void onText();
}
}
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index f75c33b23..873c288a4 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -105,10 +105,9 @@ public class InCallActivity extends Activity {
};
/**
- * Stores the current orientation of the activity. Used to determine if a change in orientation
- * has occurred.
+ * Used to determine if a change in orientation has occurred.
*/
- private int mCurrentOrientation;
+ private static int sCurrentOrientation = Configuration.ORIENTATION_UNDEFINED;
@Override
protected void onCreate(Bundle icicle) {
@@ -143,9 +142,8 @@ public class InCallActivity extends Activity {
internalResolveIntent(getIntent());
- mCurrentOrientation = getResources().getConfiguration().orientation;
- mIsLandscape = getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ mIsLandscape = getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE;
final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
View.LAYOUT_DIRECTION_RTL;
@@ -193,6 +191,11 @@ public class InCallActivity extends Activity {
// setting activity should be last thing in setup process
InCallPresenter.getInstance().setActivity(this);
+
+ // It is possible that the activity restarted because orientation changed.
+ // Notify listeners if orientation changed.
+ doOrientationChanged(getResources().getConfiguration().orientation);
+ InCallPresenter.getInstance().onActivityStarted();
}
@Override
@@ -244,6 +247,9 @@ public class InCallActivity extends Activity {
@Override
protected void onStop() {
Log.d(this, "onStop()...");
+
+ InCallPresenter.getInstance().updateIsChangingConfigurations();
+ InCallPresenter.getInstance().onActivityStopped();
super.onStop();
}
@@ -251,6 +257,7 @@ public class InCallActivity extends Activity {
protected void onDestroy() {
Log.d(this, "onDestroy()... this = " + this);
InCallPresenter.getInstance().unsetActivity(this);
+ InCallPresenter.getInstance().updateIsChangingConfigurations();
super.onDestroy();
}
@@ -446,15 +453,22 @@ public class InCallActivity extends Activity {
InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config);
Log.d(this, "onConfigurationChanged "+config.orientation);
+ doOrientationChanged(config.orientation);
+ super.onConfigurationChanged(config);
+ }
+
+
+ private void doOrientationChanged(int orientation) {
+ Log.d(this, "doOrientationChanged prevOrientation=" + sCurrentOrientation +
+ " newOrientation=" + orientation);
// Check to see if the orientation changed to prevent triggering orientation change events
// for other configuration changes.
- if (config.orientation != mCurrentOrientation) {
- mCurrentOrientation = config.orientation;
+ if (orientation != sCurrentOrientation) {
+ sCurrentOrientation = orientation;
InCallPresenter.getInstance().onDeviceRotationChange(
getWindowManager().getDefaultDisplay().getRotation());
- InCallPresenter.getInstance().onDeviceOrientationChange(mCurrentOrientation);
+ InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation);
}
- super.onConfigurationChanged(config);
}
public CallButtonFragment getCallButtonFragment() {
diff --git a/InCallUI/src/com/android/incallui/InCallApp.java b/InCallUI/src/com/android/incallui/InCallApp.java
index d6f4f42de..a273d7805 100644
--- a/InCallUI/src/com/android/incallui/InCallApp.java
+++ b/InCallUI/src/com/android/incallui/InCallApp.java
@@ -81,7 +81,9 @@ public class InCallApp extends Application {
} else if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) {
InCallPresenter.getInstance().hangUpOngoingCall(context);
} else if (action.equals(ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST)) {
- InCallPresenter.getInstance().acceptUpgradeRequest(context);
+ //TODO: Change calltype after adding support for TX and RX
+ InCallPresenter.getInstance().acceptUpgradeRequest(
+ VideoProfile.VideoState.BIDIRECTIONAL, context);
} else if (action.equals(ACTION_DECLINE_VIDEO_UPGRADE_REQUEST)) {
InCallPresenter.getInstance().declineUpgradeRequest(context);
}
diff --git a/InCallUI/src/com/android/incallui/InCallCameraManager.java b/InCallUI/src/com/android/incallui/InCallCameraManager.java
index ded9387f3..b7ec079af 100644
--- a/InCallUI/src/com/android/incallui/InCallCameraManager.java
+++ b/InCallUI/src/com/android/incallui/InCallCameraManager.java
@@ -25,12 +25,22 @@ import android.hardware.camera2.params.StreamConfigurationMap;
import android.util.Size;
import java.lang.String;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Set;
/**
* Used to track which camera is used for outgoing video.
*/
public class InCallCameraManager {
+ public interface Listener {
+ void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera);
+ }
+
+ private final Set<Listener> mCameraSelectionListeners = Collections.
+ newSetFromMap(new ConcurrentHashMap<Listener, Boolean>(8,0.9f,1));
+
/**
* The camera ID for the front facing camera.
*/
@@ -73,6 +83,9 @@ public class InCallCameraManager {
*/
public void setUseFrontFacingCamera(boolean useFrontFacingCamera) {
mUseFrontFacingCamera = useFrontFacingCamera;
+ for (Listener listener : mCameraSelectionListeners) {
+ listener.onActiveCameraSelectionChanged(mUseFrontFacingCamera);
+ }
}
/**
@@ -148,4 +161,16 @@ public class InCallCameraManager {
}
}
}
+
+ public void addCameraSelectionListener(Listener listener) {
+ if (listener != null) {
+ mCameraSelectionListeners.add(listener);
+ }
+ }
+
+ public void removeCameraSelectionListener(Listener listener) {
+ if (listener != null) {
+ mCameraSelectionListeners.remove(listener);
+ }
+ }
}
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 7c71d2c86..74c85db8a 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -34,6 +34,8 @@ import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.view.Surface;
import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
import com.google.common.base.Preconditions;
@@ -156,7 +158,6 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
*/
private boolean mIsActivityPreviouslyStarted = false;
-
/**
* Whether or not to wait for the circular reveal animation to be started, to avoid stopping
* the circular reveal animation activity before the animation is initiated.
@@ -175,6 +176,14 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
*/
private boolean mServiceBound = false;
+ /**
+ * When configuration changes Android kills the current activity and starts a new one.
+ * The flag is used to check if full clean up is necessary (activity is stopped and new
+ * activity won't be started), or if a new activity will be started right after the current one
+ * is destroyed, and therefore no need in release all resources.
+ */
+ private boolean mIsChangingConfigurations = false;
+
private Phone mPhone;
private Handler mHandler = new Handler();
@@ -243,6 +252,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
// will kick off an update and the whole process can start.
mCallList.addListener(this);
+ VideoPauseController.getInstance().setUp(this);
+
Log.d(this, "Finished InCallPresenter.setUp");
}
@@ -258,6 +269,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
Log.d(this, "tearDown");
mServiceConnected = false;
attemptCleanup();
+
+ VideoPauseController.getInstance().tearDown();
}
private void attemptFinishActivity() {
@@ -398,9 +411,12 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
if (callList == null) {
return;
}
+ Log.d(this, "onCallListChange callList=" + callList.toString() );
InCallState newState = getPotentialStateFromCallList(callList);
InCallState oldState = mInCallState;
+ Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState);
newState = startOrFinishUi(newState);
+ Log.d(this, "onCallListChange newState changed to " + newState);
// Set the new state before announcing it to the world
Log.i(this, "Phone switching state: " + oldState + " -> " + newState);
@@ -437,6 +453,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
}
+ @Override
+ public void onUpgradeToVideo(Call call) {
+ //NO-OP
+ }
/**
* Called when a call becomes disconnected. Called everytime an existing call
* changes from being connected (incoming/outgoing/active) to disconnected.
@@ -631,33 +651,36 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
}
- public void acceptUpgradeRequest(Context 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.
if (mCallList == null) {
StatusBarNotifier.clearInCallNotification(context);
+ Log.e(this, " acceptUpgradeRequest mCallList is empty so returning");
return;
}
Call call = mCallList.getVideoUpgradeRequestCall();
if (call != null) {
- VideoProfile videoProfile =
- new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL);
+ VideoProfile videoProfile = new VideoProfile(videoState);
call.getVideoCall().sendSessionModifyResponse(videoProfile);
call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
}
}
public void declineUpgradeRequest(Context context) {
+ Log.d(this, " declineUpgradeRequest");
// Bail if we have been shut down and the call list is null.
if (mCallList == null) {
StatusBarNotifier.clearInCallNotification(context);
+ Log.e(this, " declineUpgradeRequest mCallList is empty so returning");
return;
}
Call call = mCallList.getVideoUpgradeRequestCall();
if (call != null) {
VideoProfile videoProfile =
- new VideoProfile(VideoProfile.VideoState.AUDIO_ONLY);
+ new VideoProfile(call.getVideoState());
call.getVideoCall().sendSessionModifyResponse(videoProfile);
call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
}
@@ -685,6 +708,20 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
return mIsActivityPreviouslyStarted;
}
+ public boolean isChangingConfigurations() {
+ return mIsChangingConfigurations;
+ }
+
+ /*package*/
+ void updateIsChangingConfigurations() {
+ mIsChangingConfigurations = false;
+ if (mInCallActivity != null) {
+ mIsChangingConfigurations = mInCallActivity.isChangingConfigurations();
+ }
+ Log.d(this, "IsChangingConfigurations=" + mIsChangingConfigurations);
+ }
+
+
/**
* Called when the activity goes in/out of the foreground.
*/
@@ -716,6 +753,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
mIsActivityPreviouslyStarted = true;
} else {
CircularRevealActivity.sendClearDisplayBroadcast(mContext);
+ updateIsChangingConfigurations();
}
for (InCallUiListener listener : mInCallUiListeners) {
@@ -731,6 +769,26 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
return mInCallUiListeners.remove(listener);
}
+ /*package*/
+ void onActivityStarted() {
+ Log.d(this, "onActivityStarted");
+ notifyVideoPauseController(true);
+ }
+
+ /*package*/
+ void onActivityStopped() {
+ Log.d(this, "onActivityStopped");
+ notifyVideoPauseController(false);
+ }
+
+ private void notifyVideoPauseController(boolean showing) {
+ Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" +
+ mIsChangingConfigurations);
+ if (!mIsChangingConfigurations) {
+ VideoPauseController.getInstance().onUiShowing(showing);
+ }
+ }
+
/**
* Brings the app into the foreground if possible.
*/
@@ -1088,6 +1146,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
if (shouldCleanup) {
mIsActivityPreviouslyStarted = false;
+ mIsChangingConfigurations = false;
// blow away stale contact info so that we get fresh data on
// the next set of calls
@@ -1119,6 +1178,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
mListeners.clear();
mIncomingCallListeners.clear();
+ mDetailsListeners.clear();
+ mCanAddCallListeners.clear();
+ mOrientationListeners.clear();
+ mInCallEventListeners.clear();
Log.d(this, "Finished InCallPresenter.CleanUp");
}
@@ -1252,7 +1315,20 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
* @param rotation The device rotation.
*/
public void onDeviceRotationChange(int rotation) {
+ Log.d(this, "onDeviceRotationChange: rotation=" + rotation);
// First translate to rotation in degrees.
+ if (mCallList != null) {
+ mCallList.notifyCallsOfDeviceRotation(toRotationAngle(rotation));
+ } else {
+ Log.w(this, "onDeviceRotationChange: CallList is null.");
+ }
+ }
+
+ /**
+ * Converts rotation constants to rotation in degrees.
+ * @param rotation Rotation constants.
+ */
+ public static int toRotationAngle(int rotation) {
int rotationAngle;
switch (rotation) {
case Surface.ROTATION_0:
@@ -1270,8 +1346,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
default:
rotationAngle = 0;
}
-
- mCallList.notifyCallsOfDeviceRotation(rotationAngle);
+ return rotationAngle;
}
/**
@@ -1292,6 +1367,11 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
* and landscape. {@Code False} if the in-call UI should be locked in portrait.
*/
public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
+ if (mInCallActivity == null) {
+ Log.e(this, "InCallActivity is null. Can't set requested orientation.");
+ return;
+ }
+
if (!allowOrientationChange) {
mInCallActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
} else {
@@ -1299,6 +1379,21 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
}
+ public void enableScreenTimeout(boolean enable) {
+ Log.v(this, "enableScreenTimeout: value=" + enable);
+ if (mInCallActivity == null) {
+ Log.e(this, "enableScreenTimeout: InCallActivity is null.");
+ return;
+ }
+
+ final Window window = mInCallActivity.getWindow();
+ if (enable) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
/**
* Returns the space available beside the call card.
*
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java
index 245fc7316..cf2b859e0 100644
--- a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java
+++ b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java
@@ -18,6 +18,7 @@ package com.android.incallui;
import android.telecom.CameraCapabilities;
import android.telecom.Connection;
+import android.telecom.Connection.VideoProvider;
import android.telecom.InCallService.VideoCall;
import android.telecom.VideoProfile;
@@ -47,53 +48,50 @@ public class InCallVideoCallListener extends VideoCall.Listener {
*/
@Override
public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
- int previousVideoState = mCall.getVideoState();
- int newVideoState = videoProfile.getVideoState();
+ Log.d(this, " onSessionModifyRequestReceived videoProfile=" + videoProfile);
+ int previousVideoState = CallUtils.getUnPausedVideoState(mCall.getVideoState());
+ int newVideoState = CallUtils.getUnPausedVideoState(videoProfile.getVideoState());
- boolean wasVideoCall = VideoProfile.VideoState.isBidirectional(previousVideoState);
- boolean isVideoCall = VideoProfile.VideoState.isBidirectional(newVideoState);
-
- boolean wasPaused = VideoProfile.VideoState.isPaused(previousVideoState);
- boolean isPaused = VideoProfile.VideoState.isPaused(newVideoState);
+ boolean wasVideoCall = VideoProfile.VideoState.isVideo(previousVideoState);
+ boolean isVideoCall = VideoProfile.VideoState.isVideo(newVideoState);
// Check for upgrades to video and downgrades to audio.
- if (!wasVideoCall && isVideoCall) {
- InCallVideoCallListenerNotifier.getInstance().upgradeToVideoRequest(mCall);
- } else if (wasVideoCall && !isVideoCall) {
+ if (wasVideoCall && !isVideoCall) {
InCallVideoCallListenerNotifier.getInstance().downgradeToAudio(mCall);
+ } else if (previousVideoState != newVideoState) {
+ InCallVideoCallListenerNotifier.getInstance().upgradeToVideoRequest(mCall,
+ newVideoState);
}
-
- boolean pause = !wasPaused && isPaused;
- InCallVideoCallListenerNotifier.getInstance().peerPausedStateChanged(mCall, pause);
}
/**
* Handles a session modification response.
*
- * @param status Status of the session modify request. Valid values are
- * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS},
- * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL},
- * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
+ * @param status Status of the session modify request. Valid values are
+ * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS},
+ * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL},
+ * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
* @param requestedProfile
* @param responseProfile The actual profile changes made by the peer device.
*/
@Override
- public void onSessionModifyResponseReceived(
- int status, VideoProfile requestedProfile, VideoProfile responseProfile) {
- boolean modifySucceeded =
- requestedProfile.getVideoState() == responseProfile.getVideoState();
- boolean isVideoCall =
- VideoProfile.VideoState.isBidirectional(responseProfile.getVideoState());
-
- if (modifySucceeded && isVideoCall) {
- // Local Upgrade success
- InCallVideoCallListenerNotifier.getInstance().upgradeToVideoSuccess(mCall);
- } else if (!modifySucceeded || status != Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
- // Remote didn't accept invitation in bidirectional state or failure
- InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(mCall);
- } else if (modifySucceeded && !isVideoCall) {
- // Local Downgrade success (should always be successful)
- InCallVideoCallListenerNotifier.getInstance().downgradeToAudio(mCall);
+ public void onSessionModifyResponseReceived(int status, VideoProfile requestedProfile,
+ VideoProfile responseProfile) {
+ Log.d(this, "onSessionModifyResponseReceived status=" + status + " requestedProfile="
+ + requestedProfile + " responseProfile=" + responseProfile);
+ if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
+ InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(status, mCall);
+ } else if (requestedProfile != null && responseProfile != null) {
+ boolean modifySucceeded = requestedProfile.getVideoState() ==
+ responseProfile.getVideoState();
+ boolean isVideoCall = VideoProfile.VideoState.isVideo(responseProfile.getVideoState());
+ if (modifySucceeded && isVideoCall) {
+ InCallVideoCallListenerNotifier.getInstance().upgradeToVideoSuccess(mCall);
+ } else if (!modifySucceeded) {
+ InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(status, mCall);
+ }
+ } else {
+ Log.d(this, "onSessionModifyResponseReceived request and response Profiles are null");
}
}
@@ -104,6 +102,7 @@ public class InCallVideoCallListener extends VideoCall.Listener {
*/
@Override
public void onCallSessionEvent(int event) {
+ InCallVideoCallListenerNotifier.getInstance().callSessionEvent(event);
}
/**
@@ -118,13 +117,25 @@ public class InCallVideoCallListener extends VideoCall.Listener {
}
/**
+ * Handles a change to the video quality of the call.
+ *
+ * @param videoQuality The updated video call quality.
+ */
+ @Override
+ public void onVideoQualityChanged(int videoQuality) {
+ InCallVideoCallListenerNotifier.getInstance().videoQualityChanged(mCall, videoQuality);
+ }
+
+ /**
* Handles a change to the call data usage. No implementation as the in-call UI does not
* display data usage.
*
* @param dataUsage The updated data usage.
*/
@Override
- public void onCallDataUsageChanged(int dataUsage) {
+ public void onCallDataUsageChanged(long dataUsage) {
+ Log.d(this, "onCallDataUsageChanged: dataUsage = " + dataUsage);
+ InCallVideoCallListenerNotifier.getInstance().callDataUsageChanged(dataUsage);
}
/**
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java b/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java
index 9f3f062cd..818ed032a 100644
--- a/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java
+++ b/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java
@@ -123,12 +123,13 @@ public class InCallVideoCallListenerNotifier {
/**
* Inform listeners of an upgrade to video request for a call.
- *
* @param call The call.
+ * @param videoState The video state we want to upgrade to.
*/
- public void upgradeToVideoRequest(Call call) {
+ public void upgradeToVideoRequest(Call call, int videoState) {
+ Log.d(this, "upgradeToVideoRequest call = " + call + " new video state = " + videoState);
for (SessionModificationListener listener : mSessionModificationListeners) {
- listener.onUpgradeToVideoRequest(call);
+ listener.onUpgradeToVideoRequest(call, videoState);
}
}
@@ -148,9 +149,9 @@ public class InCallVideoCallListenerNotifier {
*
* @param call The call.
*/
- public void upgradeToVideoFail(Call call) {
+ public void upgradeToVideoFail(int status, Call call) {
for (SessionModificationListener listener : mSessionModificationListeners) {
- listener.onUpgradeToVideoFail(call);
+ listener.onUpgradeToVideoFail(status, call);
}
}
@@ -166,6 +167,17 @@ public class InCallVideoCallListenerNotifier {
}
/**
+ * Inform listeners of a call session event.
+ *
+ * @param event The call session event.
+ */
+ public void callSessionEvent(int event) {
+ for (VideoEventListener listener : mVideoEventListeners) {
+ listener.onCallSessionEvent(event);
+ }
+ }
+
+ /**
* Inform listeners of a downgrade to audio.
*
* @param call The call.
@@ -178,6 +190,18 @@ public class InCallVideoCallListenerNotifier {
}
/**
+ * Inform listeners of any change in the video quality of the call
+ *
+ * @param call The call.
+ * @param videoQuality The updated video quality of the call.
+ */
+ public void videoQualityChanged(Call call, int videoQuality) {
+ for (VideoEventListener listener : mVideoEventListeners) {
+ listener.onVideoQualityChanged(call, videoQuality);
+ }
+ }
+
+ /**
* Inform listeners of a change to peer dimensions.
*
* @param call The call.
@@ -204,6 +228,17 @@ public class InCallVideoCallListenerNotifier {
}
/**
+ * Inform listeners of a change to call data usage.
+ *
+ * @param dataUsage data usage value
+ */
+ public void callDataUsageChanged(long dataUsage) {
+ for (VideoEventListener listener : mVideoEventListeners) {
+ listener.onCallDataUsageChange(dataUsage);
+ }
+ }
+
+ /**
* Listener interface for any class that wants to be notified of upgrade to video and downgrade
* to audio session modification requests.
*/
@@ -212,8 +247,9 @@ public class InCallVideoCallListenerNotifier {
* Called when a peer request is received to upgrade an audio-only call to a video call.
*
* @param call The call the request was received for.
+ * @param videoState The video state that the request wants to upgrade to.
*/
- public void onUpgradeToVideoRequest(Call call);
+ 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
@@ -230,7 +266,7 @@ public class InCallVideoCallListenerNotifier {
*
* @param call The call the request was successful for.
*/
- public void onUpgradeToVideoFail(Call call);
+ public void onUpgradeToVideoFail(int status, Call call);
/**
* Called when a call has been downgraded to audio-only.
@@ -242,7 +278,7 @@ public class InCallVideoCallListenerNotifier {
/**
* Listener interface for any class that wants to be notified of video events, including pause
- * and un-pause of peer video.
+ * and un-pause of peer video, video quality changes.
*/
public interface VideoEventListener {
/**
@@ -253,6 +289,29 @@ public class InCallVideoCallListenerNotifier {
* otherwise.
*/
public void onPeerPauseStateChanged(Call call, boolean paused);
+
+ /**
+ * Called when the video quality changes.
+ *
+ * @param call The call whose video quality changes.
+ * @param videoCallQuality - values are QUALITY_HIGH, MEDIUM, LOW and UNKNOWN.
+ */
+ public void onVideoQualityChanged(Call call, int videoCallQuality);
+
+ /*
+ * Called when call data usage value is requested or when call data usage value is updated
+ * because of a call state change
+ *
+ * @param dataUsage call data usage value
+ */
+ public void onCallDataUsageChange(long dataUsage);
+
+ /**
+ * Called when call session event is raised.
+ *
+ * @param event The call session event.
+ */
+ public void onCallSessionEvent(int event);
}
/**
diff --git a/InCallUI/src/com/android/incallui/Log.java b/InCallUI/src/com/android/incallui/Log.java
index 07a0e61ca..5bc74b1a6 100644
--- a/InCallUI/src/com/android/incallui/Log.java
+++ b/InCallUI/src/com/android/incallui/Log.java
@@ -31,7 +31,7 @@ public class Log {
// Generic tag for all In Call logging
public static final String TAG = "InCall";
- public static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
+ public static final boolean FORCE_DEBUG = true; /* STOPSHIP if true */
public static final boolean DEBUG = FORCE_DEBUG ||
android.util.Log.isLoggable(TAG, android.util.Log.DEBUG);
public static final boolean VERBOSE = FORCE_DEBUG ||
diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java
index 7859a17bc..c4d10c569 100644
--- a/InCallUI/src/com/android/incallui/VideoCallFragment.java
+++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java
@@ -16,10 +16,14 @@
package com.android.incallui;
+import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.os.Bundle;
+import android.telecom.Connection;
+import android.telecom.VideoProfile;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Surface;
@@ -28,6 +32,9 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
+import android.widget.Toast;
+
+import com.google.common.base.Objects;
/**
* Fragment containing video calling surfaces.
@@ -50,11 +57,23 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
*/
public static final int SURFACE_PREVIEW = 2;
+ /**
+ * Used to indicate that the UI rotation is unknown.
+ */
+ public static final int ORIENTATION_UNKNOWN = -1;
+
+ /**
+ * Invalid resource id.
+ */
+ public static final int INVALID_RESOURCE_ID = -1;
+
+
// Static storage used to retain the video surfaces across Activity restart.
// TextureViews are not parcelable, so it is not possible to store them in the saved state.
private static boolean sVideoSurfacesInUse = false;
private static VideoCallSurface sPreviewSurface = null;
private static VideoCallSurface sDisplaySurface = null;
+ private static Point sDisplaySize = null;
/**
* {@link ViewStub} holding the video call surfaces. This is the parent for the
@@ -84,26 +103,20 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
private boolean mIsLandscape;
/**
- * The width of the surface.
- */
- private int mWidth = DIMENSIONS_NOT_SET;
-
- /**
- * The height of the surface.
- */
- private int mHeight = DIMENSIONS_NOT_SET;
-
- /**
* Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and
* {@link Surface}. Used to manage the lifecycle of these objects across device orientation
* changes.
*/
- private class VideoCallSurface implements TextureView.SurfaceTextureListener,
+ private static class VideoCallSurface implements TextureView.SurfaceTextureListener,
View.OnClickListener {
private int mSurfaceId;
+ private VideoCallPresenter mPresenter;
private TextureView mTextureView;
private SurfaceTexture mSavedSurfaceTexture;
private Surface mSavedSurface;
+ private boolean mIsDoneWithSurface;
+ private int mWidth = DIMENSIONS_NOT_SET;
+ private int mHeight = DIMENSIONS_NOT_SET;
/**
* Creates an instance of a {@link VideoCallSurface}.
@@ -111,8 +124,9 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
* @param surfaceId The surface ID of the surface.
* @param textureView The {@link TextureView} for the surface.
*/
- public VideoCallSurface(int surfaceId, TextureView textureView) {
- this(surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET);
+ public VideoCallSurface(VideoCallPresenter presenter, int surfaceId,
+ TextureView textureView) {
+ this(presenter, surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET);
}
/**
@@ -123,7 +137,11 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
* @param width The width of the surface.
* @param height The height of the surface.
*/
- public VideoCallSurface(int surfaceId, TextureView textureView, int width, int height) {
+ public VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView,
+ int width, int height) {
+ Log.d(this, "VideoCallSurface: surfaceId=" + surfaceId +
+ " width=" + width + " height=" + height);
+ mPresenter = presenter;
mWidth = width;
mHeight = height;
mSurfaceId = surfaceId;
@@ -142,9 +160,23 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
mTextureView.setSurfaceTextureListener(this);
mTextureView.setOnClickListener(this);
- if (mSavedSurfaceTexture != null) {
+ final boolean areSameSurfaces =
+ Objects.equal(mSavedSurfaceTexture, mTextureView.getSurfaceTexture());
+ Log.d(this, "recreateView: SavedSurfaceTexture=" + mSavedSurfaceTexture
+ + " areSameSurfaces=" + areSameSurfaces);
+ if (mSavedSurfaceTexture != null && !areSameSurfaces) {
mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
+ if (createSurface(mWidth, mHeight)) {
+ onSurfaceCreated();
+ }
}
+ mIsDoneWithSurface = false;
+ }
+
+ public void resetPresenter(VideoCallPresenter presenter) {
+ Log.d(this, "resetPresenter: CurrentPresenter=" + mPresenter + " NewPresenter="
+ + presenter);
+ mPresenter = presenter;
}
/**
@@ -162,17 +194,31 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
// Where there is no saved {@link SurfaceTexture} available, use the newly created one.
// If a saved {@link SurfaceTexture} is available, we are re-creating after an
// orientation change.
+ Log.d(this, " onSurfaceTextureAvailable mSurfaceId=" + mSurfaceId + " surfaceTexture="
+ + surfaceTexture + " width=" + width
+ + " height=" + height + " mSavedSurfaceTexture=" + mSavedSurfaceTexture);
+ Log.d(this, " onSurfaceTextureAvailable VideoCallPresenter=" + mPresenter);
if (mSavedSurfaceTexture == null) {
mSavedSurfaceTexture = surfaceTexture;
surfaceCreated = createSurface(width, height);
} else {
// A saved SurfaceTexture was found.
+ Log.d(this, " onSurfaceTextureAvailable: Replacing with cached surface...");
+ mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
surfaceCreated = true;
}
// Inform presenter that the surface is available.
if (surfaceCreated) {
- getPresenter().onSurfaceCreated(mSurfaceId);
+ onSurfaceCreated();
+ }
+ }
+
+ private void onSurfaceCreated() {
+ if (mPresenter != null) {
+ mPresenter.onSurfaceCreated(mSurfaceId);
+ } else {
+ Log.e(this, "onSurfaceTextureAvailable: Presenter is null");
}
}
@@ -200,17 +246,30 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
/**
* Destroying the surface texture; inform the presenter so it can null the surfaces.
*/
- if (mSavedSurfaceTexture == null) {
- getPresenter().onSurfaceDestroyed(mSurfaceId);
+ Log.d(this, " onSurfaceTextureDestroyed mSurfaceId=" + mSurfaceId + " surfaceTexture="
+ + surfaceTexture + " SavedSurfaceTexture=" + mSavedSurfaceTexture
+ + " SavedSurface=" + mSavedSurface);
+ Log.d(this, " onSurfaceTextureDestroyed VideoCallPresenter=" + mPresenter);
+
+ // Notify presenter if it is not null.
+ onSurfaceDestroyed();
+
+ if (mIsDoneWithSurface) {
+ onSurfaceReleased();
if (mSavedSurface != null) {
mSavedSurface.release();
mSavedSurface = null;
}
}
+ return mIsDoneWithSurface;
+ }
- // The saved SurfaceTexture will be null if we're shutting down, so we want to
- // return "true" in that case (indicating that TextureView can release the ST).
- return (mSavedSurfaceTexture == null);
+ private void onSurfaceDestroyed() {
+ if (mPresenter != null) {
+ mPresenter.onSurfaceDestroyed(mSurfaceId);
+ } else {
+ Log.e(this, "onSurfaceTextureDestroyed: Presenter is null.");
+ }
}
/**
@@ -236,7 +295,15 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
* change in video state. Releases and clears out the saved surface and surface textures.
*/
public void setDoneWithSurface() {
+ Log.d(this, "setDoneWithSurface: SavedSurface=" + mSavedSurface
+ + " SavedSurfaceTexture=" + mSavedSurfaceTexture);
+ mIsDoneWithSurface = true;
+ if (mTextureView != null && mTextureView.isAvailable()) {
+ return;
+ }
+
if (mSavedSurface != null) {
+ onSurfaceReleased();
mSavedSurface.release();
mSavedSurface = null;
}
@@ -246,6 +313,14 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
}
}
+ private void onSurfaceReleased() {
+ if (mPresenter != null) {
+ mPresenter.onSurfaceReleased(mSurfaceId);
+ } else {
+ Log.d(this, "setDoneWithSurface: Presenter is null.");
+ }
+ }
+
/**
* Retrieves the saved surface instance.
*
@@ -262,10 +337,12 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
* @param height The height of the surface, in pixels.
*/
public void setSurfaceDimensions(int width, int height) {
+ Log.d(this, "setSurfaceDimensions, width=" + width + " height=" + height);
mWidth = width;
mHeight = height;
if (mSavedSurfaceTexture != null) {
+ Log.d(this, "setSurfaceDimensions, mSavedSurfaceTexture is NOT equal to null.");
createSurface(width, height);
}
}
@@ -276,9 +353,10 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
* @param height The height of the surface to create.
*/
private boolean createSurface(int width, int height) {
+ Log.d(this, "createSurface mSavedSurfaceTexture=" + mSavedSurfaceTexture
+ + " mSurfaceId =" + mSurfaceId + " mWidth " + width + " mHeight=" + height);
if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET
&& mSavedSurfaceTexture != null) {
-
mSavedSurfaceTexture.setDefaultBufferSize(width, height);
mSavedSurface = new Surface(mSavedSurfaceTexture);
return true;
@@ -294,7 +372,11 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
*/
@Override
public void onClick(View view) {
- getPresenter().onSurfaceClick(mSurfaceId);
+ if (mPresenter != null) {
+ mPresenter.onSurfaceClick(mSurfaceId);
+ } else {
+ Log.e(this, "onClick: Presenter is null.");
+ }
}
};
@@ -316,6 +398,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
mIsLandscape = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
+ Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape);
getPresenter().init(getActivity());
}
@@ -376,9 +459,13 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
// so we need to translate it in the -X direction.
final boolean isLayoutRtl = InCallPresenter.isRtl();
+ ViewGroup.LayoutParams params = displayVideo.getLayoutParams();
float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard();
+ Log.d(this, "centerDisplayView: IsLandscape= " + mIsLandscape + " Layout width: " +
+ params.width + " height: " + params.height + " spaceBesideCallCard: "
+ + spaceBesideCallCard);
if (mIsLandscape) {
- float videoViewTranslation = displayVideo.getWidth() / 2
+ float videoViewTranslation = params.width / 2
- spaceBesideCallCard / 2;
if (isLayoutRtl) {
displayVideo.setTranslationX(-videoViewTranslation);
@@ -386,7 +473,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
displayVideo.setTranslationX(videoViewTranslation);
}
} else {
- float videoViewTranslation = displayVideo.getHeight() / 2
+ float videoViewTranslation = params.height / 2
- spaceBesideCallCard / 2;
displayVideo.setTranslationY(videoViewTranslation);
}
@@ -401,6 +488,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ Log.d(this, "onViewCreated: VideoSurfacesInUse=" + sVideoSurfacesInUse);
mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub);
@@ -412,13 +500,34 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
}
}
+ @Override
+ public void onStop() {
+ super.onStop();
+ Log.d(this, "onStop:");
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ Log.d(this, "onPause:");
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ Log.d(this, "onDestroyView:");
+ }
+
/**
* Creates the presenter for the {@link VideoCallFragment}.
* @return The presenter instance.
*/
@Override
public VideoCallPresenter createPresenter() {
- return new VideoCallPresenter();
+ Log.d(this, "createPresenter");
+ VideoCallPresenter presenter = new VideoCallPresenter();
+ onPresenterChanged(presenter);
+ return presenter;
}
/**
@@ -430,33 +539,144 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
}
/**
- * Toggles visibility of the video UI.
+ * Inflate video surfaces.
*
* @param show {@code True} if the video surfaces should be shown.
*/
- @Override
- public void showVideoUi(boolean show) {
+ private void inflateVideoUi(boolean show) {
int visibility = show ? View.VISIBLE : View.GONE;
getView().setVisibility(visibility);
if (show) {
inflateVideoCallViews();
- } else {
- cleanupSurfaces();
}
- if (mVideoViews != null ) {
+ if (mVideoViews != null) {
mVideoViews.setVisibility(visibility);
}
}
/**
+ * Show or hide preview and incoming video views
+ */
+ public void showVideoViews(boolean showPreview, boolean showIncoming) {
+ inflateVideoUi(true);
+
+ View incomingVideoView = mVideoViews.findViewById(R.id.incomingVideo);
+ View previewVideoView = mVideoViews.findViewById(R.id.previewVideo);
+
+ if (incomingVideoView != null) {
+ incomingVideoView.setVisibility(showIncoming ? View.VISIBLE : View.INVISIBLE);
+ }
+ if (previewVideoView != null) {
+ previewVideoView.setVisibility(showPreview ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ /**
+ * Hide all video views.
+ */
+ public void hideVideoUi() {
+ inflateVideoUi(false);
+ }
+
+ /**
+ * Displays a message on the UI that the video call quality has changed.
+ *
+ */
+ @Override
+ public void showVideoQualityChanged(int videoQuality) {
+ Log.d(this, "showVideoQualityChanged. Video quality changed to " + videoQuality);
+
+ final Context context = getActivity();
+ if (context == null) {
+ Log.e(this, "showVideoQualityChanged - Activity is null. Return");
+ return;
+ }
+
+ final Resources resources = context.getResources();
+
+ int videoQualityResourceId = R.string.video_quality_unknown;
+ switch (videoQuality) {
+ case VideoProfile.QUALITY_HIGH:
+ videoQualityResourceId = R.string.video_quality_high;
+ break;
+ case VideoProfile.QUALITY_MEDIUM:
+ videoQualityResourceId = R.string.video_quality_medium;
+ break;
+ case VideoProfile.QUALITY_LOW:
+ videoQualityResourceId = R.string.video_quality_low;
+ break;
+ default:
+ break;
+ }
+
+ String videoQualityChangedText = resources.getString(R.string.video_quality_changed) +
+ resources.getString(videoQualityResourceId);
+
+ Toast.makeText(context, videoQualityChangedText, Toast.LENGTH_SHORT).show();
+ }
+
+ /**
+ * Displays a message on the UI that the call substate has changed.
+ *
+ */
+ @Override
+ public void showCallSubstateChanged(int callSubstate) {
+ Log.d(this, "showCallSubstateChanged - call substate changed to " + callSubstate);
+
+ final Context context = getActivity();
+ if (context == null) {
+ Log.e(this, "showCallSubstateChanged - Activity is null. Return");
+ return;
+ }
+
+ final Resources resources = context.getResources();
+
+ String callSubstateChangedText = "";
+
+ if (isEnabled(Connection.SUBSTATE_AUDIO_CONNECTED_SUSPENDED, callSubstate)) {
+ callSubstateChangedText +=
+ resources.getString(R.string.call_substate_connected_suspended_audio);
+ }
+
+ if (isEnabled(Connection.SUBSTATE_VIDEO_CONNECTED_SUSPENDED, callSubstate)) {
+ callSubstateChangedText +=
+ resources.getString(R.string.call_substate_connected_suspended_video);
+ }
+
+ if (isEnabled(Connection.SUBSTATE_AVP_RETRY, callSubstate)) {
+ callSubstateChangedText +=
+ resources.getString(R.string.call_substate_avp_retry);
+ }
+
+ if (isNotEnabled(Connection.SUBSTATE_ALL, callSubstate)) {
+ callSubstateChangedText = resources.getString(R.string.call_substate_call_resumed);
+ }
+
+ if (!callSubstateChangedText.isEmpty()) {
+ String callSubstateLabelText = resources.getString(R.string.call_substate_label);
+ Toast.makeText(context, callSubstateLabelText + callSubstateChangedText,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ boolean isEnabled(int mask, int callSubstate) {
+ return (mask & callSubstate) == mask;
+ }
+
+ boolean isNotEnabled(int mask, int callSubstate) {
+ return (mask & callSubstate) == 0;
+ }
+
+ /**
* Cleans up the video telephony surfaces. Used when the presenter indicates a change to an
* audio-only state. Since the surfaces are static, it is important to ensure they are cleaned
* up promptly.
*/
@Override
public void cleanupSurfaces() {
+ Log.d(this, "cleanupSurfaces");
if (sDisplaySurface != null) {
sDisplaySurface.setDoneWithSurface();
sDisplaySurface = null;
@@ -468,8 +688,19 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
sVideoSurfacesInUse = false;
}
+ private void onPresenterChanged(VideoCallPresenter presenter) {
+ Log.d(this, "onPresenterChanged: Presenter=" + presenter);
+ if (sDisplaySurface != null) {
+ sDisplaySurface.resetPresenter(presenter);;
+ }
+ if (sPreviewSurface != null) {
+ sPreviewSurface.resetPresenter(presenter);
+ }
+ }
+
@Override
public boolean isActivityRestart() {
+ Log.d(this, "isActivityRestart " + mIsActivityRestart);
return mIsActivityRestart;
}
@@ -478,7 +709,9 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
*/
@Override
public boolean isDisplayVideoSurfaceCreated() {
- return sDisplaySurface != null && sDisplaySurface.getSurface() != null;
+ boolean ret = sDisplaySurface != null && sDisplaySurface.getSurface() != null;
+ Log.d(this, " isDisplayVideoSurfaceCreated returns " + ret);
+ return ret;
}
/**
@@ -486,7 +719,9 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
*/
@Override
public boolean isPreviewVideoSurfaceCreated() {
- return sPreviewSurface != null && sPreviewSurface.getSurface() != null;
+ boolean ret = sPreviewSurface != null && sPreviewSurface.getSurface() != null;
+ Log.d(this, " isPreviewVideoSurfaceCreated returns " + ret);
+ return ret;
}
/**
@@ -516,6 +751,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
*/
@Override
public void setPreviewSize(int width, int height) {
+ Log.d(this, "setPreviewSize: width=" + width + " height=" + height);
if (sPreviewSurface != null) {
TextureView preview = sPreviewSurface.getTextureView();
@@ -528,15 +764,123 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
params.height = height;
preview.setLayoutParams(params);
+ int rotation = InCallPresenter.toRotationAngle(getCurrentRotation());
+ int rotationAngle = 360 - rotation;
+ preview.setRotation(rotationAngle);
+ Log.d(this, "setPreviewSize: rotation=" + rotation +
+ " rotationAngle=" + rotationAngle);
+
+ }
+ }
+
+ @Override
+ public void setPreviewSurfaceSize(int width, int height) {
+ final boolean isPreviewSurfaceAvailable = sPreviewSurface != null;
+ Log.d(this, "setPreviewSurfaceSize: width=" + width + " height=" + height +
+ " isPreviewSurfaceAvailable=" + isPreviewSurfaceAvailable);
+ if (isPreviewSurfaceAvailable) {
sPreviewSurface.setSurfaceDimensions(width, height);
}
}
/**
+ * returns UI's current orientation.
+ */
+ @Override
+ public int getCurrentRotation() {
+ try {
+ return getActivity().getWindowManager().getDefaultDisplay().getRotation();
+ } catch (Exception e) {
+ Log.e(this, "getCurrentRotation: Retrieving current rotation failed. Ex=" + e);
+ }
+ return ORIENTATION_UNKNOWN;
+ }
+
+ /**
+ * Changes the dimensions of the display video surface. Called when the dimensions change due to
+ * a peer resolution update
+ *
+ * @param width The new width.
+ * @param height The new height.
+ */
+ @Override
+ public void setDisplayVideoSize(int width, int height) {
+ Log.d(this, "setDisplayVideoSize: width=" + width + " height=" + height);
+ if (sDisplaySurface != null) {
+ TextureView displayVideo = sDisplaySurface.getTextureView();
+ if (displayVideo == null) {
+ Log.e(this, "Display Video texture view is null. Bail out");
+ return;
+ }
+ sDisplaySize = new Point(width, height);
+ setSurfaceSizeAndTranslation(displayVideo, sDisplaySize);
+ } else {
+ Log.e(this, "Display Video Surface is null. Bail out");
+ }
+ }
+
+ /**
+ * Sets the call's data usage value
+ *
+ * @param context the current context
+ * @param dataUsage the data usage value
+ */
+ @Override
+ public void setCallDataUsage(Context context, long dataUsage) {
+ Log.d(this, "setDataUsage: dataUsage = " + dataUsage);
+ Toast.makeText(context, "dataUsage=" + dataUsage, Toast.LENGTH_LONG).show();
+ }
+
+ private int fromCallSessionEvent(int event) {
+ switch (event) {
+ case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE:
+ return R.string.player_stopped;
+ case Connection.VideoProvider.SESSION_EVENT_RX_RESUME:
+ return R.string.player_started;
+ case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
+ return R.string.camera_not_ready;
+ case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY:
+ return R.string.camera_ready;
+ default:
+ return R.string.unknown_call_session_event;
+ }
+ }
+
+ /**
+ * Sets the call's data usage value
+ *
+ * @param context the current context
+ * @param event the call session event
+ */
+ @Override
+ public void displayCallSessionEvent(int event) {
+ Log.d(this, "displayCallSessionEvent: event = " + event);
+ Context context = getActivity();
+ String msg = context.getResources().getString(fromCallSessionEvent(event));
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+ }
+
+ /**
+ * Determines the size of the device screen.
+ *
+ * @return {@link Point} specifying the width and height of the screen.
+ */
+ @Override
+ public Point getScreenSize() {
+ // Get current screen size.
+ Display display = getActivity().getWindowManager().getDefaultDisplay();
+ Point size = new Point();
+ display.getSize(size);
+
+ return size;
+ }
+
+ /**
* Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary,
* and creates {@link VideoCallSurface} instances to track the surfaces.
*/
private void inflateVideoCallViews() {
+ Log.d(this, "inflateVideoCallViews");
if (mVideoViews == null ) {
mVideoViews = mVideoViewsStub.inflate();
}
@@ -544,16 +888,20 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
if (mVideoViews != null) {
TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo);
- Point screenSize = getScreenSize();
+ 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;
setSurfaceSizeAndTranslation(displaySurface, screenSize);
if (!sVideoSurfacesInUse) {
// Where the video surfaces are not already in use (first time creating them),
// setup new VideoCallSurface instances to track them.
- sDisplaySurface = new VideoCallSurface(SURFACE_DISPLAY,
+ Log.d(this, " inflateVideoCallViews screenSize" + screenSize);
+
+ sDisplaySurface = new VideoCallSurface(getPresenter(), SURFACE_DISPLAY,
(TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x,
screenSize.y);
- sPreviewSurface = new VideoCallSurface(SURFACE_PREVIEW,
+ sPreviewSurface = new VideoCallSurface(getPresenter(), SURFACE_PREVIEW,
(TextureView) mVideoViews.findViewById(R.id.previewVideo));
sVideoSurfacesInUse = true;
} else {
@@ -580,28 +928,15 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
params.width = size.x;
params.height = size.y;
textureView.setLayoutParams(params);
+ Log.d(this, "setSurfaceSizeAndTranslation: Size=" + size + "IsLayoutComplete=" +
+ mIsLayoutComplete + "IsLandscape=" + mIsLandscape);
// It is only possible to center the display view if layout of the views has completed.
// It is only after layout is complete that the dimensions of the Call Card has been
// established, which is a prerequisite to centering the view.
// Incoming video calls will center the view
- if (mIsLayoutComplete && ((mIsLandscape && textureView.getTranslationX() == 0) || (
- !mIsLandscape && textureView.getTranslationY() == 0))) {
+ if (mIsLayoutComplete) {
centerDisplayView(textureView);
}
}
-
- /**
- * Determines the size of the device screen.
- *
- * @return {@link Point} specifying the width and height of the screen.
- */
- private Point getScreenSize() {
- // Get current screen size.
- Display display = getActivity().getWindowManager().getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
-
- return size;
- }
}
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
index 38d7c9556..486a38da3 100644
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
@@ -18,10 +18,14 @@ package com.android.incallui;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.os.Handler;
import android.telecom.AudioState;
import android.telecom.CameraCapabilities;
+import android.telecom.Connection;
+import android.telecom.Connection.VideoProvider;
import android.telecom.InCallService.VideoCall;
+import android.telecom.VideoProfile;
import android.view.Surface;
import com.android.contacts.common.CallUtil;
@@ -31,10 +35,14 @@ import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
import com.android.incallui.InCallVideoCallListenerNotifier.SurfaceChangeListener;
import com.android.incallui.InCallVideoCallListenerNotifier.VideoEventListener;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyProperties;
import com.google.common.base.Preconditions;
import java.util.Objects;
+import android.os.SystemProperties;
+
/**
* Logic related to the {@link VideoCallFragment} and for managing changes to the video calling
* surfaces based on other user interface events and incoming events from the
@@ -61,6 +69,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
IncomingCallListener, InCallOrientationListener, InCallStateListener,
InCallDetailsListener, SurfaceChangeListener, VideoEventListener,
InCallVideoCallListenerNotifier.SessionModificationListener {
+ public static final String TAG = "VideoCallPresenter";
/**
* Determines the device orientation (portrait/lanscape).
@@ -122,7 +131,12 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
/**
* Determines if the current UI state represents a video call.
*/
- private boolean mIsVideoCall;
+ private int mCurrentVideoState;
+
+ /**
+ * Call's current state
+ */
+ private int mCurrentCallState = Call.State.INVALID;
/**
* Determines the device orientation (portrait/lanscape).
@@ -142,7 +156,15 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
/**
* Saves the audio mode which was selected prior to going into a video call.
*/
- private int mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
+ private static int sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
+
+ private static boolean mIsVideoMode = false;
+
+ /**
+ * Stores the current call substate.
+ */
+ private int mCurrentCallSubstate;
+
/** Handler which resets request state to NO_REQUEST after an interval. */
private Handler mSessionModificationResetHandler;
@@ -168,9 +190,11 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
@Override
public void onUiReady(VideoCallUi ui) {
super.onUiReady(ui);
+ Log.d(this, "onUiReady:");
// Register for call state changes last
InCallPresenter.getInstance().addListener(this);
+ InCallPresenter.getInstance().addDetailsListener(this);
InCallPresenter.getInstance().addIncomingCallListener(this);
InCallPresenter.getInstance().addOrientationListener(this);
// To get updates of video call details changes
@@ -180,7 +204,8 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
InCallVideoCallListenerNotifier.getInstance().addSurfaceChangeListener(this);
InCallVideoCallListenerNotifier.getInstance().addVideoEventListener(this);
InCallVideoCallListenerNotifier.getInstance().addSessionModificationListener(this);
- mIsVideoCall = false;
+ mCurrentVideoState = VideoProfile.VideoState.AUDIO_ONLY;
+ mCurrentCallState = Call.State.INVALID;
}
/**
@@ -191,41 +216,44 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
@Override
public void onUiUnready(VideoCallUi ui) {
super.onUiUnready(ui);
+ Log.d(this, "onUiUnready:");
InCallPresenter.getInstance().removeListener(this);
+ InCallPresenter.getInstance().removeDetailsListener(this);
InCallPresenter.getInstance().removeIncomingCallListener(this);
InCallPresenter.getInstance().removeOrientationListener(this);
+
InCallVideoCallListenerNotifier.getInstance().removeSurfaceChangeListener(this);
InCallVideoCallListenerNotifier.getInstance().removeVideoEventListener(this);
InCallVideoCallListenerNotifier.getInstance().removeSessionModificationListener(this);
}
/**
- * @return The {@link VideoCall}.
- */
- private VideoCall getVideoCall() {
- return mVideoCall;
- }
-
- /**
* Handles the creation of a surface in the {@link VideoCallFragment}.
*
* @param surface The surface which was created.
*/
public void onSurfaceCreated(int surface) {
- final VideoCallUi ui = getUi();
+ Log.d(this, "onSurfaceCreated surface=" + surface + " mVideoCall=" + mVideoCall);
+ Log.d(this, "onSurfaceCreated PreviewSurfaceState=" + mPreviewSurfaceState);
+ Log.d(this, "onSurfaceCreated presenter=" + this);
+ final VideoCallUi ui = getUi();
if (ui == null || mVideoCall == null) {
+ Log.w(this, "onSurfaceCreated: Error bad state VideoCallUi=" + ui + " mVideoCall="
+ + mVideoCall);
return;
}
// If the preview surface has just been created and we have already received camera
// capabilities, but not yet set the surface, we will set the surface now.
- if (surface == VideoCallFragment.SURFACE_PREVIEW &&
- mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
-
- mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
- mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface());
+ if (surface == VideoCallFragment.SURFACE_PREVIEW ) {
+ if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
+ mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
+ mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface());
+ } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()){
+ enableCamera(mVideoCall, true);
+ }
} else if (surface == VideoCallFragment.SURFACE_DISPLAY) {
mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface());
}
@@ -245,12 +273,15 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
/**
* Handles the destruction of a surface in the {@link VideoCallFragment}.
+ * Note: The surface is being released, that is, it is no longer valid.
*
* @param surface The surface which was destroyed.
*/
- public void onSurfaceDestroyed(int surface) {
- final VideoCallUi ui = getUi();
- if (ui == null || mVideoCall == null) {
+ public void onSurfaceReleased(int surface) {
+ Log.d(this, "onSurfaceReleased: mSurfaceId=" + surface);
+ if ( mVideoCall == null) {
+ Log.w(this, "onSurfaceReleased: VideoCall is null. mSurfaceId=" +
+ surface);
return;
}
@@ -258,11 +289,43 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
mVideoCall.setDisplaySurface(null);
} else if (surface == VideoCallFragment.SURFACE_PREVIEW) {
mVideoCall.setPreviewSurface(null);
- // Also disable camera as preview is closed
- mVideoCall.setCamera(null);
+ enableCamera(mVideoCall, false);
+ }
+ }
+
+ /**
+ * Called by {@link VideoCallFragment} when the surface is detached from UI (TextureView).
+ * Note: The surface will be cached by {@link VideoCallFragment}, so we don't immediately
+ * null out incoming video surface.
+ * @see VideoCallPresenter#onSurfaceReleased(int)
+ *
+ * @param surface The surface which was detached.
+ */
+ public void onSurfaceDestroyed(int surface) {
+ Log.d(this, "onSurfaceDestroyed: mSurfaceId=" + surface);
+ if (mVideoCall == null) {
+ return;
+ }
+
+ final boolean isChangingConfigurations =
+ InCallPresenter.getInstance().isChangingConfigurations();
+ Log.d(this, "onSurfaceDestroyed: isChangingConfigurations=" + isChangingConfigurations);
+
+ if (surface == VideoCallFragment.SURFACE_PREVIEW) {
+ if (!isChangingConfigurations) {
+ enableCamera(mVideoCall, false);
+ } else {
+ Log.w(this, "onSurfaceDestroyed: Activity is being destroyed due "
+ + "to configuration changes. Not closing the camera.");
+ }
}
}
+ private void toggleFullScreen() {
+ mIsFullScreen = !mIsFullScreen;
+ InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen);
+ }
+
/**
* Handles clicks on the video surfaces by toggling full screen state.
* Informs the {@link InCallPresenter} of the change so that it can inform the
@@ -271,8 +334,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
* @param surfaceId The video surface receiving the click.
*/
public void onSurfaceClick(int surfaceId) {
- mIsFullScreen = !mIsFullScreen;
- InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen);
+ toggleFullScreen();
}
@@ -298,41 +360,152 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
@Override
public void onStateChange(InCallPresenter.InCallState oldState,
InCallPresenter.InCallState newState, CallList callList) {
- // Bail if video calling is disabled for the device.
- if (!CallUtil.isVideoEnabled(mContext)) {
- return;
- }
+ Log.d(this, "onStateChange oldState" + oldState + " newState=" + newState +
+ " isVideoMode=" + isVideoMode());
if (newState == InCallPresenter.InCallState.NO_CALLS) {
- exitVideoMode();
+ updateAudioMode(false);
+
+ if (isVideoMode()) {
+ exitVideoMode();
+ }
+
+ cleanupSurfaces();
}
// Determine the primary active call).
Call primary = null;
if (newState == InCallPresenter.InCallState.INCOMING) {
- primary = callList.getIncomingCall();
+ // We don't want to replace active video call (primary call)
+ // with a waiting call, since user may choose to ignore/decline the waiting call and
+ // this should have no impact on current active video call, that is, we should not
+ // change the camera or UI unless the waiting VT call becomes active.
+ primary = callList.getActiveCall();
+ if (!CallUtils.isActiveVideoCall(primary)) {
+ primary = callList.getIncomingCall();
+ }
} else if (newState == InCallPresenter.InCallState.OUTGOING) {
primary = callList.getOutgoingCall();
+ } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
+ primary = callList.getPendingOutgoingCall();
} else if (newState == InCallPresenter.InCallState.INCALL) {
primary = callList.getActiveCall();
}
final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary);
+ Log.d(this, "onStateChange primaryChanged=" + primaryChanged);
+ Log.d(this, "onStateChange primary= " + primary);
+ Log.d(this, "onStateChange mPrimaryCall = " + mPrimaryCall);
if (primaryChanged) {
- mPrimaryCall = primary;
-
- if (primary != null) {
- checkForVideoCallChange();
- mIsVideoCall = mPrimaryCall.isVideoCall(mContext);
- if (mIsVideoCall) {
- enterVideoMode();
- } else {
- exitVideoMode();
- }
- } else if (primary == null) {
- // If no primary call, ensure we exit video state and clean up the video surfaces.
- exitVideoMode();
+ onPrimaryCallChanged(primary);
+ } else if(mPrimaryCall!=null) {
+ updateVideoCall(primary);
+ }
+ updateCallCache(primary);
+ }
+
+ private void checkForVideoStateChange(Call call) {
+ final boolean isVideoCall = CallUtils.isVideoCall(call);
+ final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState();
+
+ Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall
+ + " hasVideoStateChanged=" +
+ hasVideoStateChanged + " isVideoMode=" + isVideoMode());
+
+ if (!hasVideoStateChanged) { return;}
+
+ updateCameraSelection(call);
+
+ if (isVideoCall) {
+ enterVideoMode(call.getVideoCall(), call.getVideoState());
+ } else if (isVideoMode()) {
+ exitVideoMode();
+ }
+ }
+
+ private void checkForCallStateChange(Call call) {
+ final boolean isVideoCall = CallUtils.isVideoCall(call);
+ final boolean hasCallStateChanged = mCurrentCallState != call.getState();
+
+ Log.d(this, "checkForCallStateChange: isVideoCall= " + isVideoCall
+ + " hasCallStateChanged=" +
+ hasCallStateChanged + " isVideoMode=" + isVideoMode());
+
+ if (!hasCallStateChanged) { return; }
+
+ final InCallCameraManager cameraManager = InCallPresenter.getInstance().
+ getInCallCameraManager();
+
+ String prevCameraId = cameraManager.getActiveCameraId();
+
+ updateCameraSelection(call);
+
+ String newCameraId = cameraManager.getActiveCameraId();
+
+ if (!Objects.equals(prevCameraId, newCameraId) && CallUtils.isActiveVideoCall(call)) {
+ enableCamera(call.getVideoCall(), true);
+ }
+ }
+
+ private void checkForCallSubstateChange(Call call) {
+ if (mCurrentCallSubstate != call.getCallSubstate()) {
+ VideoCallUi ui = getUi();
+ if (ui == null) {
+ Log.e(this, "Error VideoCallUi is null. Return.");
+ return;
}
+ mCurrentCallSubstate = call.getCallSubstate();
+ // Display a call substate changed message on UI.
+ ui.showCallSubstateChanged(mCurrentCallSubstate);
+ }
+ }
+
+ private void cleanupSurfaces() {
+ final VideoCallUi ui = getUi();
+ if (ui == null) {
+ Log.w(this, "cleanupSurfaces");
+ return;
+ }
+ ui.cleanupSurfaces();
+ }
+
+ private void onPrimaryCallChanged(Call newPrimaryCall) {
+ final boolean isVideoCall = CallUtils.isVideoCall(newPrimaryCall);
+ final boolean isVideoMode = isVideoMode();
+
+ Log.d(this, "onPrimaryCallChanged: isVideoCall=" + isVideoCall + " isVideoMode="
+ + isVideoMode);
+
+ if (!isVideoCall && isVideoMode) {
+ // Terminate video mode if new primary call is not a video call
+ // and we are currently in video mode.
+ Log.d(this, "onPrimaryCallChanged: Exiting video mode...");
+ exitVideoMode();
+ } else if (isVideoCall) {
+ Log.d(this, "onPrimaryCallChanged: Entering video mode...");
+
+ updateCameraSelection(newPrimaryCall);
+ enterVideoMode(newPrimaryCall.getVideoCall(), newPrimaryCall.getVideoState());
+ }
+ }
+
+ private boolean isVideoMode() {
+ return mIsVideoMode;
+ }
+
+ private void updateCallCache(Call call) {
+ if (call == null) {
+ mCurrentVideoState = VideoProfile.VideoState.AUDIO_ONLY;
+ mCurrentCallSubstate = Connection.SUBSTATE_NONE;
+ mCurrentCallState = Call.State.INVALID;
+ mVideoCall = null;
+ mPrimaryCall = null;
+ } else {
+ mCurrentVideoState = call.getVideoState();
+ mCurrentCallSubstate = call.getCallSubstate();
+ mVideoCall = call.getVideoCall();
+ mCurrentCallState = call.getState();
+ mPrimaryCall = call;
}
}
@@ -345,115 +518,214 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
*/
@Override
public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+ Log.d(this, " onDetailsChanged call=" + call + " details=" + details + " mPrimaryCall="
+ + mPrimaryCall);
// If the details change is not for the currently active call no update is required.
if (!call.equals(mPrimaryCall)) {
+ Log.d(this," onDetailsChanged: Details not for current active call so returning. ");
return;
}
- checkForVideoStateChange();
+ updateVideoCall(call);
+ checkForCallSubstateChange(call);
+
+ updateCallCache(call);
}
- /**
- * Checks for a change to the video call and changes it if required.
- */
- private void checkForVideoCallChange() {
- VideoCall videoCall = mPrimaryCall.getTelecommCall().getVideoCall();
- if (!Objects.equals(videoCall, mVideoCall)) {
- changeVideoCall(videoCall);
- }
+ private void updateVideoCall(Call call) {
+ checkForVideoCallChange(call);
+ checkForVideoStateChange(call);
+ checkForCallStateChange(call);
}
/**
- * Checks to see if the current video state has changed and updates the UI if required.
+ * Checks for a change to the video call and changes it if required.
*/
- private void checkForVideoStateChange() {
- boolean newVideoState = mPrimaryCall.isVideoCall(mContext);
-
- // Check if video state changed
- if (mIsVideoCall != newVideoState) {
- mIsVideoCall = newVideoState;
-
- if (mIsVideoCall) {
- enterVideoMode();
- } else {
- exitVideoMode();
- }
+ private void checkForVideoCallChange(Call call) {
+ final VideoCall videoCall = call.getTelecommCall().getVideoCall();
+ Log.d(this, "checkForVideoCallChange: videoCall=" + videoCall + " mVideoCall="
+ + mVideoCall);
+ if (!Objects.equals(videoCall, mVideoCall)) {
+ changeVideoCall(call);
}
}
/**
- * Handles a change to the video call. Sets the surfaces on the previous call to null and sets
+ * Handles a change to the video call. Sets the surfaces on the previous call to null and sets
* the surfaces on the new video call accordingly.
*
* @param videoCall The new video call.
*/
- private void changeVideoCall(VideoCall videoCall) {
+ private void changeVideoCall(Call call) {
+ final VideoCall videoCall = call.getTelecommCall().getVideoCall();
+ Log.d(this, "changeVideoCall to videoCall=" + videoCall + " mVideoCall=" + mVideoCall);
// Null out the surfaces on the previous video call.
if (mVideoCall != null) {
- mVideoCall.setDisplaySurface(null);
- mVideoCall.setPreviewSurface(null);
+ // Log.d(this, "Null out the surfaces on the previous video call.");
+ // mVideoCall.setDisplaySurface(null);
+ // mVideoCall.setPreviewSurface(null);
}
+ final boolean hasChanged = mVideoCall == null && videoCall != null;
+
mVideoCall = videoCall;
+ if (mVideoCall == null || call == null) {
+ Log.d(this, "Video call or primary call is null. Return");
+ return;
+ }
+
+ if (CallUtils.isVideoCall(call) && hasChanged) {
+ enterVideoMode(call.getVideoCall(), call.getVideoState());
+ }
+ }
+
+ private static boolean isCameraRequired(int videoState) {
+ return VideoProfile.VideoState.isBidirectional(videoState) ||
+ VideoProfile.VideoState.isTransmissionEnabled(videoState);
+ }
+
+ private boolean isCameraRequired() {
+ return mPrimaryCall != null ? isCameraRequired(mPrimaryCall.getVideoState()) : false;
}
/**
* Enters video mode by showing the video surfaces and making other adjustments (eg. audio).
* TODO(vt): Need to adjust size and orientation of preview surface here.
*/
- private void enterVideoMode() {
+ private void enterVideoMode(VideoCall videoCall, int newVideoState) {
+ Log.d(this, "enterVideoMode videoCall= " + videoCall + " videoState: " + newVideoState);
VideoCallUi ui = getUi();
if (ui == null) {
+ Log.e(this, "Error VideoCallUi is null so returning");
return;
}
- ui.showVideoUi(true);
+ showVideoUi(newVideoState);
InCallPresenter.getInstance().setInCallAllowsOrientationChange(true);
// Communicate the current camera to telephony and make a request for the camera
// capabilities.
- if (mVideoCall != null) {
- // Do not reset the surfaces if we just restarted the activity due to an orientation
- // change.
- if (ui.isActivityRestart()) {
- return;
+ if (videoCall != null) {
+ if (ui.isDisplayVideoSurfaceCreated()) {
+ Log.d(this, "Calling setDisplaySurface with " + ui.getDisplayVideoSurface());
+ videoCall.setDisplaySurface(ui.getDisplayVideoSurface());
}
- mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
+ final int rotation = ui.getCurrentRotation();
+ if (rotation != VideoCallFragment.ORIENTATION_UNKNOWN) {
+ videoCall.setDeviceOrientation(InCallPresenter.toRotationAngle(rotation));
+ }
+
+ enableCamera(videoCall, isCameraRequired(newVideoState));
+ }
+ mCurrentVideoState = newVideoState;
+ updateAudioMode(true);
+
+ mIsVideoMode = true;
+ }
+
+ //TODO: Move this into Telecom. InCallUI should not be this close to audio functionality.
+ private void updateAudioMode(boolean enableSpeaker) {
+ if (!isSpeakerEnabledForVideoCalls()) {
+ Log.d(this, "Speaker is disabled. Can't update audio mode");
+ return;
+ }
+
+ final TelecomAdapter telecomAdapter = TelecomAdapter.getInstance();
+ final boolean isPrevAudioModeValid =
+ sPrevVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID;
+
+ Log.d(this, "Is previous audio mode valid = " + isPrevAudioModeValid + " enableSpeaker is "
+ + enableSpeaker);
+
+ // Set audio mode to previous mode if enableSpeaker is false.
+ if (isPrevAudioModeValid && !enableSpeaker) {
+ telecomAdapter.setAudioRoute(sPrevVideoAudioMode);
+ sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
+ return;
+ }
+
+ int currentAudioMode = AudioModeProvider.getInstance().getAudioMode();
+
+ // Set audio mode to speaker if enableSpeaker is true and bluetooth or headset are not
+ // connected and it's a video call.
+ if (!isAudioRouteEnabled(currentAudioMode,
+ AudioState.ROUTE_BLUETOOTH | AudioState.ROUTE_WIRED_HEADSET) &&
+ !isPrevAudioModeValid && enableSpeaker && CallUtils.isVideoCall(mPrimaryCall)) {
+ sPrevVideoAudioMode = currentAudioMode;
+
+ Log.d(this, "Routing audio to speaker");
+ telecomAdapter.setAudioRoute(AudioState.ROUTE_SPEAKER);
+ }
+ }
+
+ private static boolean isSpeakerEnabledForVideoCalls() {
+ return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
+ PhoneConstants.AUDIO_OUTPUT_DEFAULT) ==
+ PhoneConstants.AUDIO_OUTPUT_ENABLE_SPEAKER);
+ }
+
+ private void enableCamera(VideoCall videoCall, boolean isCameraRequired) {
+ Log.d(this, "enableCamera: VideoCall=" + videoCall + " enabling=" + isCameraRequired);
+ if (videoCall == null) {
+ Log.w(this, "enableCamera: VideoCall is null.");
+ return;
+ }
+
+ if (isCameraRequired) {
InCallCameraManager cameraManager = InCallPresenter.getInstance().
getInCallCameraManager();
- mVideoCall.setCamera(cameraManager.getActiveCameraId());
- mVideoCall.requestCameraCapabilities();
+ videoCall.setCamera(cameraManager.getActiveCameraId());
+ mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
- if (ui.isDisplayVideoSurfaceCreated()) {
- mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface());
- }
+ videoCall.requestCameraCapabilities();
+ } else {
+ mPreviewSurfaceState = PreviewSurfaceState.NONE;
+ videoCall.setCamera(null);
}
-
- mPreVideoAudioMode = AudioModeProvider.getInstance().getAudioMode();
- TelecomAdapter.getInstance().setAudioRoute(AudioState.ROUTE_SPEAKER);
}
/**
- * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio).
+ * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio).
*/
private void exitVideoMode() {
+ Log.d(this, "exitVideoMode");
+
+ InCallPresenter.getInstance().setInCallAllowsOrientationChange(false);
+
+ showVideoUi(VideoProfile.VideoState.AUDIO_ONLY);
+ enableCamera(mVideoCall, false);
+
+ Log.d(this, "exitVideoMode mIsFullScreen: " + mIsFullScreen);
+ if (mIsFullScreen) {
+ toggleFullScreen();
+ }
+
+ mIsVideoMode = false;
+ }
+
+ /**
+ * Show video Ui depends on video state.
+ */
+ private void showVideoUi(int videoState) {
VideoCallUi ui = getUi();
if (ui == null) {
+ Log.e(this, "showVideoUi, VideoCallUi is null returning");
return;
}
- InCallPresenter.getInstance().setInCallAllowsOrientationChange(false);
- ui.showVideoUi(false);
- if (mVideoCall != null) {
- // Also disable camera otherwise it will be already in use for next upgrade
- mVideoCall.setCamera(null);
+ if (VideoProfile.VideoState.isBidirectional(videoState)) {
+ ui.showVideoViews(true, true);
+ } else if (VideoProfile.VideoState.isTransmissionEnabled(videoState)) {
+ ui.showVideoViews(true, false);
+ } else if (VideoProfile.VideoState.isReceptionEnabled(videoState)) {
+ ui.showVideoViews(false, true);
+ } else {
+ ui.hideVideoUi();
}
- if (mPreVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID) {
- TelecomAdapter.getInstance().setAudioRoute(mPreVideoAudioMode);
- mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
- }
+ InCallPresenter.getInstance().enableScreenTimeout(
+ VideoProfile.VideoState.isAudioOnly(videoState));
}
/**
@@ -481,11 +753,43 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
*/
@Override
public void onUpdatePeerDimensions(Call call, int width, int height) {
+ Log.d(this, "onUpdatePeerDimensions: width= " + width + " height= " + height);
+ VideoCallUi ui = getUi();
+ if (ui == null) {
+ Log.e(this, "VideoCallUi is null. Bail out");
+ return;
+ }
if (!call.equals(mPrimaryCall)) {
+ Log.e(this, "Current call is not equal to primary call. Bail out");
return;
}
- // TODO(vt): Change display surface aspect ratio.
+ // Change size of display surface to match the peer aspect ratio
+ if (width > 0 && height > 0) {
+ setDisplayVideoSize(width, height);
+ }
+ }
+
+ /**
+ * Handles any video quality changes in the call.
+ *
+ * @param call The call which experienced a video quality change.
+ * @param videoQuality The new video call quality.
+ */
+ @Override
+ public void onVideoQualityChanged(Call call, int videoQuality) {
+ if (!call.equals(mPrimaryCall)) {
+ return;
+ }
+
+ VideoCallUi ui = getUi();
+ if (ui == null) {
+ Log.e(this, "Error VideoCallUi is null. Return.");
+ return;
+ }
+
+ // Display a video quality changed message on UI.
+ ui.showVideoQualityChanged(videoQuality);
}
/**
@@ -498,16 +802,21 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
*/
@Override
public void onCameraDimensionsChange(Call call, int width, int height) {
+ Log.d(this, "onCameraDimensionsChange call=" + call + " width=" + width + " height="
+ + height);
VideoCallUi ui = getUi();
if (ui == null) {
+ Log.e(this, "onCameraDimensionsChange ui is null");
return;
}
if (!call.equals(mPrimaryCall)) {
+ Log.e(this, "Call is not primary call");
return;
}
mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
+ ui.setPreviewSurfaceSize(width, height);
// Configure the preview surface to the correct aspect ratio.
float aspectRatio = 1.0f;
@@ -525,45 +834,105 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
}
/**
- * Handles hanges to the device orientation.
+ * Called when call session event is raised.
+ *
+ * @param event The call session event.
+ */
+ @Override
+ public void onCallSessionEvent(int event) {
+ Log.d(this, "onCallSessionEvent event =" + event);
+ VideoCallUi ui = getUi();
+ if (ui == null) {
+ Log.e(this, "onCallSessionEvent: VideoCallUi is null");
+ return;
+ }
+ ui.displayCallSessionEvent(event);
+ }
+
+ /**
+ * Handles a change to the call data usage
+ *
+ * @param dataUsage call data usage value
+ */
+ @Override
+ public void onCallDataUsageChange(long dataUsage) {
+ Log.d(this, "onCallDataUsageChange dataUsage=" + dataUsage);
+ VideoCallUi ui = getUi();
+ if (ui == null) {
+ Log.e(this, "onCallDataUsageChange: VideoCallUi is null");
+ return;
+ }
+ ui.setCallDataUsage(mContext, dataUsage);
+ }
+
+ /**
+ * Handles changes to the device orientation.
* See: {@link Configuration.ORIENTATION_LANDSCAPE}, {@link Configuration.ORIENTATION_PORTRAIT}
* @param orientation The device orientation.
*/
@Override
public void onDeviceOrientationChanged(int orientation) {
+ Log.d(this, "onDeviceOrientationChanged: orientation=" + orientation);
mDeviceOrientation = orientation;
}
@Override
- public void onUpgradeToVideoRequest(Call call) {
- mPrimaryCall.setSessionModificationState(
- Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST);
+ public void onUpgradeToVideoRequest(Call call, int videoState) {
+ Log.d(this, "onUpgradeToVideoRequest call = " + call + " new video state = " + videoState);
+ if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) {
+ Log.w(this, "UpgradeToVideoRequest received for non-primary call");
+ }
+
+ if (call == null) {
+ return;
+ }
+
+ call.setSessionModificationTo(videoState);
}
@Override
public void onUpgradeToVideoSuccess(Call call) {
+ Log.d(this, "onUpgradeToVideoSuccess call=" + call);
if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) {
+ Log.w(this, "UpgradeToVideoSuccess received for non-primary call");
+ }
+
+ if (call == null) {
return;
}
- mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
+ call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
}
@Override
- public void onUpgradeToVideoFail(Call call) {
+ public void onUpgradeToVideoFail(int status, Call call) {
+ Log.d(this, "onUpgradeToVideoFail call=" + call);
if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) {
- return;
+ Log.w(this, "UpgradeToVideoFail received for non-primary call");
}
- call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED);
+ if (call == null) {
+ return;
+ }
- // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval.
- mSessionModificationResetHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
- }
- }, SESSION_MODIFICATION_RESET_DELAY_MS);
+ if (status == VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT) {
+ call.setSessionModificationState(
+ Call.SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT);
+ } else {
+ call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED);
+
+ final Call modifyCall = call;
+ // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval.
+ mSessionModificationResetHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (modifyCall != null) {
+ modifyCall
+ .setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
+ }
+ }
+ }, SESSION_MODIFICATION_RESET_DELAY_MS);
+ }
}
@Override
@@ -599,16 +968,140 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi
}
/**
+ * Sets the display video surface size based on peer width and height
+ *
+ * @param width peer width
+ * @param height peer height
+ */
+
+ private void setDisplayVideoSize(int width, int height) {
+ Log.d(this, "setDisplayVideoSize:Received peer width=" + width + " peer height=" + height);
+ VideoCallUi ui = getUi();
+ if (ui == null) {
+ return;
+ }
+
+ // Get current display size
+ Point size = ui.getScreenSize();
+ Log.d("VideoCallPresenter", "setDisplayVideoSize: windowmgr width=" + size.x
+ + " windowmgr height=" + size.y);
+ if (size.y * width > size.x * height) {
+ // current display height is too much. Correct it
+ size.y = (int) (size.x * height / width);
+ } else if (size.y * width < size.x * height) {
+ // current display width is too much. Correct it
+ size.x = (int) (size.y * width / height);
+ }
+ ui.setDisplayVideoSize(size.x, size.y);
+ }
+
+ private static boolean isAudioRouteEnabled(int audioRoute, int audioRouteMask) {
+ return ((audioRoute & audioRouteMask) != 0);
+ }
+
+ private static void updateCameraSelection(Call call) {
+ Log.d(TAG, "updateCameraSelection: call=" + call);
+ Log.d(TAG, "updateCameraSelection: call=" + toSimpleString(call));
+
+ final Call activeCall = CallList.getInstance().getActiveCall();
+ int cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+
+ // this function should never be called with null call object, however if it happens we
+ // should handle it gracefully.
+ if (call == null) {
+ cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+ com.android.incallui.Log.e(TAG, "updateCameraSelection: Call object is null."
+ + " Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
+ }
+
+ // Clear camera direction if this is not a video call.
+ else if (CallUtils.isAudioCall(call)) {
+ cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+ call.getVideoSettings().setCameraDir(cameraDir);
+ }
+
+ // If this is a waiting video call, default to active call's camera,
+ // since we don't want to change the current camera for waiting call
+ // without user's permission.
+ else if (CallUtils.isVideoCall(activeCall) && CallUtils.isIncomingVideoCall(call)) {
+ cameraDir = activeCall.getVideoSettings().getCameraDir();
+ }
+
+ // Infer the camera direction from the video state and store it,
+ // if this is an outgoing video call.
+ else if (CallUtils.isOutgoingVideoCall(call) && !isCameraDirectionSet(call) ) {
+ cameraDir = toCameraDirection(call.getVideoState());
+ call.getVideoSettings().setCameraDir(cameraDir);
+ }
+
+ // Use the stored camera dir if this is an outgoing video call for which camera direction
+ // is set.
+ else if (CallUtils.isOutgoingVideoCall(call)) {
+ cameraDir = call.getVideoSettings().getCameraDir();
+ }
+
+ // Infer the camera direction from the video state and store it,
+ // if this is an active video call and camera direction is not set.
+ else if (CallUtils.isActiveVideoCall(call) && !isCameraDirectionSet(call)) {
+ cameraDir = toCameraDirection(call.getVideoState());
+ call.getVideoSettings().setCameraDir(cameraDir);
+ }
+
+ // Use the stored camera dir if this is an active video call for which camera direction
+ // is set.
+ else if (CallUtils.isActiveVideoCall(call)) {
+ cameraDir = call.getVideoSettings().getCameraDir();
+ }
+
+ // For all other cases infer the camera direction but don't store it in the call object.
+ else {
+ cameraDir = toCameraDirection(call.getVideoState());
+ }
+
+ com.android.incallui.Log.d(TAG, "updateCameraSelection: Setting camera direction to " +
+ cameraDir + " Call=" + call);
+ final InCallCameraManager cameraManager = InCallPresenter.getInstance().
+ getInCallCameraManager();
+ cameraManager.setUseFrontFacingCamera(cameraDir ==
+ Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING);
+ }
+
+ private static int toCameraDirection(int videoState) {
+ return VideoProfile.VideoState.isTransmissionEnabled(videoState) &&
+ !VideoProfile.VideoState.isBidirectional(videoState)
+ ? Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING
+ : Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING;
+ }
+
+ private static boolean isCameraDirectionSet(Call call) {
+ return CallUtils.isVideoCall(call) && call.getVideoSettings().getCameraDir()
+ != Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+ }
+
+ private static String toSimpleString(Call call) {
+ return call == null ? null : call.toSimpleString();
+ }
+
+ /**
* Defines the VideoCallUI interactions.
*/
public interface VideoCallUi extends Ui {
- void showVideoUi(boolean show);
+ void showVideoViews(boolean showPreview, boolean showIncoming);
+ void hideVideoUi();
+ void showVideoQualityChanged(int videoQuality);
boolean isDisplayVideoSurfaceCreated();
boolean isPreviewVideoSurfaceCreated();
Surface getDisplayVideoSurface();
Surface getPreviewVideoSurface();
+ int getCurrentRotation();
void setPreviewSize(int width, int height);
+ void setPreviewSurfaceSize(int width, int height);
+ void setDisplayVideoSize(int width, int height);
+ void setCallDataUsage(Context context, long dataUsage);
+ void displayCallSessionEvent(int event);
+ Point getScreenSize();
void cleanupSurfaces();
boolean isActivityRestart();
+ void showCallSubstateChanged(int callSubstate);
}
}
diff --git a/InCallUI/src/com/android/incallui/VideoPauseController.java b/InCallUI/src/com/android/incallui/VideoPauseController.java
new file mode 100644
index 000000000..727780e8f
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/VideoPauseController.java
@@ -0,0 +1,389 @@
+/* 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.
+ */
+
+package com.android.incallui;
+
+import android.os.SystemProperties;
+import android.telecom.VideoProfile;
+import com.android.incallui.Call.State;
+import com.android.incallui.InCallPresenter.InCallState;
+import com.android.incallui.InCallPresenter.InCallStateListener;
+import com.android.incallui.InCallPresenter.IncomingCallListener;
+import com.android.incallui.InCallVideoCallListenerNotifier.SessionModificationListener;
+import com.android.internal.util.Preconditions;
+
+/**
+ * The class is responsible for generating video pause/resume request.
+ */
+class VideoPauseController implements InCallStateListener, IncomingCallListener,
+ SessionModificationListener {
+ private static final String TAG = "VideoCallPauseController:";
+
+ private class CallContext {
+ public CallContext(Call call) {
+ Preconditions.checkNotNull(call);
+ update(call);
+ }
+
+ public void update(Call call) {
+ mCall = Preconditions.checkNotNull(call);
+ mState = call.getState();
+ mId = call.getId();
+ mVideoState = call.getVideoState();
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public int getVideoState() {
+ return mVideoState;
+ }
+
+ public String toString() {
+ return String.format("CallContext {CallId=%s, State=%s, VideoState=",
+ mId, mState, mVideoState);
+ }
+
+ public Call getCall() {
+ return mCall;
+ }
+
+ private int mState = State.INVALID;
+ private String mId;
+ private int mVideoState;
+ private Call mCall;
+ }
+
+ private InCallPresenter mInCallPresenter;
+ private static VideoPauseController sVideoPauseController;
+
+ private CallContext mPrimaryCallContext = null; // Context of primary call, if any.
+ private boolean mIsInBackground = false; // True if UI is not visible, false otherwise.
+ private int mVideoPauseMode = VIDEO_PAUSE_MODE_DISABLED;
+
+ /**
+ * Stores current video pause mode.
+ * 0 - Video Pause is disabled.
+ * 1 - Video Pause is enabled.
+ */
+ private static final String PROPERTY_VIDEO_PAUSE_MODE = "persist.radio.videopause.mode";
+ private static int VIDEO_PAUSE_MODE_DISABLED = 0;
+ private static int VIDEO_PAUSE_MODE_ENABLED = 1;
+
+ private VideoPauseController() {
+ mVideoPauseMode = SystemProperties.getInt(PROPERTY_VIDEO_PAUSE_MODE,
+ VIDEO_PAUSE_MODE_DISABLED);
+ if (mVideoPauseMode != VIDEO_PAUSE_MODE_ENABLED) { // Validate the mode before using.
+ mVideoPauseMode = VIDEO_PAUSE_MODE_DISABLED;
+ }
+ }
+
+ /*package*/
+ static synchronized VideoPauseController getInstance() {
+ if (sVideoPauseController == null) {
+ sVideoPauseController = new VideoPauseController();
+ }
+ return sVideoPauseController;
+ }
+
+ public void setUp(InCallPresenter inCallPresenter) {
+ if (!isVideoPausedEnabled()) {
+ return;
+ }
+
+ log("setUp...");
+ mInCallPresenter = Preconditions.checkNotNull(inCallPresenter);
+ mInCallPresenter.addListener(this);
+ mInCallPresenter.addIncomingCallListener(this);
+ InCallVideoCallListenerNotifier.getInstance().addSessionModificationListener(this);
+ }
+
+ public void tearDown() {
+ if (!isVideoPausedEnabled()) {
+ return;
+ }
+
+ log("tearDown...");
+ InCallVideoCallListenerNotifier.getInstance().removeSessionModificationListener(this);
+ mInCallPresenter.removeListener(this);
+ mInCallPresenter.removeIncomingCallListener(this);
+ clear();
+ }
+
+ private void clear() {
+ mInCallPresenter = null;
+ mPrimaryCallContext = null;
+ mIsInBackground = false;
+ }
+
+ /**
+ * The function gets called when call state changes.
+ * @param state Phone state.
+ * @param callList List of current call.
+ */
+ @Override
+ public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
+ log("onStateChange, OldState=" + oldState + " NewState=" + newState);
+
+ Call call = null;
+ if (newState == InCallState.INCOMING) {
+ call = callList.getIncomingCall();
+ } else if (newState == InCallState.WAITING_FOR_ACCOUNT) {
+ call = callList.getWaitingForAccountCall();
+ } else if (newState == InCallState.PENDING_OUTGOING) {
+ call = callList.getPendingOutgoingCall();
+ } else if (newState == InCallState.OUTGOING) {
+ call = callList.getOutgoingCall();
+ } else {
+ call = callList.getActiveCall();
+ }
+
+ boolean hasPrimaryCallChanged = !areSame(call, mPrimaryCallContext);
+ boolean canVideoPause = CallUtils.canVideoPause(call);
+ log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged);
+ log("onStateChange, canVideoPause=" + canVideoPause);
+ log("onStateChange, IsInBackground=" + mIsInBackground);
+
+ if (hasPrimaryCallChanged) {
+ onPrimaryCallChanged(call);
+ return;
+ }
+
+ if (isOutgoing(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
+ // Bring UI to foreground if outgoing request becomes active while UI is in
+ // background.
+ bringToForeground();
+ } else if (!isVideoCall(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
+ // Bring UI to foreground if VoLTE call becomes active while UI is in
+ // background.
+ bringToForeground();
+ }
+
+ updatePrimaryCallContext(call);
+ }
+
+ private void onPrimaryCallChanged(Call call) {
+ log("onPrimaryCallChanged: New call = " + call);
+ log("onPrimaryCallChanged: Old call = " + mPrimaryCallContext);
+ log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground);
+
+ Preconditions.checkState(!areSame(call, mPrimaryCallContext));
+ final boolean canVideoPause = CallUtils.canVideoPause(call);
+
+ if (isWaitingCall(mPrimaryCallContext) && canVideoPause && !mIsInBackground) {
+ // Send resume request for the active call, if user rejects incoming
+ // call and UI is in foreground.
+ sendRequest(call, true);
+ } else if (isWaitingCall(call) && canVideoPause(mPrimaryCallContext)) {
+ // Send pause request if there is an active video call, and we just received a new
+ // incoming call.
+ sendRequest(mPrimaryCallContext.getCall(), false);
+ } else if (isOutgoing(mPrimaryCallContext) && canVideoPause && !mIsInBackground) {
+ // Send resume request for the active call, if user ends outgoing call
+ // and UI is in foreground.
+ sendRequest(call, true);
+ }
+
+ updatePrimaryCallContext(call);
+ }
+
+ /**
+ * The function gets called when InCallUI receives a new incoming call.
+ */
+ @Override
+ public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
+ log("onIncomingCall, OldState=" + oldState + " NewState=" + newState + " Call=" + call);
+
+ if (areSame(call, mPrimaryCallContext)) {
+ return;
+ }
+
+ onPrimaryCallChanged(call);
+ }
+
+ private void updatePrimaryCallContext(Call call) {
+ if (call == null) {
+ mPrimaryCallContext = null;
+ } else if (mPrimaryCallContext != null) {
+ mPrimaryCallContext.update(call);
+ } else {
+ mPrimaryCallContext = new CallContext(call);
+ }
+ }
+
+ /**
+ * Called when UI goes in/out of the foreground.
+ * @param showing true if UI is in the foreground, false otherwise.
+ */
+ public void onUiShowing(boolean showing) {
+ if (!isVideoPausedEnabled() || mInCallPresenter == null) {
+ return;
+ }
+
+ final boolean notify = mInCallPresenter.getInCallState() == InCallState.INCALL;
+ if (showing) {
+ onResume(notify);
+ } else {
+ onPause(notify);
+ }
+ }
+
+ @Override
+ public void onUpgradeToVideoRequest(Call call, int videoState) {
+ }
+
+ @Override
+ public void onUpgradeToVideoSuccess(Call call) {
+ }
+
+ @Override
+ public void onUpgradeToVideoFail(int status, Call call) {
+ // TODO (ims-vt) Automatically bring in call ui to foreground.
+ }
+
+ @Override
+ public void onDowngradeToAudio(Call call) {
+ }
+
+ /**
+ * Called when UI becomes visible. This will send resume request for current video call, if any.
+ */
+ private void onResume(boolean notify) {
+ log("onResume: notify=" + notify);
+
+ mIsInBackground = false;
+ if (canVideoPause(mPrimaryCallContext) && notify) {
+ sendRequest(mPrimaryCallContext.getCall(), true);
+ } else {
+ log("onResume. Ignoring...");
+ }
+ }
+
+ /**
+ * Called when UI becomes invisible. This will send pause request for current video call, if any.
+ */
+ private void onPause(boolean notify) {
+ log("onPause: notify=" + notify);
+
+ mIsInBackground = true;
+ if (canVideoPause(mPrimaryCallContext) && notify) {
+ sendRequest(mPrimaryCallContext.getCall(), false);
+ } else {
+ log("onPause, Ignoring...");
+ }
+ }
+
+ private void bringToForeground() {
+ if (mInCallPresenter != null) {
+ log("Bringing UI to foreground");
+ mInCallPresenter.bringToForeground(false);
+ } else {
+ loge("InCallPresenter is null. Cannot bring UI to foreground");
+ }
+ }
+
+ /**
+ * Sends Pause/Resume request.
+ * @param call Call to be paused/resumed.
+ * @param resume If true resume request will be sent, otherwise pause request.
+ */
+ private void sendRequest(Call call, boolean resume) {
+ if (resume) {
+ log("sending resume request, call=" + call);
+ call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoUnPauseProfile(call));
+ } else {
+ log("sending pause request, call=" + call);
+ call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoPauseProfile(call));
+ }
+ }
+
+ private boolean isVideoPausedEnabled() {
+ return mVideoPauseMode != VIDEO_PAUSE_MODE_DISABLED;
+ }
+
+ private static boolean areSame(Call call, CallContext callContext) {
+ if (call == null && callContext == null) {
+ return true;
+ } else if (call == null || callContext == null) {
+ return false;
+ }
+ return call.getId().equals(callContext.getId());
+ }
+
+ private static boolean areSame(CallContext callContext, Call call) {
+ return areSame(call, callContext);
+ }
+
+ private static boolean canVideoPause(CallContext callContext) {
+ return isVideoCall(callContext) && callContext.getState() == Call.State.ACTIVE;
+ }
+
+ private static boolean isVideoCall(CallContext callContext) {
+ return callContext != null && VideoProfile.VideoState.isVideo(callContext.getVideoState());
+ }
+
+ /**
+ * Returns true if call is in incoming/waiting state, false otherwise.
+ */
+ private static boolean isWaitingCall(CallContext call) {
+ return call != null && (call.getState() == Call.State.CALL_WAITING
+ || call.getState() == Call.State.INCOMING);
+ }
+
+ private static boolean isWaitingCall(Call call) {
+ return call != null && (call.getState() == Call.State.CALL_WAITING
+ || call.getState() == Call.State.INCOMING);
+ }
+
+ /**
+ * Returns true if the call is outgoing, false otherwise
+ */
+ private static boolean isOutgoing(CallContext call) {
+ return call != null && Call.State.isDialing(call.getState());
+ }
+
+ /**
+ * Returns true if the call is on hold, false otherwise
+ */
+ private static boolean isHolding(CallContext call) {
+ return call != null && call.getState() == Call.State.ONHOLD;
+ }
+
+ private void log(String msg) {
+ Log.d(this, TAG + msg);
+ }
+
+ private void loge(String msg) {
+ Log.e(this, TAG + msg);
+ }
+}