summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/drawable/ic_lockscreen_answer_rx_video.xml30
-rw-r--r--res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml28
-rw-r--r--res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml36
-rw-r--r--res/drawable/ic_lockscreen_answer_tx_video.xml30
-rw-r--r--res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml28
-rw-r--r--res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml36
-rw-r--r--res/layout-land/call_card_fragment.xml7
-rw-r--r--res/layout-land/video_call_views.xml4
-rw-r--r--res/layout/call_button_fragment.xml7
-rw-r--r--res/layout/call_card_fragment.xml7
-rw-r--r--res/layout/video_call_views.xml4
-rw-r--r--res/menu/incall_overflow_menu.xml3
-rw-r--r--res/values/array.xml43
-rw-r--r--res/values/strings.xml60
-rw-r--r--src/com/android/incallui/AnswerFragment.java35
-rw-r--r--src/com/android/incallui/AnswerPresenter.java120
-rw-r--r--src/com/android/incallui/Call.java143
-rw-r--r--src/com/android/incallui/CallButtonFragment.java119
-rw-r--r--src/com/android/incallui/CallButtonPresenter.java100
-rw-r--r--src/com/android/incallui/CallCardFragment.java5
-rw-r--r--src/com/android/incallui/CallCardPresenter.java3
-rw-r--r--src/com/android/incallui/CallList.java16
-rw-r--r--src/com/android/incallui/CallUtils.java90
-rw-r--r--src/com/android/incallui/GlowPadWrapper.java14
-rw-r--r--src/com/android/incallui/InCallActivity.java34
-rw-r--r--src/com/android/incallui/InCallApp.java4
-rw-r--r--src/com/android/incallui/InCallCameraManager.java25
-rw-r--r--src/com/android/incallui/InCallPresenter.java104
-rw-r--r--src/com/android/incallui/InCallVideoCallListener.java79
-rw-r--r--src/com/android/incallui/InCallVideoCallListenerNotifier.java75
-rw-r--r--src/com/android/incallui/Log.java2
-rw-r--r--src/com/android/incallui/VideoCallFragment.java447
-rw-r--r--src/com/android/incallui/VideoCallPresenter.java725
-rw-r--r--src/com/android/incallui/VideoPauseController.java389
34 files changed, 2547 insertions, 305 deletions
diff --git a/res/drawable/ic_lockscreen_answer_rx_video.xml b/res/drawable/ic_lockscreen_answer_rx_video.xml
new file mode 100644
index 00000000..c5a41d81
--- /dev/null
+++ b/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/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml b/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml
new file mode 100644
index 00000000..750ef5e2
--- /dev/null
+++ b/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/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml b/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml
new file mode 100644
index 00000000..5efd3d14
--- /dev/null
+++ b/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/res/drawable/ic_lockscreen_answer_tx_video.xml b/res/drawable/ic_lockscreen_answer_tx_video.xml
new file mode 100644
index 00000000..15d11978
--- /dev/null
+++ b/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/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml b/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml
new file mode 100644
index 00000000..c1dca4d0
--- /dev/null
+++ b/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/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml b/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml
new file mode 100644
index 00000000..b0ad943d
--- /dev/null
+++ b/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/res/layout-land/call_card_fragment.xml b/res/layout-land/call_card_fragment.xml
index aa04840c..7714178f 100644
--- a/res/layout-land/call_card_fragment.xml
+++ b/res/layout-land/call_card_fragment.xml
@@ -81,6 +81,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/res/layout-land/video_call_views.xml b/res/layout-land/video_call_views.xml
index 7065d451..8961ea4b 100644
--- a/res/layout-land/video_call_views.xml
+++ b/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/res/layout/call_button_fragment.xml b/res/layout/call_button_fragment.xml
index 18906dda..604d79e6 100644
--- a/res/layout/call_button_fragment.xml
+++ b/res/layout/call_button_fragment.xml
@@ -159,6 +159,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/res/layout/call_card_fragment.xml b/res/layout/call_card_fragment.xml
index 17906f73..dc3ee118 100644
--- a/res/layout/call_card_fragment.xml
+++ b/res/layout/call_card_fragment.xml
@@ -72,6 +72,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/res/layout/video_call_views.xml b/res/layout/video_call_views.xml
index ab03aa35..8961ea4b 100644
--- a/res/layout/video_call_views.xml
+++ b/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/res/menu/incall_overflow_menu.xml b/res/menu/incall_overflow_menu.xml
index 06208ebd..2de85871 100644
--- a/res/menu/incall_overflow_menu.xml
+++ b/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/res/values/array.xml b/res/values/array.xml
index 5270de1a..46592e12 100644
--- a/res/values/array.xml
+++ b/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/res/values/strings.xml b/res/values/strings.xml
index 944eff6f..99b8111b 100644
--- a/res/values/strings.xml
+++ b/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/src/com/android/incallui/AnswerFragment.java b/src/com/android/incallui/AnswerFragment.java
index 8cbce10c..d1636371 100644
--- a/src/com/android/incallui/AnswerFragment.java
+++ b/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.
@@ -161,6 +164,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;
@@ -336,12 +366,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/src/com/android/incallui/AnswerPresenter.java b/src/com/android/incallui/AnswerPresenter.java
index cc95e019..32c7b9a1 100644
--- a/src/com/android/incallui/AnswerPresenter.java
+++ b/src/com/android/incallui/AnswerPresenter.java
@@ -19,6 +19,8 @@ package com.android.incallui;
import android.content.Context;
import com.android.incallui.InCallPresenter.InCallState;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
import java.util.List;
@@ -51,7 +53,8 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
processIncomingCall(call);
}
call = calls.getVideoUpgradeRequestCall();
- if (call != null) {
+ Log.d(this, "getVideoUpgradeRequestCall call =" + call);
+ if (videoCall != null && call == null) {
processVideoUpgradeRequestCall(call);
}
} else {
@@ -72,6 +75,57 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
}
}
+ @Override
+ public void onDisconnect(Call call) {
+ // no-op
+ }
+
+ @Override
+ public void onIncomingCall(Call call) {
+ // TODO: Ui is being destroyed when the fragment detaches. Need clean up step to stop
+ // 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);
+ }
+>>>>>>> 8bef461
+ }
+ }
+
+ 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;
@@ -97,28 +151,65 @@ 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);
+
+ final int currentVideoState = call.getVideoState();
+ final int modifyToVideoState = call.getModifyToVideoState();
+
+ if (currentVideoState == modifyToVideoState) {
+ Log.w(this, "processVideoUpgradeRequestCall: Video states are same. Return.");
+ return;
+ }
+
showAnswerUi(true);
+ getUi().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;
+ }
- getUi().showTargets(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);
+ }
- showAnswerUi(false);
+ final Call incall = CallList.getInstance().getIncomingCall();
+ if (incall != null || isUpgradePending) {
+ showAnswerUi(true);
+ } else {
+ 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());
@@ -129,14 +220,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);
}
@@ -146,9 +237,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/src/com/android/incallui/Call.java b/src/com/android/incallui/Call.java
index d8e76c7b..4ed7a49c 100644
--- a/src/com/android/incallui/Call.java
+++ b/src/com/android/incallui/Call.java
@@ -18,8 +18,10 @@ package com.android.incallui;
import com.android.contacts.common.CallUtil;
import com.android.contacts.common.testing.NeededForTesting;
+import com.android.incallui.CallList.Listener;
import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
@@ -122,8 +124,51 @@ public 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;
@@ -131,12 +176,16 @@ public 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();
}
@@ -149,29 +198,38 @@ public 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);
}
@@ -188,6 +246,11 @@ public 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;
@@ -212,6 +275,14 @@ public 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() {
Trace.beginSection("Update");
int oldState = getState();
@@ -225,7 +296,7 @@ public class Call {
}
private void updateFromTelecommCall() {
- Log.d(this, "updateFromTelecommCall: " + mTelecommCall);
+ Log.d(this, "updateFromTelecommCall: " + mTelecommCall.toString());
setState(translateState(mTelecommCall.getState()));
setDisconnectCause(mTelecommCall.getDetails().getDisconnectCause());
@@ -395,24 +466,69 @@ public 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;
@@ -437,7 +553,7 @@ public class Call {
}
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
@@ -445,6 +561,13 @@ public 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/src/com/android/incallui/CallButtonFragment.java b/src/com/android/incallui/CallButtonFragment.java
index 78956cbb..a9fafae1 100644
--- a/src/com/android/incallui/CallButtonFragment.java
+++ b/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");
@@ -325,6 +340,7 @@ public class CallButtonFragment
mMergeButton.setEnabled(isEnabled);
mPauseVideoButton.setEnabled(isEnabled);
mOverflowButton.setEnabled(isEnabled);
+ mManageVideoCallConferenceButton.setEnabled(isEnabled);
}
@Override
@@ -402,6 +418,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);
@@ -427,9 +447,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);
@@ -455,6 +549,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;
@@ -478,6 +575,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());
}
@@ -557,6 +656,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,
@@ -785,4 +889,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/src/com/android/incallui/CallButtonPresenter.java b/src/com/android/incallui/CallButtonPresenter.java
index 9fedc95b..4e840be9 100644
--- a/src/com/android/incallui/CallButtonPresenter.java
+++ b/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/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java
index af588767..7420ba90 100644
--- a/src/com/android/incallui/CallCardFragment.java
+++ b/src/com/android/incallui/CallCardFragment.java
@@ -564,7 +564,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);
@@ -694,6 +694,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/src/com/android/incallui/CallCardPresenter.java b/src/com/android/incallui/CallCardPresenter.java
index 9e5c6115..bd369433 100644
--- a/src/com/android/incallui/CallCardPresenter.java
+++ b/src/com/android/incallui/CallCardPresenter.java
@@ -326,7 +326,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/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java
index 1ede89d8..9868aef5 100644
--- a/src/com/android/incallui/CallList.java
+++ b/src/com/android/incallui/CallList.java
@@ -75,7 +75,9 @@ public class CallList implements InCallPhoneListener {
public void onCallAdded(Phone phone, android.telecom.Call telecommCall) {
Trace.beginSection("onCallAdded");
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);
@@ -141,6 +143,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.
*/
@@ -547,7 +555,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/src/com/android/incallui/CallUtils.java b/src/com/android/incallui/CallUtils.java
new file mode 100644
index 00000000..80b553aa
--- /dev/null
+++ b/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/src/com/android/incallui/GlowPadWrapper.java b/src/com/android/incallui/GlowPadWrapper.java
index b50fdd8c..584ce65d 100644
--- a/src/com/android/incallui/GlowPadWrapper.java
+++ b/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/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java
index dd49e072..1283177d 100644
--- a/src/com/android/incallui/InCallActivity.java
+++ b/src/com/android/incallui/InCallActivity.java
@@ -123,10 +123,9 @@ public class InCallActivity extends Activity implements FragmentDisplayManager {
};
/**
- * 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) {
@@ -158,9 +157,8 @@ public class InCallActivity extends Activity implements FragmentDisplayManager {
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;
@@ -216,6 +214,11 @@ public class InCallActivity extends Activity implements FragmentDisplayManager {
// 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
@@ -267,6 +270,9 @@ public class InCallActivity extends Activity implements FragmentDisplayManager {
@Override
protected void onStop() {
Log.d(this, "onStop()...");
+
+ InCallPresenter.getInstance().updateIsChangingConfigurations();
+ InCallPresenter.getInstance().onActivityStopped();
super.onStop();
}
@@ -274,6 +280,7 @@ public class InCallActivity extends Activity implements FragmentDisplayManager {
protected void onDestroy() {
Log.d(this, "onDestroy()... this = " + this);
InCallPresenter.getInstance().unsetActivity(this);
+ InCallPresenter.getInstance().updateIsChangingConfigurations();
super.onDestroy();
}
@@ -471,15 +478,22 @@ public class InCallActivity extends Activity implements FragmentDisplayManager {
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/src/com/android/incallui/InCallApp.java b/src/com/android/incallui/InCallApp.java
index d6f4f42d..a273d780 100644
--- a/src/com/android/incallui/InCallApp.java
+++ b/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/src/com/android/incallui/InCallCameraManager.java b/src/com/android/incallui/InCallCameraManager.java
index ded9387f..b7ec079a 100644
--- a/src/com/android/incallui/InCallCameraManager.java
+++ b/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/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java
index 05a05a60..fdf19331 100644
--- a/src/com/android/incallui/InCallPresenter.java
+++ b/src/com/android/incallui/InCallPresenter.java
@@ -33,6 +33,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;
@@ -175,6 +177,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;
/** Display colors for the UI. Consists of a primary color and secondary (darker) color */
@@ -251,6 +261,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");
}
@@ -266,6 +278,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener,
Log.d(this, "tearDown");
mServiceConnected = false;
attemptCleanup();
+
+ VideoPauseController.getInstance().tearDown();
}
private void attemptFinishActivity() {
@@ -411,7 +425,9 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener,
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);
@@ -448,6 +464,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.
@@ -683,33 +703,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);
}
@@ -737,6 +760,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.
*/
@@ -766,6 +803,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener,
if (showing) {
mIsActivityPreviouslyStarted = true;
+ } else {
+ updateIsChangingConfigurations();
}
for (InCallUiListener listener : mInCallUiListeners) {
@@ -781,6 +820,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.
*/
@@ -1130,6 +1189,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
@@ -1161,6 +1221,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");
}
@@ -1245,7 +1309,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:
@@ -1263,8 +1340,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener,
default:
rotationAngle = 0;
}
-
- mCallList.notifyCallsOfDeviceRotation(rotationAngle);
+ return rotationAngle;
}
/**
@@ -1286,6 +1362,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener,
*/
public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
if (mInCallActivity == null) {
+ Log.e(this, "InCallActivity is null. Can't set requested orientation.");
return;
}
@@ -1296,6 +1373,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/src/com/android/incallui/InCallVideoCallListener.java b/src/com/android/incallui/InCallVideoCallListener.java
index 4ba2bd99..df9dfdce 100644
--- a/src/com/android/incallui/InCallVideoCallListener.java
+++ b/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/src/com/android/incallui/InCallVideoCallListenerNotifier.java b/src/com/android/incallui/InCallVideoCallListenerNotifier.java
index 9f3f062c..818ed032 100644
--- a/src/com/android/incallui/InCallVideoCallListenerNotifier.java
+++ b/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/src/com/android/incallui/Log.java b/src/com/android/incallui/Log.java
index 07a0e61c..5bc74b1a 100644
--- a/src/com/android/incallui/Log.java
+++ b/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/src/com/android/incallui/VideoCallFragment.java b/src/com/android/incallui/VideoCallFragment.java
index 143ee245..233ff92a 100644
--- a/src/com/android/incallui/VideoCallFragment.java
+++ b/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.
@@ -52,11 +59,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
@@ -86,26 +105,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,
- View.OnClickListener, View.OnAttachStateChangeListener {
+ 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}.
@@ -113,8 +126,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);
}
/**
@@ -125,7 +139,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;
@@ -151,7 +169,24 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
mTextureView = view;
mTextureView.setSurfaceTextureListener(this);
mTextureView.setOnClickListener(this);
- mTextureView.addOnAttachStateChangeListener(this);
+
+ 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;
}
/**
@@ -172,17 +207,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");
}
}
@@ -210,17 +259,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.");
+ }
}
/**
@@ -259,7 +321,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;
}
@@ -269,6 +339,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.
*
@@ -285,10 +363,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);
}
}
@@ -299,9 +379,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;
@@ -317,7 +398,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.");
+ }
}
};
@@ -339,6 +424,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
mIsLandscape = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
+ Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape);
getPresenter().init(getActivity());
}
@@ -375,9 +461,14 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
// In a right-to-left locale, the space for the video view is to the left of the call card
// 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);
@@ -385,7 +476,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);
}
@@ -400,6 +491,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);
@@ -411,13 +503,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;
}
/**
@@ -429,33 +542,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;
@@ -467,8 +691,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;
}
@@ -477,7 +712,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;
}
/**
@@ -485,7 +722,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;
}
/**
@@ -515,6 +754,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();
@@ -527,15 +767,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();
}
@@ -543,16 +891,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 {
@@ -600,28 +952,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/src/com/android/incallui/VideoCallPresenter.java b/src/com/android/incallui/VideoCallPresenter.java
index d63c6e8b..e4a5db97 100644
--- a/src/com/android/incallui/VideoCallPresenter.java
+++ b/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";
private static final String TAG = VideoCallPresenter.class.getSimpleName();
@@ -126,7 +135,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).
@@ -146,7 +160,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;
@@ -172,9 +194,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
@@ -184,7 +208,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;
}
/**
@@ -195,44 +220,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) {
- if (DEBUG) {
- Log.i(TAG, "onSurfaceCreated: " + 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());
}
@@ -252,12 +277,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;
}
@@ -265,12 +293,44 @@ 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
* {@link CallCardPresenter} of the change.
@@ -278,8 +338,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();
}
@@ -305,41 +364,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;
}
}
@@ -352,120 +522,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() {
- if (DEBUG) {
- Log.i(TAG, "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;
- InCallCameraManager cameraManager = InCallPresenter.getInstance().
- getInCallCameraManager();
- mVideoCall.setCamera(cameraManager.getActiveCameraId());
- mVideoCall.requestCameraCapabilities();
- if (DEBUG) {
- Log.i(TAG, "isDisplayVideoSurfacedCreated: " + ui.isDisplayVideoSurfaceCreated());
- }
- if (ui.isDisplayVideoSurfaceCreated()) {
- mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface());
+ 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;
}
- mPreVideoAudioMode = AudioModeProvider.getInstance().getAudioMode();
- TelecomAdapter.getInstance().setAudioRoute(AudioState.ROUTE_SPEAKER);
+ if (isCameraRequired) {
+ InCallCameraManager cameraManager = InCallPresenter.getInstance().
+ getInCallCameraManager();
+ videoCall.setCamera(cameraManager.getActiveCameraId());
+ mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
+
+ videoCall.requestCameraCapabilities();
+ } else {
+ mPreviewSurfaceState = PreviewSurfaceState.NONE;
+ videoCall.setCamera(null);
+ }
}
/**
- * 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));
}
/**
@@ -493,11 +757,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);
}
/**
@@ -510,16 +806,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;
@@ -537,45 +838,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
@@ -611,16 +972,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/src/com/android/incallui/VideoPauseController.java b/src/com/android/incallui/VideoPauseController.java
new file mode 100644
index 00000000..727780e8
--- /dev/null
+++ b/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);
+ }
+}