From 18c0feda76fe333f3db1bf7bd307458a9e6b6005 Mon Sep 17 00:00:00 2001 From: Rekha Kumar Date: Wed, 18 Mar 2015 09:55:55 -0700 Subject: IMS-VT: Add support for video calls IMS-VT: Upgrade downgrade and hold resume video calls -Add support for upgrade downgrade video calls. -Add support for hold resume and call waiting IMS-VT: Fixed surface caching. Fixed surface caching. IMS-VT: Fixes InCallUI/Dialer crash when there is a VT call. -Fixes InCallUI/Dialer crash when UE is rotated. -Fixes InCallUI/Dialer crash when UE VoLTE call is upgraded to VT. Don't default to speaker phone for VT when speaker is disabled - The adb property persist.radio.ims.audio.output indicates whether speaker is disabled explicitly or not. - If the above property is set to 1, don't enable speaker phone by default in VT call. IMS-VT: Fix CVO, surface and resource related issues. - Send initial orientation to VT Service. - Detect if Activity is being destroyed due to confugration changes. - Close the camera when InCallUI is pushed to background. - Fix surface and VT service related issues when Fragment and Presenter gets destroyed. IMS-VT: Peer Resolution Feature Implementation Change display video size based on peer resolution values received from far end. IMS-VT: Answering calls and upgrade requests as VT_TX and VT_RX -Support for showing one way options for incoming upgrade request -Fix issue where incoming video popup stays on screen even after rejecting the request. -Fix issue where incoming video popup stays on even after it has been timed out by lower layers. - Answer with different calltypes support. Notify listeners of video quality changed event and display message on UI - Add methods to notify listeners of type VideoEventListener when video quality changes. - Display a notification on the UI when video quality changes. IMS-VT: Enable SIP based video multitasking. Enable SIP based video multitasking. IMS-VT: Call data usage feature - Add support for call data usage callback - Request for call data usage stats - Log call data usage stats when IMS layers send update Change-Id: I3f0dde0d82698085fa5d3f110720f10326eca768 IMS-VT: When TTY is ON, do not allow upgrade to VT call When TTY mode is ON, the user SHOULD NOT be allowed to upgrade a call from VOLTE to VT/VT-TX/VT-RX and an UI alert message will be displayed explaining upgrade to VT call cannot be initiated. IMS-VT: Add null check in call upgrade fail scenario Crash is observed if call is ended in the interval wherein handler is started to change state from REQUEST_FAILED to NO_REQUEST after an interval. Added null check to change state only when call exists Propagate call substate message and display a notification on the UI Ims: Reject upgrade request 1. If there is waiting call that is pending user action and 2. Before offering a waiting call IMS-VT: Provide Player State indication to user. Change to display "player started/stopped" toast message whenever video starts/stops flowing IMS-VT: Cleanup video views when not required - Hide video views when not required. Set display size as per current TextureView size - Using TextureView height and width to calculate the display size. - When ever there is change in display make sure that center the display. IMS-VT: Exit VT call full screen mode. If the call is no longer a VT call, then exit full screen mode Change-Id: Ibc4ad8f9a4c38e467820028cdc2c7e68d65fd93c CRs-Fixed: 760925 IMS-VT: Upgrade button fix -Show upgrade/downgrade button only when call is in ACTIVE or ONHOLD state IMS-VT: Show correct call types during video pause -In paused state, upgrade downgrade button does not list the calltypes dropdown box -Remove the paused bit to calculate call type during video paused state. IMS-VT: Move strings for video quality changed indication to resource files IMS-VT: Clean up the showCallSubstateChanged API - Make the code more readable. IMS-VT: Set audio route to Speaker if current route is not bluetooth or headset - We always set the audio route to speaker when we enter video mode. - That is not correct. We should check if headset or bluetooth is connected before defaulting to speaker. IMS-VT: Turn speaker on/off for video calls based on call state changes - Turn speaker on only when video call is active or dialing - Switch back to the previous audio mode when there are no more calls - Make the previous audio mode static so that information persists when the class is recreated (e.g. when UE is rotated or multitasking happens) IMS-VT: Use back camera instead of front camera for VT-TX Set back camera for below cases 1) VOLTE to VT-TX 2) VT to VT-TX 3) Waiting call over Vt-TX call. IMS-VT: Fix upgrade in call waiting scenarios -When one VoLTE call is active and another VoLTE call is on hold and upgrade button is hit, call ends. Fix this upgrade issue. IMS-VT: Enter video mode when the primary video call changes - We only enter video mode when we have any video state changes - Enter video mode should also be called when the video call changes. e.g. When we have a second call with the same video state, we don't enter video mode which is incorrect. This change fixes the issue Add null check for InCallActivity in setInCallAllowsOrientationChange - In some cases, this API is called when InCallActivity is null. This causes a null pointer exception. Fix is to add a null check. IMS-VT: Fix check for VOLTE call (AND to OR) when turning speaker on IMS-VT: Check if call is video before enabling speaker in updateAudioMode - Moved the video call check to the correct block where we are enabling speaker IMS-VT: Get call substate values correctly from the bit mask - We were getting the call substate incorrectly as an int. - Fixed that by getting it from the bit mask and using the possible multiple values to display the call substate message. IMS-VT: Keep the screen on during video calls. UI screens times out after some time. During video calls the screen must be kept on. Change-Id: Icaa8662210b2dd323b29f4a472869a9ed1e01d00 IMS-VT: Open front facing camera for VT calls. Open front facing camera for VT calls. IMS-VT: Show manage conference button for Video Call - We show the manage conference in a separate section for VOLTE Calls. - In the case of Video Call, the entire screen is occupied by the far end video. So we display manage conference in the overflow section of the call button fragment. IMS-VT: Fix camera preview freeze for CONF video calls. Fix camera preview freeze for CONF video calls. IMS-VT: Move persist.radio.ims.audio.output to frameworks Move persist.radio.ims.audio.output and related constants to TelephonyProperties and PhoneConstants so that these can be accessed from multiple git projects instead of redefining. IMS-VT: Multiple video call fixes/optimizations. -Current implementation clears primary call cache upon exiting video mode. This will remove video call provider interface as well, which will prevent further communication with the backend. Don't clear primary call cache upon exiting video mode. -Unregister call details listener when UI transitions into unready state -Send surface of incoming video to video call provider before opening camera. IMS: Cleanup all listener objects Clean all stale listener objects to avoid the memory leak. IMS: Fix speaker icon display issue in Call UI. In few devices, InCall UI can show upto five buttons in a row. Modify code to display at most five buttons in a row. Show overflow menu if number of buttons is more than five. IMS-VT: Show glowpad view with accept/reject for most video upgrade cases - Show the glowpad view with all options only when upgrading from Volte to VT - For all other cases, show the glowpad with accept/reject only Change-Id: I41ecbda40db7c3c69428fc4272f8bfbd258e2980 --- .../res/drawable/ic_lockscreen_answer_rx_video.xml | 30 + ..._lockscreen_answer_rx_video_activated_layer.xml | 28 + .../ic_lockscreen_answer_rx_video_normal_layer.xml | 36 ++ .../res/drawable/ic_lockscreen_answer_tx_video.xml | 30 + ..._lockscreen_answer_tx_video_activated_layer.xml | 28 + .../ic_lockscreen_answer_tx_video_normal_layer.xml | 36 ++ InCallUI/res/layout-land/call_card_content.xml | 7 + InCallUI/res/layout-land/video_call_views.xml | 4 +- InCallUI/res/layout/call_button_fragment.xml | 7 + InCallUI/res/layout/call_card_content.xml | 7 + InCallUI/res/layout/video_call_views.xml | 4 +- InCallUI/res/menu/incall_overflow_menu.xml | 3 + InCallUI/res/values/array.xml | 43 +- InCallUI/res/values/strings.xml | 60 ++ .../src/com/android/incallui/AnswerFragment.java | 35 +- .../src/com/android/incallui/AnswerPresenter.java | 116 +++- InCallUI/src/com/android/incallui/Call.java | 143 ++++- .../com/android/incallui/CallButtonFragment.java | 119 +++- .../com/android/incallui/CallButtonPresenter.java | 100 ++- .../src/com/android/incallui/CallCardFragment.java | 5 +- .../com/android/incallui/CallCardPresenter.java | 3 +- InCallUI/src/com/android/incallui/CallList.java | 16 +- InCallUI/src/com/android/incallui/CallUtils.java | 90 +++ .../src/com/android/incallui/GlowPadWrapper.java | 14 +- .../src/com/android/incallui/InCallActivity.java | 34 +- InCallUI/src/com/android/incallui/InCallApp.java | 4 +- .../com/android/incallui/InCallCameraManager.java | 25 + .../src/com/android/incallui/InCallPresenter.java | 109 +++- .../android/incallui/InCallVideoCallListener.java | 79 ++- .../incallui/InCallVideoCallListenerNotifier.java | 75 ++- InCallUI/src/com/android/incallui/Log.java | 2 +- .../com/android/incallui/VideoCallFragment.java | 441 +++++++++++-- .../com/android/incallui/VideoCallPresenter.java | 715 +++++++++++++++++---- .../com/android/incallui/VideoPauseController.java | 389 +++++++++++ 34 files changed, 2538 insertions(+), 299 deletions(-) create mode 100644 InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml create mode 100644 InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml create mode 100644 InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml create mode 100644 InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml create mode 100644 InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml create mode 100644 InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml create mode 100644 InCallUI/src/com/android/incallui/CallUtils.java create mode 100644 InCallUI/src/com/android/incallui/VideoPauseController.java diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml new file mode 100644 index 000000000..c5a41d814 --- /dev/null +++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml new file mode 100644 index 000000000..750ef5e26 --- /dev/null +++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml new file mode 100644 index 000000000..5efd3d142 --- /dev/null +++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml new file mode 100644 index 000000000..15d11978e --- /dev/null +++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml new file mode 100644 index 000000000..c1dca4d06 --- /dev/null +++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml new file mode 100644 index 000000000..b0ad943dc --- /dev/null +++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + diff --git a/InCallUI/res/layout-land/call_card_content.xml b/InCallUI/res/layout-land/call_card_content.xml index 496b6b399..0bb45a244 100644 --- a/InCallUI/res/layout-land/call_card_content.xml +++ b/InCallUI/res/layout-land/call_card_content.xml @@ -71,6 +71,13 @@ android:layout_height="wrap_content" android:layout_alignTop="@id/photo" /> + + @@ -32,4 +32,4 @@ android:layout_margin="@dimen/video_preview_margin" android:layout_width="70dp" android:layout_height="120dp" /> - \ No newline at end of file + diff --git a/InCallUI/res/layout/call_button_fragment.xml b/InCallUI/res/layout/call_button_fragment.xml index e8feca68a..69d0ee3a0 100644 --- a/InCallUI/res/layout/call_button_fragment.xml +++ b/InCallUI/res/layout/call_button_fragment.xml @@ -160,6 +160,13 @@ android:contentDescription="@string/onscreenOverflowText" android:visibility="gone" /> + + + diff --git a/InCallUI/res/layout/call_card_content.xml b/InCallUI/res/layout/call_card_content.xml index 7a42586ea..a5fd8f72a 100644 --- a/InCallUI/res/layout/call_card_content.xml +++ b/InCallUI/res/layout/call_card_content.xml @@ -70,6 +70,13 @@ android:background="@android:color/white" android:src="@drawable/img_no_image_automirrored" /> + + @@ -32,4 +32,4 @@ android:layout_margin="@dimen/video_preview_margin" android:layout_width="70dp" android:layout_height="120dp" /> - \ No newline at end of file + diff --git a/InCallUI/res/menu/incall_overflow_menu.xml b/InCallUI/res/menu/incall_overflow_menu.xml index 06208ebd8..2de858711 100644 --- a/InCallUI/res/menu/incall_overflow_menu.xml +++ b/InCallUI/res/menu/incall_overflow_menu.xml @@ -30,4 +30,7 @@ + + diff --git a/InCallUI/res/values/array.xml b/InCallUI/res/values/array.xml index 5270de1ac..46592e126 100644 --- a/InCallUI/res/values/array.xml +++ b/InCallUI/res/values/array.xml @@ -74,6 +74,8 @@ @null @drawable/ic_lockscreen_decline @drawable/ic_lockscreen_answer_video + @drawable/ic_lockscreen_answer_tx_video + @drawable/ic_lockscreen_answer_rx_video @string/description_target_answer_video_call @@ -98,6 +100,8 @@ @drawable/ic_lockscreen_text @drawable/ic_lockscreen_decline @drawable/ic_lockscreen_answer + @drawable/ic_lockscreen_answer_tx_video + @drawable/ic_lockscreen_answer_rx_video @string/description_target_answer_video_call @@ -113,7 +117,7 @@ - @@ -121,7 +125,9 @@ @drawable/ic_lockscreen_answer_video @null @drawable/ic_lockscreen_decline - @null" + @drawable/ic_lockscreen_answer + @drawable/ic_lockscreen_answer_tx_video + @drawable/ic_lockscreen_answer_rx_video @string/description_target_accept_upgrade_to_video_request @@ -135,4 +141,37 @@ @string/description_direction_left @null + + + + @drawable/ic_lockscreen_answer_video + @drawable/ic_lockscreen_decline + + + + + @drawable/ic_lockscreen_answer_tx_video + @drawable/ic_lockscreen_decline + + + @string/description_target_accept_upgrade_to_video_transmit_request + @string/description_target_decline_upgrade_to_video_transmit_request + + + + + @drawable/ic_lockscreen_answer_rx_video + @drawable/ic_lockscreen_decline + + + @string/description_target_accept_upgrade_to_video_receive_request + @string/description_target_decline_upgrade_to_video_receive_request + + diff --git a/InCallUI/res/values/strings.xml b/InCallUI/res/values/strings.xml index 944eff6fc..99b8111bb 100644 --- a/InCallUI/res/values/strings.xml +++ b/InCallUI/res/values/strings.xml @@ -114,6 +114,8 @@ Requesting video Can\'t connect video call + + Video call (Paused) @@ -263,6 +265,8 @@ Merge calls Swap calls + + Manage Conference Hold @@ -299,6 +303,28 @@ More options + + Which type of call? + Video bidirectional + Video transmit + Video receive + Voice Only + + + Please disable TTY Mode to upgrade to video calls. + + + Player Started + + Player Stopped + + Camera not ready + + Camera ready + + "Unkown call session event" + @@ -359,6 +385,18 @@ Decline video request + + Accept video transmit request + + Decline video transmit request + + Accept video receive request + + Decline video receive request Slide up for %s. @@ -417,4 +455,26 @@ Emergency number + + + + Call substate - \u000a + + Resumed \u000a + + Connected Suspended (Audio) \u000a + + Connected Suspended (Video) \u000a + + Avp Retry \u000a + + Video quality changed to \u0020 + + High + + Medium + + Low + + Unknown diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java index 29747dabd..a066396c9 100644 --- a/InCallUI/src/com/android/incallui/AnswerFragment.java +++ b/InCallUI/src/com/android/incallui/AnswerFragment.java @@ -50,6 +50,9 @@ public class AnswerFragment extends BaseFragment } } + private boolean isVideoUpgradePending(Call call) { + return call.getSessionModificationState() + == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; + } + + @Override + public void onUpgradeToVideo(Call call) { + Log.d(this, "onUpgradeToVideo: " + this + " call=" + call); + if (getUi() == null) { + Log.d(this, "onUpgradeToVideo ui is null"); + return; + } + boolean isUpgradePending = isVideoUpgradePending(call); + InCallPresenter inCallPresenter = InCallPresenter.getInstance(); + if (isUpgradePending + && inCallPresenter.getInCallState() == InCallPresenter.InCallState.INCOMING) { + Log.d(this, "declining upgrade request"); + //If there is incoming call reject upgrade request + inCallPresenter.declineUpgradeRequest(getUi().getContext()); + } else if (isUpgradePending) { + Log.d(this, "process upgrade request as no MT call"); + processVideoUpgradeRequestCall(call); + } + } + private void processIncomingCall(Call call) { mCallId = call.getId(); mCall = call; @@ -102,28 +140,71 @@ public class AnswerPresenter extends Presenter } private void processVideoUpgradeRequestCall(Call call) { + Log.d(this, " processVideoUpgradeRequestCall call=" + call); mCallId = call.getId(); mCall = call; // Listen for call updates for the current call. CallList.getInstance().addCallUpdateListener(mCallId, this); - getUi().showAnswerUi(true); - getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST); + final int currentVideoState = call.getVideoState(); + final int modifyToVideoState = call.getModifyToVideoState(); + + if (currentVideoState == modifyToVideoState) { + Log.w(this, "processVideoUpgradeRequestCall: Video states are same. Return."); + return; + } + + AnswerUi ui = getUi(); + + if (ui == null) { + Log.e(this, "Ui is null. Can't process upgrade request"); + return; + } + ui.showAnswerUi(true); + ui.showTargets(getUiTarget(currentVideoState, modifyToVideoState)); + + } + + private int getUiTarget(int currentVideoState, int modifyToVideoState) { + if (showVideoUpgradeOptions(currentVideoState, modifyToVideoState)) { + return AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST; + } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.BIDIRECTIONAL)) { + return AnswerFragment.TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST; + } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.TX_ENABLED)) { + return AnswerFragment.TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST; + } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.RX_ENABLED)) { + return AnswerFragment.TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST; + } + return AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST; + } + + private boolean showVideoUpgradeOptions(int currentVideoState, int modifyToVideoState) { + return currentVideoState == VideoProfile.VideoState.AUDIO_ONLY && + isEnabled(modifyToVideoState, VideoProfile.VideoState.BIDIRECTIONAL); + } + + private boolean isEnabled(int videoState, int mask) { + return (videoState & mask) == mask; } @Override public void onCallChanged(Call call) { Log.d(this, "onCallStateChange() " + call + " " + this); if (call.getState() != Call.State.INCOMING) { - // Stop listening for updates. - CallList.getInstance().removeCallUpdateListener(mCallId, this); + boolean isUpgradePending = isVideoUpgradePending(call); + if (!isUpgradePending) { + // Stop listening for updates. + CallList.getInstance().removeCallUpdateListener(mCallId, this); + } - getUi().showAnswerUi(false); + final Call incall = CallList.getInstance().getIncomingCall(); + if (incall != null || isUpgradePending) { + getUi().showAnswerUi(true); + } else { + getUi().showAnswerUi(false); + } - // mCallId will hold the state of the call. We don't clear the mCall variable here as - // it may be useful for sending text messages after phone disconnects. - mCallId = null; mHasTextMessages = false; } else if (!mHasTextMessages) { final List textMsgs = CallList.getInstance().getTextResponses(call.getId()); @@ -134,14 +215,14 @@ public class AnswerPresenter extends Presenter } public void onAnswer(int videoState, Context context) { + Log.d(this, "onAnswer mCallId=" + mCallId + " videoState=" + videoState); if (mCallId == null) { return; } - Log.d(this, "onAnswer " + mCallId); if (mCall.getSessionModificationState() == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { - InCallPresenter.getInstance().acceptUpgradeRequest(context); + InCallPresenter.getInstance().acceptUpgradeRequest(videoState, context); } else { TelecomAdapter.getInstance().answerCall(mCall.getId(), videoState); } @@ -151,9 +232,14 @@ public class AnswerPresenter extends Presenter * TODO: We are using reject and decline interchangeably. We should settle on * reject since it seems to be more prevalent. */ - public void onDecline() { + public void onDecline(Context context) { Log.d(this, "onDecline " + mCallId); - TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null); + if (mCall.getSessionModificationState() + == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { + InCallPresenter.getInstance().declineUpgradeRequest(context); + } else { + TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null); + } } public void onText() { diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java index fd06e53b3..3d6f2032e 100644 --- a/InCallUI/src/com/android/incallui/Call.java +++ b/InCallUI/src/com/android/incallui/Call.java @@ -17,8 +17,10 @@ package com.android.incallui; import com.android.contacts.common.CallUtil; +import com.android.incallui.CallList.Listener; import android.content.Context; +import android.hardware.camera2.CameraCharacteristics; import android.net.Uri; import android.telecom.CallProperties; import android.telecom.DisconnectCause; @@ -118,8 +120,51 @@ public final class Call { public static final int WAITING_FOR_RESPONSE = 1; public static final int REQUEST_FAILED = 2; public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3; + public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4; + } + + public static class VideoSettings { + public static final int CAMERA_DIRECTION_UNKNOWN = -1; + public static final int CAMERA_DIRECTION_FRONT_FACING = + CameraCharacteristics.LENS_FACING_FRONT; + public static final int CAMERA_DIRECTION_BACK_FACING = + CameraCharacteristics.LENS_FACING_BACK; + + private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN; + + /** + * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, + * the video state of the call should be used to infer the camera direction. + * + * @see {@link CameraCharacteristics#LENS_FACING_FRONT} + * @see {@link CameraCharacteristics#LENS_FACING_BACK} + */ + public void setCameraDir(int cameraDirection) { + if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING + || cameraDirection == CAMERA_DIRECTION_BACK_FACING) { + mCameraDirection = cameraDirection; + } else { + mCameraDirection = CAMERA_DIRECTION_UNKNOWN; + } + } + + /** + * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, + * the video state of the call should be used to infer the camera direction. + * + * @see {@link CameraCharacteristics#LENS_FACING_FRONT} + * @see {@link CameraCharacteristics#LENS_FACING_BACK} + */ + public int getCameraDir() { + return mCameraDirection; + } + + public String toString() { + return "(CameraDir:" + getCameraDir() + ")"; + } } + private static final String ID_PREFIX = Call.class.getSimpleName() + "_"; private static int sIdCounter = 0; @@ -127,12 +172,16 @@ public final class Call { new android.telecom.Call.Listener() { @Override public void onStateChanged(android.telecom.Call call, int newState) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + " newState=" + + newState); update(); } @Override public void onParentChanged(android.telecom.Call call, android.telecom.Call newParent) { + Log.d(this, "TelecommCallListener onParentChanged call=" + call + " newParent=" + + newParent); update(); } @@ -145,29 +194,38 @@ public final class Call { @Override public void onDetailsChanged(android.telecom.Call call, android.telecom.Call.Details details) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + " details=" + + details); update(); } @Override public void onCannedTextResponsesLoaded(android.telecom.Call call, List cannedTextResponses) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + + " cannedTextResponses=" + cannedTextResponses); update(); } @Override public void onPostDialWait(android.telecom.Call call, String remainingPostDialSequence) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + + " remainingPostDialSequence=" + remainingPostDialSequence); update(); } @Override public void onVideoCallChanged(android.telecom.Call call, VideoCall videoCall) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + " videoCall=" + + videoCall); update(); } @Override public void onCallDestroyed(android.telecom.Call call) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call); call.removeListener(mTelecommCallListener); } @@ -184,6 +242,11 @@ public final class Call { private DisconnectCause mDisconnectCause; private int mSessionModificationState; private final List mChildCallIds = new ArrayList<>(); + private final VideoSettings mVideoSettings = new VideoSettings(); + /** + * mModifyToVideoState is used to store requested upgrade / downgrade video state + */ + private int mModifyToVideoState = VideoProfile.VideoState.AUDIO_ONLY; private InCallVideoCallListener mVideoCallListener; @@ -198,6 +261,14 @@ public final class Call { return mTelecommCall; } + /** + * @return video settings of the call, null if the call is not a video call. + * @see VideoProfile + */ + public VideoSettings getVideoSettings() { + return mVideoSettings; + } + private void update() { int oldState = getState(); updateFromTelecommCall(); @@ -209,7 +280,7 @@ public final class Call { } private void updateFromTelecommCall() { - Log.d(this, "updateFromTelecommCall: " + mTelecommCall); + Log.d(this, "updateFromTelecommCall: " + mTelecommCall.toString()); setState(translateState(mTelecommCall.getState())); setDisconnectCause(mTelecommCall.getDetails().getDisconnectCause()); @@ -370,24 +441,69 @@ public final class Call { return mTelecommCall.getDetails().getVideoState(); } + public int getCallSubstate() { + return mTelecommCall.getDetails().getCallSubstate(); + } + public boolean isVideoCall(Context context) { - // We want to show Video call buttons even if only one direction is enabled - // (That is what is happening when we receive a video call for example) - return CallUtil.isVideoEnabled(context) && ( - VideoProfile.VideoState.isBidirectional(getVideoState()) || - VideoProfile.VideoState.isReceptionEnabled(getVideoState()) || - VideoProfile.VideoState.isTransmissionEnabled(getVideoState())); + return CallUtil.isVideoEnabled(context) && + VideoProfile.VideoState.isVideo(getVideoState()); + } + + /** + * This method is called when we request for a video upgrade or downgrade. This handles the + * session modification state RECEIVED_UPGRADE_TO_VIDEO_REQUEST and sets the video state we + * want to upgrade/downgrade to. + */ + public void setSessionModificationTo(int videoState) { + Log.d(this, "setSessionModificationTo - video state= " + videoState); + if (videoState == getVideoState()) { + mSessionModificationState = Call.SessionModificationState.NO_REQUEST; + Log.w(this,"setSessionModificationTo - Clearing session modification state"); + } else { + mSessionModificationState = + Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; + setModifyToVideoState(videoState); + CallList.getInstance().onUpgradeToVideo(this); + } + + Log.d(this, "setSessionModificationTo - mSessionModificationState=" + + mSessionModificationState + " video state= " + videoState); + update(); } + /** + * This method is called to handle any other session modification states other than + * RECEIVED_UPGRADE_TO_VIDEO_REQUEST. We set the modification state and reset the video state + * when an upgrade request has been completed or failed. + */ public void setSessionModificationState(int state) { + if (state == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { + Log.e(this, + "setSessionModificationState not to be called for RECEIVED_UPGRADE_TO_VIDEO_REQUEST"); + return; + } + boolean hasChanged = mSessionModificationState != state; mSessionModificationState = state; - + Log.d(this, "setSessionModificationState " + state + " mSessionModificationState=" + + mSessionModificationState); + if (state != Call.SessionModificationState.WAITING_FOR_RESPONSE) { + setModifyToVideoState(VideoProfile.VideoState.AUDIO_ONLY); + } if (hasChanged) { update(); } } + private void setModifyToVideoState(int newVideoState) { + mModifyToVideoState = newVideoState; + } + + public int getModifyToVideoState() { + return mModifyToVideoState; + } + public static boolean areSame(Call call1, Call call2) { if (call1 == null && call2 == null) { return true; @@ -406,7 +522,7 @@ public final class Call { @Override public String toString() { return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " + - "videoState:%d]", + "videoState:%d, callSubState:%d, mSessionModificationState:%d, VideoSettings:%s]", mId, State.toString(getState()), android.telecom.Call.Details @@ -414,6 +530,13 @@ public final class Call { mChildCallIds, getParentId(), this.mTelecommCall.getConferenceableCalls(), - mTelecommCall.getDetails().getVideoState()); + mTelecommCall.getDetails().getVideoState(), + mTelecommCall.getDetails().getCallSubstate(), + mSessionModificationState, + getVideoSettings()); + } + + public String toSimpleString() { + return super.toString(); } } diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java index b2d109a6a..8682d65e5 100644 --- a/InCallUI/src/com/android/incallui/CallButtonFragment.java +++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java @@ -16,7 +16,9 @@ package com.android.incallui; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -26,6 +28,8 @@ import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.StateListDrawable; import android.os.Bundle; import android.telecom.AudioState; +import android.telecom.TelecomManager; +import android.telecom.VideoProfile; import android.view.ContextThemeWrapper; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; @@ -36,11 +40,13 @@ import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.PopupMenu; +import android.widget.Toast; import android.widget.PopupMenu.OnDismissListener; import android.widget.PopupMenu.OnMenuItemClickListener; import com.android.contacts.common.util.MaterialColorMapUtils; import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; +import java.util.ArrayList; /** * Fragment for call control buttons @@ -50,6 +56,7 @@ public class CallButtonFragment implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener, View.OnClickListener { private CompoundButton mAudioButton; + private static final int INVALID_INDEX = -1; private ImageButton mChangeToVoiceButton; private CompoundButton mMuteButton; private CompoundButton mShowDialpadButton; @@ -61,6 +68,7 @@ public class CallButtonFragment private ImageButton mMergeButton; private CompoundButton mPauseVideoButton; private ImageButton mOverflowButton; + private ImageButton mManageVideoCallConferenceButton; private PopupMenu mAudioModePopup; private boolean mAudioModePopupVisible; @@ -120,7 +128,9 @@ public class CallButtonFragment mPauseVideoButton.setOnClickListener(this); mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton); mOverflowButton.setOnClickListener(this); - + mManageVideoCallConferenceButton = (ImageButton) parent.findViewById( + R.id.manageVideoCallConferenceButton); + mManageVideoCallConferenceButton.setOnClickListener(this); return parent; } @@ -156,7 +166,8 @@ public class CallButtonFragment getPresenter().addCallClicked(); break; case R.id.changeToVoiceButton: - getPresenter().changeToVoiceClicked(); + // STOPSHIP One way video options + getPresenter().displayModifyCallOptions(); break; case R.id.muteButton: { getPresenter().muteClicked(!mMuteButton.isSelected()); @@ -177,7 +188,8 @@ public class CallButtonFragment getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected()); break; case R.id.changeToVideoButton: - getPresenter().changeToVideoClicked(); + // STOPSHIP One way video options + getPresenter().displayModifyCallOptions(); break; case R.id.switchCameraButton: getPresenter().switchCameraClicked( @@ -190,6 +202,9 @@ public class CallButtonFragment case R.id.overflowButton: mOverflowPopup.show(); break; + case R.id.manageVideoCallConferenceButton: + onManageVideoCallConferenceClicked(); + break; default: isClickHandled = false; Log.wtf(this, "onClick: unexpected"); @@ -329,6 +344,7 @@ public class CallButtonFragment mMergeButton.setEnabled(isEnabled); mPauseVideoButton.setEnabled(isEnabled); mOverflowButton.setEnabled(isEnabled); + mManageVideoCallConferenceButton.setEnabled(isEnabled); } @Override @@ -406,6 +422,10 @@ public class CallButtonFragment mAddCallButton.setVisibility(show ? View.VISIBLE : View.GONE); } + public void showManageConferenceVideoCallButton(boolean show) { + mManageVideoCallConferenceButton.setVisibility(show ? View.VISIBLE : View.GONE); + } + @Override public void showMergeButton(boolean show) { mMergeButton.setVisibility(show ? View.VISIBLE : View.GONE); @@ -431,9 +451,83 @@ public class CallButtonFragment mOverflowButton.setVisibility(show ? View.VISIBLE : View.GONE); } + /**The function is called when Modify Call button gets pressed. The function creates and + * displays modify call options. + */ + public void displayModifyCallOptions() { + CallButtonPresenter.CallButtonUi ui = getUi(); + if (ui == null) { + Log.e(this, "Cannot display ModifyCallOptions as ui is null"); + return; + } + + Context context = getContext(); + if (isTtyModeEnabled()) { + Toast.makeText(context, context.getResources().getString( + R.string.video_call_not_allowed_if_tty_enabled), + Toast.LENGTH_SHORT).show(); + return; + } + + final ArrayList items = new ArrayList(); + final ArrayList itemToCallType = new ArrayList(); + final Resources res = ui.getContext().getResources(); + // Prepare the string array and mapping. + items.add(res.getText(R.string.modify_call_option_voice)); + itemToCallType.add(VideoProfile.VideoState.AUDIO_ONLY); + + items.add(res.getText(R.string.modify_call_option_vt_rx)); + itemToCallType.add(VideoProfile.VideoState.RX_ENABLED); + + items.add(res.getText(R.string.modify_call_option_vt_tx)); + itemToCallType.add(VideoProfile.VideoState.TX_ENABLED); + + items.add(res.getText(R.string.modify_call_option_vt)); + itemToCallType.add(VideoProfile.VideoState.BIDIRECTIONAL); + + AlertDialog.Builder builder = new AlertDialog.Builder(getUi().getContext()); + builder.setTitle(R.string.modify_call_option_title); + final AlertDialog alert; + + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + Toast.makeText(getUi().getContext(), items.get(item), Toast.LENGTH_SHORT).show(); + final int selCallType = itemToCallType.get(item); + Log.v(this, "Videocall: ModifyCall: upgrade/downgrade to " + + fromCallType(selCallType)); + VideoProfile videoProfile = new VideoProfile(selCallType); + getPresenter().changeToVideoClicked(videoProfile); + dialog.dismiss(); + } + }; + int currVideoState = getPresenter().getCurrentVideoState(); + int currUnpausedVideoState = CallUtils.getUnPausedVideoState(currVideoState); + int index = itemToCallType.indexOf(currUnpausedVideoState); + if (index == INVALID_INDEX) { + return; + } + builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), index, listener); + alert = builder.create(); + alert.show(); + } + + public static String fromCallType(int callType) { + switch (callType) { + case VideoProfile.VideoState.BIDIRECTIONAL: + return "VT"; + case VideoProfile.VideoState.TX_ENABLED: + return "VT_TX"; + case VideoProfile.VideoState.RX_ENABLED: + return "VT_RX"; + } + return ""; + } + @Override public void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption, - boolean showHoldMenuOption, boolean showSwapMenuOption) { + boolean showHoldMenuOption, boolean showSwapMenuOption, + boolean showManageConferenceVideoCallOption) { if (mOverflowPopup == null) { final ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), R.style.InCallPopupMenuStyle); @@ -459,6 +553,9 @@ public class CallButtonFragment case R.id.overflow_swap_menu_item: getPresenter().addCallClicked(); break; + case R.id.overflow_manage_conference_menu_item: + onManageVideoCallConferenceClicked(); + break; default: Log.wtf(this, "onMenuItemClick: unexpected overflow menu click"); break; @@ -482,6 +579,8 @@ public class CallButtonFragment menu.findItem(R.id.overflow_resume_menu_item).setVisible( showHoldMenuOption && mHoldButton.isSelected()); menu.findItem(R.id.overflow_swap_menu_item).setVisible(showSwapMenuOption); + menu.findItem(R.id.overflow_manage_conference_menu_item).setVisible( + showManageConferenceVideoCallOption); mOverflowButton.setEnabled(menu.hasVisibleItems()); } @@ -561,6 +660,11 @@ public class CallButtonFragment } } + private void onManageVideoCallConferenceClicked() { + Log.d(this, "onManageVideoCallConferenceClicked"); + InCallPresenter.getInstance().showConferenceCallManager(true); + } + /** * Refreshes the "Audio mode" popup if it's visible. This is useful * (for example) when a wired headset is plugged or unplugged, @@ -789,4 +893,11 @@ public class CallButtonFragment public Context getContext() { return getActivity(); } + + private boolean isTtyModeEnabled() { + return (android.provider.Settings.Secure.getInt( + getContext().getContentResolver(), + android.provider.Settings.Secure.PREFERRED_TTY_MODE, + TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF); + } } diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java index 9fedc95b1..4e840be9f 100644 --- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java +++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java @@ -16,6 +16,7 @@ package com.android.incallui; +import android.app.AlertDialog; import android.content.Context; import android.os.Bundle; import android.telecom.AudioState; @@ -23,6 +24,7 @@ import android.telecom.InCallService.VideoCall; import android.telecom.VideoProfile; import com.android.incallui.AudioModeProvider.AudioModeListener; +import com.android.incallui.InCallCameraManager.Listener; import com.android.incallui.InCallPresenter.CanAddCallListener; import com.android.incallui.InCallPresenter.InCallState; import com.android.incallui.InCallPresenter.InCallStateListener; @@ -36,7 +38,7 @@ import java.util.Objects; */ public class CallButtonPresenter extends Presenter 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 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 return false; } - return mPrimary.can(Details.CAPABILITY_MANAGE_CONFERENCE); + return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) + && !mPrimary.isVideoCall(mContext); } private void setCallbackNumber() { diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java index db43b1657..f2d04cf11 100644 --- a/InCallUI/src/com/android/incallui/CallList.java +++ b/InCallUI/src/com/android/incallui/CallList.java @@ -72,7 +72,9 @@ public class CallList implements InCallPhoneListener { @Override public void onCallAdded(Phone phone, android.telecom.Call telecommCall) { Call call = new Call(telecommCall); - if (call.getState() == Call.State.INCOMING) { + Log.d(this, "onCallAdded: callState=" + call.getState()); + if (call.getState() == Call.State.INCOMING || + call.getState() == Call.State.CALL_WAITING) { onIncoming(call, call.getCannedSmsResponses()); } else { onUpdate(call); @@ -135,6 +137,12 @@ public class CallList implements InCallPhoneListener { } } + public void onUpgradeToVideo(Call call){ + Log.d(this, "onUpgradeToVideo call=" + call); + for (Listener listener : mListeners) { + listener.onUpgradeToVideo(call); + } + } /** * Called when a single call has changed. */ @@ -539,7 +547,11 @@ public class CallList implements InCallPhoneListener { * incoming calls. */ public void onIncomingCall(Call call); - + /** + * Called when a new modify call request comes in + * This is the only method that gets called for modify requests. + */ + public void onUpgradeToVideo(Call call); /** * Called anytime there are changes to the call list. The change can be switching call * states, updating information, etc. This method will NOT be called for new incoming diff --git a/InCallUI/src/com/android/incallui/CallUtils.java b/InCallUI/src/com/android/incallui/CallUtils.java new file mode 100644 index 000000000..80b553aa7 --- /dev/null +++ b/InCallUI/src/com/android/incallui/CallUtils.java @@ -0,0 +1,90 @@ +/* Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import android.telecom.VideoProfile; + +import com.google.common.base.Preconditions; + +public class CallUtils { + + public static boolean isVideoCall(Call call) { + return call != null && VideoProfile.VideoState.isVideo(call.getVideoState()); + } + + public static boolean isIncomingVideoCall(Call call) { + if (!CallUtils.isVideoCall(call)) { + return false; + } + final int state = call.getState(); + return (state == Call.State.INCOMING) || (state == Call.State.CALL_WAITING); + } + + public static boolean isActiveVideoCall(Call call) { + return CallUtils.isVideoCall(call) && call.getState() == Call.State.ACTIVE; + } + + public static boolean isOutgoingVideoCall(Call call) { + if (!CallUtils.isVideoCall(call)) { + return false; + } + final int state = call.getState(); + return Call.State.isDialing(state) || state == Call.State.CONNECTING + || state == Call.State.PRE_DIAL_WAIT; + } + + public static boolean isAudioCall(Call call) { + return call != null && VideoProfile.VideoState.isAudioOnly(call.getVideoState()); + } + + // TODO (ims-vt) Check if special handling is needed for CONF calls. + public static boolean canVideoPause(Call call) { + return isVideoCall(call) && call.getState() == Call.State.ACTIVE; + } + + public static VideoProfile makeVideoPauseProfile(Call call) { + Preconditions.checkNotNull(call); + Preconditions.checkState(!VideoProfile.VideoState.isAudioOnly(call.getVideoState())); + return new VideoProfile(getPausedVideoState(call.getVideoState())); + } + + public static VideoProfile makeVideoUnPauseProfile(Call call) { + Preconditions.checkNotNull(call); + return new VideoProfile(getUnPausedVideoState(call.getVideoState())); + } + + public static int getUnPausedVideoState(int videoState) { + return videoState & (~VideoProfile.VideoState.PAUSED); + } + + public static int getPausedVideoState(int videoState) { + return videoState | VideoProfile.VideoState.PAUSED; + } + +} diff --git a/InCallUI/src/com/android/incallui/GlowPadWrapper.java b/InCallUI/src/com/android/incallui/GlowPadWrapper.java index b50fdd8c2..584ce65de 100644 --- a/InCallUI/src/com/android/incallui/GlowPadWrapper.java +++ b/InCallUI/src/com/android/incallui/GlowPadWrapper.java @@ -108,7 +108,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger @Override public void onTrigger(View v, int target) { - Log.d(this, "onTrigger()"); + Log.d(this, "onTrigger() view=" + v + " target=" + target); final int resId = getResourceIdForTarget(target); switch (resId) { case R.drawable.ic_lockscreen_answer: @@ -116,7 +116,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger mTargetTriggered = true; break; case R.drawable.ic_lockscreen_decline: - mAnswerListener.onDecline(); + mAnswerListener.onDecline(getContext()); mTargetTriggered = true; break; case R.drawable.ic_lockscreen_text: @@ -128,6 +128,14 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger mAnswerListener.onAnswer(VideoProfile.VideoState.BIDIRECTIONAL, getContext()); mTargetTriggered = true; break; + case R.drawable.ic_lockscreen_answer_tx_video: + mAnswerListener.onAnswer(VideoProfile.VideoState.TX_ENABLED, getContext()); + mTargetTriggered = true; + break; + case R.drawable.ic_lockscreen_answer_rx_video: + mAnswerListener.onAnswer(VideoProfile.VideoState.RX_ENABLED, getContext()); + mTargetTriggered = true; + break; case R.drawable.ic_toolbar_video_off: InCallPresenter.getInstance().declineUpgradeRequest(getContext()); mTargetTriggered = true; @@ -154,7 +162,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger public interface AnswerListener { void onAnswer(int videoState, Context context); - void onDecline(); + void onDecline(Context context); void onText(); } } diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java index f75c33b23..873c288a4 100644 --- a/InCallUI/src/com/android/incallui/InCallActivity.java +++ b/InCallUI/src/com/android/incallui/InCallActivity.java @@ -105,10 +105,9 @@ public class InCallActivity extends Activity { }; /** - * Stores the current orientation of the activity. Used to determine if a change in orientation - * has occurred. + * Used to determine if a change in orientation has occurred. */ - private int mCurrentOrientation; + private static int sCurrentOrientation = Configuration.ORIENTATION_UNDEFINED; @Override protected void onCreate(Bundle icicle) { @@ -143,9 +142,8 @@ public class InCallActivity extends Activity { internalResolveIntent(getIntent()); - mCurrentOrientation = getResources().getConfiguration().orientation; - mIsLandscape = getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; + mIsLandscape = getResources().getConfiguration().orientation == + Configuration.ORIENTATION_LANDSCAPE; final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; @@ -193,6 +191,11 @@ public class InCallActivity extends Activity { // setting activity should be last thing in setup process InCallPresenter.getInstance().setActivity(this); + + // It is possible that the activity restarted because orientation changed. + // Notify listeners if orientation changed. + doOrientationChanged(getResources().getConfiguration().orientation); + InCallPresenter.getInstance().onActivityStarted(); } @Override @@ -244,6 +247,9 @@ public class InCallActivity extends Activity { @Override protected void onStop() { Log.d(this, "onStop()..."); + + InCallPresenter.getInstance().updateIsChangingConfigurations(); + InCallPresenter.getInstance().onActivityStopped(); super.onStop(); } @@ -251,6 +257,7 @@ public class InCallActivity extends Activity { protected void onDestroy() { Log.d(this, "onDestroy()... this = " + this); InCallPresenter.getInstance().unsetActivity(this); + InCallPresenter.getInstance().updateIsChangingConfigurations(); super.onDestroy(); } @@ -446,15 +453,22 @@ public class InCallActivity extends Activity { InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config); Log.d(this, "onConfigurationChanged "+config.orientation); + doOrientationChanged(config.orientation); + super.onConfigurationChanged(config); + } + + + private void doOrientationChanged(int orientation) { + Log.d(this, "doOrientationChanged prevOrientation=" + sCurrentOrientation + + " newOrientation=" + orientation); // Check to see if the orientation changed to prevent triggering orientation change events // for other configuration changes. - if (config.orientation != mCurrentOrientation) { - mCurrentOrientation = config.orientation; + if (orientation != sCurrentOrientation) { + sCurrentOrientation = orientation; InCallPresenter.getInstance().onDeviceRotationChange( getWindowManager().getDefaultDisplay().getRotation()); - InCallPresenter.getInstance().onDeviceOrientationChange(mCurrentOrientation); + InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation); } - super.onConfigurationChanged(config); } public CallButtonFragment getCallButtonFragment() { diff --git a/InCallUI/src/com/android/incallui/InCallApp.java b/InCallUI/src/com/android/incallui/InCallApp.java index d6f4f42de..a273d7805 100644 --- a/InCallUI/src/com/android/incallui/InCallApp.java +++ b/InCallUI/src/com/android/incallui/InCallApp.java @@ -81,7 +81,9 @@ public class InCallApp extends Application { } else if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) { InCallPresenter.getInstance().hangUpOngoingCall(context); } else if (action.equals(ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST)) { - InCallPresenter.getInstance().acceptUpgradeRequest(context); + //TODO: Change calltype after adding support for TX and RX + InCallPresenter.getInstance().acceptUpgradeRequest( + VideoProfile.VideoState.BIDIRECTIONAL, context); } else if (action.equals(ACTION_DECLINE_VIDEO_UPGRADE_REQUEST)) { InCallPresenter.getInstance().declineUpgradeRequest(context); } diff --git a/InCallUI/src/com/android/incallui/InCallCameraManager.java b/InCallUI/src/com/android/incallui/InCallCameraManager.java index ded9387f3..b7ec079af 100644 --- a/InCallUI/src/com/android/incallui/InCallCameraManager.java +++ b/InCallUI/src/com/android/incallui/InCallCameraManager.java @@ -25,12 +25,22 @@ import android.hardware.camera2.params.StreamConfigurationMap; import android.util.Size; import java.lang.String; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; /** * Used to track which camera is used for outgoing video. */ public class InCallCameraManager { + public interface Listener { + void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera); + } + + private final Set mCameraSelectionListeners = Collections. + newSetFromMap(new ConcurrentHashMap(8,0.9f,1)); + /** * The camera ID for the front facing camera. */ @@ -73,6 +83,9 @@ public class InCallCameraManager { */ public void setUseFrontFacingCamera(boolean useFrontFacingCamera) { mUseFrontFacingCamera = useFrontFacingCamera; + for (Listener listener : mCameraSelectionListeners) { + listener.onActiveCameraSelectionChanged(mUseFrontFacingCamera); + } } /** @@ -148,4 +161,16 @@ public class InCallCameraManager { } } } + + public void addCameraSelectionListener(Listener listener) { + if (listener != null) { + mCameraSelectionListeners.add(listener); + } + } + + public void removeCameraSelectionListener(Listener listener) { + if (listener != null) { + mCameraSelectionListeners.remove(listener); + } + } } diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java index 7c71d2c86..74c85db8a 100644 --- a/InCallUI/src/com/android/incallui/InCallPresenter.java +++ b/InCallUI/src/com/android/incallui/InCallPresenter.java @@ -34,6 +34,8 @@ import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.Surface; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import com.google.common.base.Preconditions; @@ -156,7 +158,6 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { */ private boolean mIsActivityPreviouslyStarted = false; - /** * Whether or not to wait for the circular reveal animation to be started, to avoid stopping * the circular reveal animation activity before the animation is initiated. @@ -175,6 +176,14 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { */ private boolean mServiceBound = false; + /** + * When configuration changes Android kills the current activity and starts a new one. + * The flag is used to check if full clean up is necessary (activity is stopped and new + * activity won't be started), or if a new activity will be started right after the current one + * is destroyed, and therefore no need in release all resources. + */ + private boolean mIsChangingConfigurations = false; + private Phone mPhone; private Handler mHandler = new Handler(); @@ -243,6 +252,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { // will kick off an update and the whole process can start. mCallList.addListener(this); + VideoPauseController.getInstance().setUp(this); + Log.d(this, "Finished InCallPresenter.setUp"); } @@ -258,6 +269,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { Log.d(this, "tearDown"); mServiceConnected = false; attemptCleanup(); + + VideoPauseController.getInstance().tearDown(); } private void attemptFinishActivity() { @@ -398,9 +411,12 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { if (callList == null) { return; } + Log.d(this, "onCallListChange callList=" + callList.toString() ); InCallState newState = getPotentialStateFromCallList(callList); InCallState oldState = mInCallState; + Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState); newState = startOrFinishUi(newState); + Log.d(this, "onCallListChange newState changed to " + newState); // Set the new state before announcing it to the world Log.i(this, "Phone switching state: " + oldState + " -> " + newState); @@ -437,6 +453,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } + @Override + public void onUpgradeToVideo(Call call) { + //NO-OP + } /** * Called when a call becomes disconnected. Called everytime an existing call * changes from being connected (incoming/outgoing/active) to disconnected. @@ -631,33 +651,36 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } - public void acceptUpgradeRequest(Context context) { + public void acceptUpgradeRequest(int videoState, Context context) { + Log.d(this, " acceptUpgradeRequest videoState " + videoState); // Bail if we have been shut down and the call list is null. if (mCallList == null) { StatusBarNotifier.clearInCallNotification(context); + Log.e(this, " acceptUpgradeRequest mCallList is empty so returning"); return; } Call call = mCallList.getVideoUpgradeRequestCall(); if (call != null) { - VideoProfile videoProfile = - new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL); + VideoProfile videoProfile = new VideoProfile(videoState); call.getVideoCall().sendSessionModifyResponse(videoProfile); call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); } } public void declineUpgradeRequest(Context context) { + Log.d(this, " declineUpgradeRequest"); // Bail if we have been shut down and the call list is null. if (mCallList == null) { StatusBarNotifier.clearInCallNotification(context); + Log.e(this, " declineUpgradeRequest mCallList is empty so returning"); return; } Call call = mCallList.getVideoUpgradeRequestCall(); if (call != null) { VideoProfile videoProfile = - new VideoProfile(VideoProfile.VideoState.AUDIO_ONLY); + new VideoProfile(call.getVideoState()); call.getVideoCall().sendSessionModifyResponse(videoProfile); call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); } @@ -685,6 +708,20 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { return mIsActivityPreviouslyStarted; } + public boolean isChangingConfigurations() { + return mIsChangingConfigurations; + } + + /*package*/ + void updateIsChangingConfigurations() { + mIsChangingConfigurations = false; + if (mInCallActivity != null) { + mIsChangingConfigurations = mInCallActivity.isChangingConfigurations(); + } + Log.d(this, "IsChangingConfigurations=" + mIsChangingConfigurations); + } + + /** * Called when the activity goes in/out of the foreground. */ @@ -716,6 +753,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { mIsActivityPreviouslyStarted = true; } else { CircularRevealActivity.sendClearDisplayBroadcast(mContext); + updateIsChangingConfigurations(); } for (InCallUiListener listener : mInCallUiListeners) { @@ -731,6 +769,26 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { return mInCallUiListeners.remove(listener); } + /*package*/ + void onActivityStarted() { + Log.d(this, "onActivityStarted"); + notifyVideoPauseController(true); + } + + /*package*/ + void onActivityStopped() { + Log.d(this, "onActivityStopped"); + notifyVideoPauseController(false); + } + + private void notifyVideoPauseController(boolean showing) { + Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" + + mIsChangingConfigurations); + if (!mIsChangingConfigurations) { + VideoPauseController.getInstance().onUiShowing(showing); + } + } + /** * Brings the app into the foreground if possible. */ @@ -1088,6 +1146,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { if (shouldCleanup) { mIsActivityPreviouslyStarted = false; + mIsChangingConfigurations = false; // blow away stale contact info so that we get fresh data on // the next set of calls @@ -1119,6 +1178,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { mListeners.clear(); mIncomingCallListeners.clear(); + mDetailsListeners.clear(); + mCanAddCallListeners.clear(); + mOrientationListeners.clear(); + mInCallEventListeners.clear(); Log.d(this, "Finished InCallPresenter.CleanUp"); } @@ -1252,7 +1315,20 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { * @param rotation The device rotation. */ public void onDeviceRotationChange(int rotation) { + Log.d(this, "onDeviceRotationChange: rotation=" + rotation); // First translate to rotation in degrees. + if (mCallList != null) { + mCallList.notifyCallsOfDeviceRotation(toRotationAngle(rotation)); + } else { + Log.w(this, "onDeviceRotationChange: CallList is null."); + } + } + + /** + * Converts rotation constants to rotation in degrees. + * @param rotation Rotation constants. + */ + public static int toRotationAngle(int rotation) { int rotationAngle; switch (rotation) { case Surface.ROTATION_0: @@ -1270,8 +1346,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { default: rotationAngle = 0; } - - mCallList.notifyCallsOfDeviceRotation(rotationAngle); + return rotationAngle; } /** @@ -1292,6 +1367,11 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { * and landscape. {@Code False} if the in-call UI should be locked in portrait. */ public void setInCallAllowsOrientationChange(boolean allowOrientationChange) { + if (mInCallActivity == null) { + Log.e(this, "InCallActivity is null. Can't set requested orientation."); + return; + } + if (!allowOrientationChange) { mInCallActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); } else { @@ -1299,6 +1379,21 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } + public void enableScreenTimeout(boolean enable) { + Log.v(this, "enableScreenTimeout: value=" + enable); + if (mInCallActivity == null) { + Log.e(this, "enableScreenTimeout: InCallActivity is null."); + return; + } + + final Window window = mInCallActivity.getWindow(); + if (enable) { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + /** * Returns the space available beside the call card. * diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java index 245fc7316..cf2b859e0 100644 --- a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java +++ b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java @@ -18,6 +18,7 @@ package com.android.incallui; import android.telecom.CameraCapabilities; import android.telecom.Connection; +import android.telecom.Connection.VideoProvider; import android.telecom.InCallService.VideoCall; import android.telecom.VideoProfile; @@ -47,53 +48,50 @@ public class InCallVideoCallListener extends VideoCall.Listener { */ @Override public void onSessionModifyRequestReceived(VideoProfile videoProfile) { - int previousVideoState = mCall.getVideoState(); - int newVideoState = videoProfile.getVideoState(); + Log.d(this, " onSessionModifyRequestReceived videoProfile=" + videoProfile); + int previousVideoState = CallUtils.getUnPausedVideoState(mCall.getVideoState()); + int newVideoState = CallUtils.getUnPausedVideoState(videoProfile.getVideoState()); - boolean wasVideoCall = VideoProfile.VideoState.isBidirectional(previousVideoState); - boolean isVideoCall = VideoProfile.VideoState.isBidirectional(newVideoState); - - boolean wasPaused = VideoProfile.VideoState.isPaused(previousVideoState); - boolean isPaused = VideoProfile.VideoState.isPaused(newVideoState); + boolean wasVideoCall = VideoProfile.VideoState.isVideo(previousVideoState); + boolean isVideoCall = VideoProfile.VideoState.isVideo(newVideoState); // Check for upgrades to video and downgrades to audio. - if (!wasVideoCall && isVideoCall) { - InCallVideoCallListenerNotifier.getInstance().upgradeToVideoRequest(mCall); - } else if (wasVideoCall && !isVideoCall) { + if (wasVideoCall && !isVideoCall) { InCallVideoCallListenerNotifier.getInstance().downgradeToAudio(mCall); + } else if (previousVideoState != newVideoState) { + InCallVideoCallListenerNotifier.getInstance().upgradeToVideoRequest(mCall, + newVideoState); } - - boolean pause = !wasPaused && isPaused; - InCallVideoCallListenerNotifier.getInstance().peerPausedStateChanged(mCall, pause); } /** * Handles a session modification response. * - * @param status Status of the session modify request. Valid values are - * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, - * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, - * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID} + * @param status Status of the session modify request. Valid values are + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID} * @param requestedProfile * @param responseProfile The actual profile changes made by the peer device. */ @Override - public void onSessionModifyResponseReceived( - int status, VideoProfile requestedProfile, VideoProfile responseProfile) { - boolean modifySucceeded = - requestedProfile.getVideoState() == responseProfile.getVideoState(); - boolean isVideoCall = - VideoProfile.VideoState.isBidirectional(responseProfile.getVideoState()); - - if (modifySucceeded && isVideoCall) { - // Local Upgrade success - InCallVideoCallListenerNotifier.getInstance().upgradeToVideoSuccess(mCall); - } else if (!modifySucceeded || status != Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { - // Remote didn't accept invitation in bidirectional state or failure - InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(mCall); - } else if (modifySucceeded && !isVideoCall) { - // Local Downgrade success (should always be successful) - InCallVideoCallListenerNotifier.getInstance().downgradeToAudio(mCall); + public void onSessionModifyResponseReceived(int status, VideoProfile requestedProfile, + VideoProfile responseProfile) { + Log.d(this, "onSessionModifyResponseReceived status=" + status + " requestedProfile=" + + requestedProfile + " responseProfile=" + responseProfile); + if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { + InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(status, mCall); + } else if (requestedProfile != null && responseProfile != null) { + boolean modifySucceeded = requestedProfile.getVideoState() == + responseProfile.getVideoState(); + boolean isVideoCall = VideoProfile.VideoState.isVideo(responseProfile.getVideoState()); + if (modifySucceeded && isVideoCall) { + InCallVideoCallListenerNotifier.getInstance().upgradeToVideoSuccess(mCall); + } else if (!modifySucceeded) { + InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(status, mCall); + } + } else { + Log.d(this, "onSessionModifyResponseReceived request and response Profiles are null"); } } @@ -104,6 +102,7 @@ public class InCallVideoCallListener extends VideoCall.Listener { */ @Override public void onCallSessionEvent(int event) { + InCallVideoCallListenerNotifier.getInstance().callSessionEvent(event); } /** @@ -117,6 +116,16 @@ public class InCallVideoCallListener extends VideoCall.Listener { InCallVideoCallListenerNotifier.getInstance().peerDimensionsChanged(mCall, width, height); } + /** + * 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. @@ -124,7 +133,9 @@ public class InCallVideoCallListener extends VideoCall.Listener { * @param dataUsage The updated data usage. */ @Override - public void onCallDataUsageChanged(int dataUsage) { + public void onCallDataUsageChanged(long dataUsage) { + Log.d(this, "onCallDataUsageChanged: dataUsage = " + dataUsage); + InCallVideoCallListenerNotifier.getInstance().callDataUsageChanged(dataUsage); } /** diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java b/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java index 9f3f062cd..818ed032a 100644 --- a/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java +++ b/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java @@ -123,12 +123,13 @@ public class InCallVideoCallListenerNotifier { /** * Inform listeners of an upgrade to video request for a call. - * * @param call The call. + * @param videoState The video state we want to upgrade to. */ - public void upgradeToVideoRequest(Call call) { + public void upgradeToVideoRequest(Call call, int videoState) { + Log.d(this, "upgradeToVideoRequest call = " + call + " new video state = " + videoState); for (SessionModificationListener listener : mSessionModificationListeners) { - listener.onUpgradeToVideoRequest(call); + listener.onUpgradeToVideoRequest(call, videoState); } } @@ -148,9 +149,9 @@ public class InCallVideoCallListenerNotifier { * * @param call The call. */ - public void upgradeToVideoFail(Call call) { + public void upgradeToVideoFail(int status, Call call) { for (SessionModificationListener listener : mSessionModificationListeners) { - listener.onUpgradeToVideoFail(call); + listener.onUpgradeToVideoFail(status, call); } } @@ -165,6 +166,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. * @@ -177,6 +189,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. * @@ -203,6 +227,17 @@ public class InCallVideoCallListenerNotifier { } } + /** + * Inform listeners of a change to call data usage. + * + * @param dataUsage data usage value + */ + public void callDataUsageChanged(long dataUsage) { + for (VideoEventListener listener : mVideoEventListeners) { + listener.onCallDataUsageChange(dataUsage); + } + } + /** * Listener interface for any class that wants to be notified of upgrade to video and downgrade * to audio session modification requests. @@ -212,8 +247,9 @@ public class InCallVideoCallListenerNotifier { * Called when a peer request is received to upgrade an audio-only call to a video call. * * @param call The call the request was received for. + * @param videoState The video state that the request wants to upgrade to. */ - public void onUpgradeToVideoRequest(Call call); + public void onUpgradeToVideoRequest(Call call, int videoState); /** * Called when a request to a peer to upgrade an audio-only call to a video call is @@ -230,7 +266,7 @@ public class InCallVideoCallListenerNotifier { * * @param call The call the request was successful for. */ - public void onUpgradeToVideoFail(Call call); + public void onUpgradeToVideoFail(int status, Call call); /** * Called when a call has been downgraded to audio-only. @@ -242,7 +278,7 @@ public class InCallVideoCallListenerNotifier { /** * Listener interface for any class that wants to be notified of video events, including pause - * and un-pause of peer video. + * and un-pause of peer video, video quality changes. */ public interface VideoEventListener { /** @@ -253,6 +289,29 @@ public class InCallVideoCallListenerNotifier { * otherwise. */ public void onPeerPauseStateChanged(Call call, boolean paused); + + /** + * Called when the video quality changes. + * + * @param call The call whose video quality changes. + * @param videoCallQuality - values are QUALITY_HIGH, MEDIUM, LOW and UNKNOWN. + */ + public void onVideoQualityChanged(Call call, int videoCallQuality); + + /* + * Called when call data usage value is requested or when call data usage value is updated + * because of a call state change + * + * @param dataUsage call data usage value + */ + public void onCallDataUsageChange(long dataUsage); + + /** + * Called when call session event is raised. + * + * @param event The call session event. + */ + public void onCallSessionEvent(int event); } /** diff --git a/InCallUI/src/com/android/incallui/Log.java b/InCallUI/src/com/android/incallui/Log.java index 07a0e61ca..5bc74b1a6 100644 --- a/InCallUI/src/com/android/incallui/Log.java +++ b/InCallUI/src/com/android/incallui/Log.java @@ -31,7 +31,7 @@ public class Log { // Generic tag for all In Call logging public static final String TAG = "InCall"; - public static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */ + public static final boolean FORCE_DEBUG = true; /* STOPSHIP if true */ public static final boolean DEBUG = FORCE_DEBUG || android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); public static final boolean VERBOSE = FORCE_DEBUG || diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java index 7859a17bc..c4d10c569 100644 --- a/InCallUI/src/com/android/incallui/VideoCallFragment.java +++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java @@ -16,10 +16,14 @@ package com.android.incallui; +import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.SurfaceTexture; import android.os.Bundle; +import android.telecom.Connection; +import android.telecom.VideoProfile; import android.view.Display; import android.view.LayoutInflater; import android.view.Surface; @@ -28,6 +32,9 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.view.ViewTreeObserver; +import android.widget.Toast; + +import com.google.common.base.Objects; /** * Fragment containing video calling surfaces. @@ -50,11 +57,23 @@ public class VideoCallFragment extends BaseFragment 0 && height > 0) { + setDisplayVideoSize(width, height); + } + } + + /** + * Handles any video quality changes in the call. + * + * @param call The call which experienced a video quality change. + * @param videoQuality The new video call quality. + */ + @Override + public void onVideoQualityChanged(Call call, int videoQuality) { + if (!call.equals(mPrimaryCall)) { + return; + } + + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "Error VideoCallUi is null. Return."); + return; + } + + // Display a video quality changed message on UI. + ui.showVideoQualityChanged(videoQuality); } /** @@ -498,16 +802,21 @@ public class VideoCallPresenter extends Presenter