diff options
author | Rekha Kumar <rekhak@codeaurora.org> | 2015-03-18 09:55:55 -0700 |
---|---|---|
committer | Rekha Kumar <rekhak@quicinc.com> | 2015-03-26 22:56:33 +0000 |
commit | 18c0feda76fe333f3db1bf7bd307458a9e6b6005 (patch) | |
tree | 61c6fcc9baa269a3c81298ae13c8e4a9ac909144 /InCallUI/src/com/android/incallui/VideoCallPresenter.java | |
parent | 22875bb1d3fa4f023e6ca127a77159ab42bbbabc (diff) |
IMS-VT: Add support for video calls
IMS-VT: Upgrade downgrade and hold resume video calls
-Add support for upgrade downgrade video calls.
-Add support for hold resume and call waiting
IMS-VT: Fixed surface caching.
Fixed surface caching.
IMS-VT: Fixes InCallUI/Dialer crash when there is a VT call.
-Fixes InCallUI/Dialer crash when UE is rotated.
-Fixes InCallUI/Dialer crash when UE VoLTE call
is upgraded to VT.
Don't default to speaker phone for VT when speaker is disabled
- The adb property persist.radio.ims.audio.output indicates
whether speaker is disabled explicitly or not.
- If the above property is set to 1, don't enable speaker
phone by default in VT call.
IMS-VT: Fix CVO, surface and resource related issues.
- Send initial orientation to VT Service.
- Detect if Activity is being destroyed due to
confugration changes.
- Close the camera when InCallUI is pushed to background.
- Fix surface and VT service related issues when Fragment
and Presenter gets destroyed.
IMS-VT: Peer Resolution Feature Implementation
Change display video size based on peer resolution values
received from far end.
IMS-VT: Answering calls and upgrade requests as VT_TX and VT_RX
-Support for showing one way options for incoming upgrade request
-Fix issue where incoming video popup stays on screen even after
rejecting the request.
-Fix issue where incoming video popup stays on even after it has
been timed out by lower layers.
- Answer with different calltypes support.
Notify listeners of video quality changed event and display message on UI
- Add methods to notify listeners of type VideoEventListener
when video quality changes.
- Display a notification on the UI when video quality changes.
IMS-VT: Enable SIP based video multitasking.
Enable SIP based video multitasking.
IMS-VT: Call data usage feature
- Add support for call data usage callback
- Request for call data usage stats
- Log call data usage stats when IMS layers send update
Change-Id: I3f0dde0d82698085fa5d3f110720f10326eca768
IMS-VT: When TTY is ON, do not allow upgrade to VT call
When TTY mode is ON, the user SHOULD NOT be allowed to upgrade
a call from VOLTE to VT/VT-TX/VT-RX and an UI alert message will
be displayed explaining upgrade to VT call cannot be initiated.
IMS-VT: Add null check in call upgrade fail scenario
Crash is observed if call is ended in the interval wherein
handler is started to change state from REQUEST_FAILED to NO_REQUEST
after an interval. Added null check to change state only when call
exists
Propagate call substate message and display a notification on the UI
Ims: Reject upgrade request
1. If there is waiting call that is pending user action and
2. Before offering a waiting call
IMS-VT: Provide Player State indication to user.
Change to display "player started/stopped" toast message
whenever video starts/stops flowing
IMS-VT: Cleanup video views when not required
- Hide video views when not required.
Set display size as per current TextureView size
- Using TextureView height and width to calculate the display size.
- When ever there is change in display make sure that center the display.
IMS-VT: Exit VT call full screen mode.
If the call is no longer a VT call, then exit full screen mode
Change-Id: Ibc4ad8f9a4c38e467820028cdc2c7e68d65fd93c
CRs-Fixed: 760925
IMS-VT: Upgrade button fix
-Show upgrade/downgrade button only when call is in ACTIVE or
ONHOLD state
IMS-VT: Show correct call types during video pause
-In paused state, upgrade downgrade button does not list the
calltypes dropdown box
-Remove the paused bit to calculate call type during video paused
state.
IMS-VT: Move strings for video quality changed indication to resource files
IMS-VT: Clean up the showCallSubstateChanged API
- Make the code more readable.
IMS-VT: Set audio route to Speaker if current route is not bluetooth or headset
- We always set the audio route to speaker when we enter video mode.
- That is not correct. We should check if headset or bluetooth is
connected before defaulting to speaker.
IMS-VT: Turn speaker on/off for video calls based on call state changes
- Turn speaker on only when video call is active or dialing
- Switch back to the previous audio mode when there are no
more calls
- Make the previous audio mode static so that information
persists when the class is recreated (e.g. when UE is
rotated or multitasking happens)
IMS-VT: Use back camera instead of front camera for VT-TX
Set back camera for below cases
1) VOLTE to VT-TX
2) VT to VT-TX
3) Waiting call over Vt-TX call.
IMS-VT: Fix upgrade in call waiting scenarios
-When one VoLTE call is active and another VoLTE call is on hold
and upgrade button is hit, call ends. Fix this upgrade issue.
IMS-VT: Enter video mode when the primary video call changes
- We only enter video mode when we have any video state changes
- Enter video mode should also be called when the video call
changes. e.g. When we have a second call with the same video
state, we don't enter video mode which is incorrect. This
change fixes the issue
Add null check for InCallActivity in setInCallAllowsOrientationChange
- In some cases, this API is called when InCallActivity is null.
This causes a null pointer exception. Fix is to add a null check.
IMS-VT: Fix check for VOLTE call (AND to OR) when turning speaker on
IMS-VT: Check if call is video before enabling speaker in updateAudioMode
- Moved the video call check to the correct block where we
are enabling speaker
IMS-VT: Get call substate values correctly from the bit mask
- We were getting the call substate incorrectly as an int.
- Fixed that by getting it from the bit mask and using the
possible multiple values to display the call substate message.
IMS-VT: Keep the screen on during video calls.
UI screens times out after some time. During
video calls the screen must be kept on.
Change-Id: Icaa8662210b2dd323b29f4a472869a9ed1e01d00
IMS-VT: Open front facing camera for VT calls.
Open front facing camera for VT calls.
IMS-VT: Show manage conference button for Video Call
- We show the manage conference in a separate section for
VOLTE Calls.
- In the case of Video Call, the entire screen is occupied
by the far end video. So we display manage conference in
the overflow section of the call button fragment.
IMS-VT: Fix camera preview freeze for CONF video calls.
Fix camera preview freeze for CONF video calls.
IMS-VT: Move persist.radio.ims.audio.output to frameworks
Move persist.radio.ims.audio.output and related constants to
TelephonyProperties and PhoneConstants so that these can be accessed
from multiple git projects instead of redefining.
IMS-VT: Multiple video call fixes/optimizations.
-Current implementation clears primary call cache upon exiting
video mode. This will remove video call provider interface as
well, which will prevent further communication with the backend.
Don't clear primary call cache upon exiting video mode.
-Unregister call details listener when UI transitions into
unready state
-Send surface of incoming video to video call provider before
opening camera.
IMS: Cleanup all listener objects
Clean all stale listener objects to avoid the memory leak.
IMS: Fix speaker icon display issue in Call UI.
In few devices, InCall UI can show upto five buttons
in a row. Modify code to display at most five buttons
in a row. Show overflow menu if number of buttons is
more than five.
IMS-VT: Show glowpad view with accept/reject for most video upgrade cases
- Show the glowpad view with all options only when upgrading
from Volte to VT
- For all other cases, show the glowpad with accept/reject only
Change-Id: I41ecbda40db7c3c69428fc4272f8bfbd258e2980
Diffstat (limited to 'InCallUI/src/com/android/incallui/VideoCallPresenter.java')
-rw-r--r-- | InCallUI/src/com/android/incallui/VideoCallPresenter.java | 715 |
1 files changed, 604 insertions, 111 deletions
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java index 38d7c9556..486a38da3 100644 --- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java +++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java @@ -18,10 +18,14 @@ package com.android.incallui; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Point; import android.os.Handler; import android.telecom.AudioState; import android.telecom.CameraCapabilities; +import android.telecom.Connection; +import android.telecom.Connection.VideoProvider; import android.telecom.InCallService.VideoCall; +import android.telecom.VideoProfile; import android.view.Surface; import com.android.contacts.common.CallUtil; @@ -31,10 +35,14 @@ import com.android.incallui.InCallPresenter.InCallStateListener; import com.android.incallui.InCallPresenter.IncomingCallListener; import com.android.incallui.InCallVideoCallListenerNotifier.SurfaceChangeListener; import com.android.incallui.InCallVideoCallListenerNotifier.VideoEventListener; +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.TelephonyProperties; import com.google.common.base.Preconditions; import java.util.Objects; +import android.os.SystemProperties; + /** * Logic related to the {@link VideoCallFragment} and for managing changes to the video calling * surfaces based on other user interface events and incoming events from the @@ -61,6 +69,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi IncomingCallListener, InCallOrientationListener, InCallStateListener, InCallDetailsListener, SurfaceChangeListener, VideoEventListener, InCallVideoCallListenerNotifier.SessionModificationListener { + public static final String TAG = "VideoCallPresenter"; /** * Determines the device orientation (portrait/lanscape). @@ -122,7 +131,12 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi /** * Determines if the current UI state represents a video call. */ - private boolean mIsVideoCall; + private int mCurrentVideoState; + + /** + * Call's current state + */ + private int mCurrentCallState = Call.State.INVALID; /** * Determines the device orientation (portrait/lanscape). @@ -142,7 +156,15 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi /** * Saves the audio mode which was selected prior to going into a video call. */ - private int mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; + private static int sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; + + private static boolean mIsVideoMode = false; + + /** + * Stores the current call substate. + */ + private int mCurrentCallSubstate; + /** Handler which resets request state to NO_REQUEST after an interval. */ private Handler mSessionModificationResetHandler; @@ -168,9 +190,11 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi @Override public void onUiReady(VideoCallUi ui) { super.onUiReady(ui); + Log.d(this, "onUiReady:"); // Register for call state changes last InCallPresenter.getInstance().addListener(this); + InCallPresenter.getInstance().addDetailsListener(this); InCallPresenter.getInstance().addIncomingCallListener(this); InCallPresenter.getInstance().addOrientationListener(this); // To get updates of video call details changes @@ -180,7 +204,8 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi InCallVideoCallListenerNotifier.getInstance().addSurfaceChangeListener(this); InCallVideoCallListenerNotifier.getInstance().addVideoEventListener(this); InCallVideoCallListenerNotifier.getInstance().addSessionModificationListener(this); - mIsVideoCall = false; + mCurrentVideoState = VideoProfile.VideoState.AUDIO_ONLY; + mCurrentCallState = Call.State.INVALID; } /** @@ -191,41 +216,44 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi @Override public void onUiUnready(VideoCallUi ui) { super.onUiUnready(ui); + Log.d(this, "onUiUnready:"); InCallPresenter.getInstance().removeListener(this); + InCallPresenter.getInstance().removeDetailsListener(this); InCallPresenter.getInstance().removeIncomingCallListener(this); InCallPresenter.getInstance().removeOrientationListener(this); + InCallVideoCallListenerNotifier.getInstance().removeSurfaceChangeListener(this); InCallVideoCallListenerNotifier.getInstance().removeVideoEventListener(this); InCallVideoCallListenerNotifier.getInstance().removeSessionModificationListener(this); } /** - * @return The {@link VideoCall}. - */ - private VideoCall getVideoCall() { - return mVideoCall; - } - - /** * Handles the creation of a surface in the {@link VideoCallFragment}. * * @param surface The surface which was created. */ public void onSurfaceCreated(int surface) { - final VideoCallUi ui = getUi(); + Log.d(this, "onSurfaceCreated surface=" + surface + " mVideoCall=" + mVideoCall); + Log.d(this, "onSurfaceCreated PreviewSurfaceState=" + mPreviewSurfaceState); + Log.d(this, "onSurfaceCreated presenter=" + this); + final VideoCallUi ui = getUi(); if (ui == null || mVideoCall == null) { + Log.w(this, "onSurfaceCreated: Error bad state VideoCallUi=" + ui + " mVideoCall=" + + mVideoCall); return; } // If the preview surface has just been created and we have already received camera // capabilities, but not yet set the surface, we will set the surface now. - if (surface == VideoCallFragment.SURFACE_PREVIEW && - mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) { - - mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; - mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); + if (surface == VideoCallFragment.SURFACE_PREVIEW ) { + if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) { + mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; + mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); + } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()){ + enableCamera(mVideoCall, true); + } } else if (surface == VideoCallFragment.SURFACE_DISPLAY) { mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); } @@ -245,12 +273,15 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi /** * Handles the destruction of a surface in the {@link VideoCallFragment}. + * Note: The surface is being released, that is, it is no longer valid. * * @param surface The surface which was destroyed. */ - public void onSurfaceDestroyed(int surface) { - final VideoCallUi ui = getUi(); - if (ui == null || mVideoCall == null) { + public void onSurfaceReleased(int surface) { + Log.d(this, "onSurfaceReleased: mSurfaceId=" + surface); + if ( mVideoCall == null) { + Log.w(this, "onSurfaceReleased: VideoCall is null. mSurfaceId=" + + surface); return; } @@ -258,11 +289,43 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi mVideoCall.setDisplaySurface(null); } else if (surface == VideoCallFragment.SURFACE_PREVIEW) { mVideoCall.setPreviewSurface(null); - // Also disable camera as preview is closed - mVideoCall.setCamera(null); + enableCamera(mVideoCall, false); + } + } + + /** + * Called by {@link VideoCallFragment} when the surface is detached from UI (TextureView). + * Note: The surface will be cached by {@link VideoCallFragment}, so we don't immediately + * null out incoming video surface. + * @see VideoCallPresenter#onSurfaceReleased(int) + * + * @param surface The surface which was detached. + */ + public void onSurfaceDestroyed(int surface) { + Log.d(this, "onSurfaceDestroyed: mSurfaceId=" + surface); + if (mVideoCall == null) { + return; + } + + final boolean isChangingConfigurations = + InCallPresenter.getInstance().isChangingConfigurations(); + Log.d(this, "onSurfaceDestroyed: isChangingConfigurations=" + isChangingConfigurations); + + if (surface == VideoCallFragment.SURFACE_PREVIEW) { + if (!isChangingConfigurations) { + enableCamera(mVideoCall, false); + } else { + Log.w(this, "onSurfaceDestroyed: Activity is being destroyed due " + + "to configuration changes. Not closing the camera."); + } } } + private void toggleFullScreen() { + mIsFullScreen = !mIsFullScreen; + InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen); + } + /** * Handles clicks on the video surfaces by toggling full screen state. * Informs the {@link InCallPresenter} of the change so that it can inform the @@ -271,8 +334,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * @param surfaceId The video surface receiving the click. */ public void onSurfaceClick(int surfaceId) { - mIsFullScreen = !mIsFullScreen; - InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen); + toggleFullScreen(); } @@ -298,41 +360,152 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi @Override public void onStateChange(InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, CallList callList) { - // Bail if video calling is disabled for the device. - if (!CallUtil.isVideoEnabled(mContext)) { - return; - } + Log.d(this, "onStateChange oldState" + oldState + " newState=" + newState + + " isVideoMode=" + isVideoMode()); if (newState == InCallPresenter.InCallState.NO_CALLS) { - exitVideoMode(); + updateAudioMode(false); + + if (isVideoMode()) { + exitVideoMode(); + } + + cleanupSurfaces(); } // Determine the primary active call). Call primary = null; if (newState == InCallPresenter.InCallState.INCOMING) { - primary = callList.getIncomingCall(); + // We don't want to replace active video call (primary call) + // with a waiting call, since user may choose to ignore/decline the waiting call and + // this should have no impact on current active video call, that is, we should not + // change the camera or UI unless the waiting VT call becomes active. + primary = callList.getActiveCall(); + if (!CallUtils.isActiveVideoCall(primary)) { + primary = callList.getIncomingCall(); + } } else if (newState == InCallPresenter.InCallState.OUTGOING) { primary = callList.getOutgoingCall(); + } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) { + primary = callList.getPendingOutgoingCall(); } else if (newState == InCallPresenter.InCallState.INCALL) { primary = callList.getActiveCall(); } final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary); + Log.d(this, "onStateChange primaryChanged=" + primaryChanged); + Log.d(this, "onStateChange primary= " + primary); + Log.d(this, "onStateChange mPrimaryCall = " + mPrimaryCall); if (primaryChanged) { - mPrimaryCall = primary; - - if (primary != null) { - checkForVideoCallChange(); - mIsVideoCall = mPrimaryCall.isVideoCall(mContext); - if (mIsVideoCall) { - enterVideoMode(); - } else { - exitVideoMode(); - } - } else if (primary == null) { - // If no primary call, ensure we exit video state and clean up the video surfaces. - exitVideoMode(); + onPrimaryCallChanged(primary); + } else if(mPrimaryCall!=null) { + updateVideoCall(primary); + } + updateCallCache(primary); + } + + private void checkForVideoStateChange(Call call) { + final boolean isVideoCall = CallUtils.isVideoCall(call); + final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState(); + + Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall + + " hasVideoStateChanged=" + + hasVideoStateChanged + " isVideoMode=" + isVideoMode()); + + if (!hasVideoStateChanged) { return;} + + updateCameraSelection(call); + + if (isVideoCall) { + enterVideoMode(call.getVideoCall(), call.getVideoState()); + } else if (isVideoMode()) { + exitVideoMode(); + } + } + + private void checkForCallStateChange(Call call) { + final boolean isVideoCall = CallUtils.isVideoCall(call); + final boolean hasCallStateChanged = mCurrentCallState != call.getState(); + + Log.d(this, "checkForCallStateChange: isVideoCall= " + isVideoCall + + " hasCallStateChanged=" + + hasCallStateChanged + " isVideoMode=" + isVideoMode()); + + if (!hasCallStateChanged) { return; } + + final InCallCameraManager cameraManager = InCallPresenter.getInstance(). + getInCallCameraManager(); + + String prevCameraId = cameraManager.getActiveCameraId(); + + updateCameraSelection(call); + + String newCameraId = cameraManager.getActiveCameraId(); + + if (!Objects.equals(prevCameraId, newCameraId) && CallUtils.isActiveVideoCall(call)) { + enableCamera(call.getVideoCall(), true); + } + } + + private void checkForCallSubstateChange(Call call) { + if (mCurrentCallSubstate != call.getCallSubstate()) { + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "Error VideoCallUi is null. Return."); + return; } + mCurrentCallSubstate = call.getCallSubstate(); + // Display a call substate changed message on UI. + ui.showCallSubstateChanged(mCurrentCallSubstate); + } + } + + private void cleanupSurfaces() { + final VideoCallUi ui = getUi(); + if (ui == null) { + Log.w(this, "cleanupSurfaces"); + return; + } + ui.cleanupSurfaces(); + } + + private void onPrimaryCallChanged(Call newPrimaryCall) { + final boolean isVideoCall = CallUtils.isVideoCall(newPrimaryCall); + final boolean isVideoMode = isVideoMode(); + + Log.d(this, "onPrimaryCallChanged: isVideoCall=" + isVideoCall + " isVideoMode=" + + isVideoMode); + + if (!isVideoCall && isVideoMode) { + // Terminate video mode if new primary call is not a video call + // and we are currently in video mode. + Log.d(this, "onPrimaryCallChanged: Exiting video mode..."); + exitVideoMode(); + } else if (isVideoCall) { + Log.d(this, "onPrimaryCallChanged: Entering video mode..."); + + updateCameraSelection(newPrimaryCall); + enterVideoMode(newPrimaryCall.getVideoCall(), newPrimaryCall.getVideoState()); + } + } + + private boolean isVideoMode() { + return mIsVideoMode; + } + + private void updateCallCache(Call call) { + if (call == null) { + mCurrentVideoState = VideoProfile.VideoState.AUDIO_ONLY; + mCurrentCallSubstate = Connection.SUBSTATE_NONE; + mCurrentCallState = Call.State.INVALID; + mVideoCall = null; + mPrimaryCall = null; + } else { + mCurrentVideoState = call.getVideoState(); + mCurrentCallSubstate = call.getCallSubstate(); + mVideoCall = call.getVideoCall(); + mCurrentCallState = call.getState(); + mPrimaryCall = call; } } @@ -345,115 +518,214 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onDetailsChanged(Call call, android.telecom.Call.Details details) { + Log.d(this, " onDetailsChanged call=" + call + " details=" + details + " mPrimaryCall=" + + mPrimaryCall); // If the details change is not for the currently active call no update is required. if (!call.equals(mPrimaryCall)) { + Log.d(this," onDetailsChanged: Details not for current active call so returning. "); return; } - checkForVideoStateChange(); + updateVideoCall(call); + checkForCallSubstateChange(call); + + updateCallCache(call); } - /** - * Checks for a change to the video call and changes it if required. - */ - private void checkForVideoCallChange() { - VideoCall videoCall = mPrimaryCall.getTelecommCall().getVideoCall(); - if (!Objects.equals(videoCall, mVideoCall)) { - changeVideoCall(videoCall); - } + private void updateVideoCall(Call call) { + checkForVideoCallChange(call); + checkForVideoStateChange(call); + checkForCallStateChange(call); } /** - * Checks to see if the current video state has changed and updates the UI if required. + * Checks for a change to the video call and changes it if required. */ - private void checkForVideoStateChange() { - boolean newVideoState = mPrimaryCall.isVideoCall(mContext); - - // Check if video state changed - if (mIsVideoCall != newVideoState) { - mIsVideoCall = newVideoState; - - if (mIsVideoCall) { - enterVideoMode(); - } else { - exitVideoMode(); - } + private void checkForVideoCallChange(Call call) { + final VideoCall videoCall = call.getTelecommCall().getVideoCall(); + Log.d(this, "checkForVideoCallChange: videoCall=" + videoCall + " mVideoCall=" + + mVideoCall); + if (!Objects.equals(videoCall, mVideoCall)) { + changeVideoCall(call); } } /** - * Handles a change to the video call. Sets the surfaces on the previous call to null and sets + * Handles a change to the video call. Sets the surfaces on the previous call to null and sets * the surfaces on the new video call accordingly. * * @param videoCall The new video call. */ - private void changeVideoCall(VideoCall videoCall) { + private void changeVideoCall(Call call) { + final VideoCall videoCall = call.getTelecommCall().getVideoCall(); + Log.d(this, "changeVideoCall to videoCall=" + videoCall + " mVideoCall=" + mVideoCall); // Null out the surfaces on the previous video call. if (mVideoCall != null) { - mVideoCall.setDisplaySurface(null); - mVideoCall.setPreviewSurface(null); + // Log.d(this, "Null out the surfaces on the previous video call."); + // mVideoCall.setDisplaySurface(null); + // mVideoCall.setPreviewSurface(null); } + final boolean hasChanged = mVideoCall == null && videoCall != null; + mVideoCall = videoCall; + if (mVideoCall == null || call == null) { + Log.d(this, "Video call or primary call is null. Return"); + return; + } + + if (CallUtils.isVideoCall(call) && hasChanged) { + enterVideoMode(call.getVideoCall(), call.getVideoState()); + } + } + + private static boolean isCameraRequired(int videoState) { + return VideoProfile.VideoState.isBidirectional(videoState) || + VideoProfile.VideoState.isTransmissionEnabled(videoState); + } + + private boolean isCameraRequired() { + return mPrimaryCall != null ? isCameraRequired(mPrimaryCall.getVideoState()) : false; } /** * Enters video mode by showing the video surfaces and making other adjustments (eg. audio). * TODO(vt): Need to adjust size and orientation of preview surface here. */ - private void enterVideoMode() { + private void enterVideoMode(VideoCall videoCall, int newVideoState) { + Log.d(this, "enterVideoMode videoCall= " + videoCall + " videoState: " + newVideoState); VideoCallUi ui = getUi(); if (ui == null) { + Log.e(this, "Error VideoCallUi is null so returning"); return; } - ui.showVideoUi(true); + showVideoUi(newVideoState); InCallPresenter.getInstance().setInCallAllowsOrientationChange(true); // Communicate the current camera to telephony and make a request for the camera // capabilities. - if (mVideoCall != null) { - // Do not reset the surfaces if we just restarted the activity due to an orientation - // change. - if (ui.isActivityRestart()) { - return; + if (videoCall != null) { + if (ui.isDisplayVideoSurfaceCreated()) { + Log.d(this, "Calling setDisplaySurface with " + ui.getDisplayVideoSurface()); + videoCall.setDisplaySurface(ui.getDisplayVideoSurface()); } - mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET; + final int rotation = ui.getCurrentRotation(); + if (rotation != VideoCallFragment.ORIENTATION_UNKNOWN) { + videoCall.setDeviceOrientation(InCallPresenter.toRotationAngle(rotation)); + } + + enableCamera(videoCall, isCameraRequired(newVideoState)); + } + mCurrentVideoState = newVideoState; + updateAudioMode(true); + + mIsVideoMode = true; + } + + //TODO: Move this into Telecom. InCallUI should not be this close to audio functionality. + private void updateAudioMode(boolean enableSpeaker) { + if (!isSpeakerEnabledForVideoCalls()) { + Log.d(this, "Speaker is disabled. Can't update audio mode"); + return; + } + + final TelecomAdapter telecomAdapter = TelecomAdapter.getInstance(); + final boolean isPrevAudioModeValid = + sPrevVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID; + + Log.d(this, "Is previous audio mode valid = " + isPrevAudioModeValid + " enableSpeaker is " + + enableSpeaker); + + // Set audio mode to previous mode if enableSpeaker is false. + if (isPrevAudioModeValid && !enableSpeaker) { + telecomAdapter.setAudioRoute(sPrevVideoAudioMode); + sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; + return; + } + + int currentAudioMode = AudioModeProvider.getInstance().getAudioMode(); + + // Set audio mode to speaker if enableSpeaker is true and bluetooth or headset are not + // connected and it's a video call. + if (!isAudioRouteEnabled(currentAudioMode, + AudioState.ROUTE_BLUETOOTH | AudioState.ROUTE_WIRED_HEADSET) && + !isPrevAudioModeValid && enableSpeaker && CallUtils.isVideoCall(mPrimaryCall)) { + sPrevVideoAudioMode = currentAudioMode; + + Log.d(this, "Routing audio to speaker"); + telecomAdapter.setAudioRoute(AudioState.ROUTE_SPEAKER); + } + } + + private static boolean isSpeakerEnabledForVideoCalls() { + return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT, + PhoneConstants.AUDIO_OUTPUT_DEFAULT) == + PhoneConstants.AUDIO_OUTPUT_ENABLE_SPEAKER); + } + + private void enableCamera(VideoCall videoCall, boolean isCameraRequired) { + Log.d(this, "enableCamera: VideoCall=" + videoCall + " enabling=" + isCameraRequired); + if (videoCall == null) { + Log.w(this, "enableCamera: VideoCall is null."); + return; + } + + if (isCameraRequired) { InCallCameraManager cameraManager = InCallPresenter.getInstance(). getInCallCameraManager(); - mVideoCall.setCamera(cameraManager.getActiveCameraId()); - mVideoCall.requestCameraCapabilities(); + videoCall.setCamera(cameraManager.getActiveCameraId()); + mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET; - if (ui.isDisplayVideoSurfaceCreated()) { - mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); - } + videoCall.requestCameraCapabilities(); + } else { + mPreviewSurfaceState = PreviewSurfaceState.NONE; + videoCall.setCamera(null); } - - mPreVideoAudioMode = AudioModeProvider.getInstance().getAudioMode(); - TelecomAdapter.getInstance().setAudioRoute(AudioState.ROUTE_SPEAKER); } /** - * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). + * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). */ private void exitVideoMode() { + Log.d(this, "exitVideoMode"); + + InCallPresenter.getInstance().setInCallAllowsOrientationChange(false); + + showVideoUi(VideoProfile.VideoState.AUDIO_ONLY); + enableCamera(mVideoCall, false); + + Log.d(this, "exitVideoMode mIsFullScreen: " + mIsFullScreen); + if (mIsFullScreen) { + toggleFullScreen(); + } + + mIsVideoMode = false; + } + + /** + * Show video Ui depends on video state. + */ + private void showVideoUi(int videoState) { VideoCallUi ui = getUi(); if (ui == null) { + Log.e(this, "showVideoUi, VideoCallUi is null returning"); return; } - InCallPresenter.getInstance().setInCallAllowsOrientationChange(false); - ui.showVideoUi(false); - if (mVideoCall != null) { - // Also disable camera otherwise it will be already in use for next upgrade - mVideoCall.setCamera(null); + if (VideoProfile.VideoState.isBidirectional(videoState)) { + ui.showVideoViews(true, true); + } else if (VideoProfile.VideoState.isTransmissionEnabled(videoState)) { + ui.showVideoViews(true, false); + } else if (VideoProfile.VideoState.isReceptionEnabled(videoState)) { + ui.showVideoViews(false, true); + } else { + ui.hideVideoUi(); } - if (mPreVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID) { - TelecomAdapter.getInstance().setAudioRoute(mPreVideoAudioMode); - mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; - } + InCallPresenter.getInstance().enableScreenTimeout( + VideoProfile.VideoState.isAudioOnly(videoState)); } /** @@ -481,11 +753,43 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onUpdatePeerDimensions(Call call, int width, int height) { + Log.d(this, "onUpdatePeerDimensions: width= " + width + " height= " + height); + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "VideoCallUi is null. Bail out"); + return; + } if (!call.equals(mPrimaryCall)) { + Log.e(this, "Current call is not equal to primary call. Bail out"); return; } - // TODO(vt): Change display surface aspect ratio. + // Change size of display surface to match the peer aspect ratio + if (width > 0 && height > 0) { + setDisplayVideoSize(width, height); + } + } + + /** + * Handles any video quality changes in the call. + * + * @param call The call which experienced a video quality change. + * @param videoQuality The new video call quality. + */ + @Override + public void onVideoQualityChanged(Call call, int videoQuality) { + if (!call.equals(mPrimaryCall)) { + return; + } + + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "Error VideoCallUi is null. Return."); + return; + } + + // Display a video quality changed message on UI. + ui.showVideoQualityChanged(videoQuality); } /** @@ -498,16 +802,21 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onCameraDimensionsChange(Call call, int width, int height) { + Log.d(this, "onCameraDimensionsChange call=" + call + " width=" + width + " height=" + + height); VideoCallUi ui = getUi(); if (ui == null) { + Log.e(this, "onCameraDimensionsChange ui is null"); return; } if (!call.equals(mPrimaryCall)) { + Log.e(this, "Call is not primary call"); return; } mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED; + ui.setPreviewSurfaceSize(width, height); // Configure the preview surface to the correct aspect ratio. float aspectRatio = 1.0f; @@ -525,45 +834,105 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi } /** - * Handles hanges to the device orientation. + * Called when call session event is raised. + * + * @param event The call session event. + */ + @Override + public void onCallSessionEvent(int event) { + Log.d(this, "onCallSessionEvent event =" + event); + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "onCallSessionEvent: VideoCallUi is null"); + return; + } + ui.displayCallSessionEvent(event); + } + + /** + * Handles a change to the call data usage + * + * @param dataUsage call data usage value + */ + @Override + public void onCallDataUsageChange(long dataUsage) { + Log.d(this, "onCallDataUsageChange dataUsage=" + dataUsage); + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "onCallDataUsageChange: VideoCallUi is null"); + return; + } + ui.setCallDataUsage(mContext, dataUsage); + } + + /** + * Handles changes to the device orientation. * See: {@link Configuration.ORIENTATION_LANDSCAPE}, {@link Configuration.ORIENTATION_PORTRAIT} * @param orientation The device orientation. */ @Override public void onDeviceOrientationChanged(int orientation) { + Log.d(this, "onDeviceOrientationChanged: orientation=" + orientation); mDeviceOrientation = orientation; } @Override - public void onUpgradeToVideoRequest(Call call) { - mPrimaryCall.setSessionModificationState( - Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST); + public void onUpgradeToVideoRequest(Call call, int videoState) { + Log.d(this, "onUpgradeToVideoRequest call = " + call + " new video state = " + videoState); + if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) { + Log.w(this, "UpgradeToVideoRequest received for non-primary call"); + } + + if (call == null) { + return; + } + + call.setSessionModificationTo(videoState); } @Override public void onUpgradeToVideoSuccess(Call call) { + Log.d(this, "onUpgradeToVideoSuccess call=" + call); if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) { + Log.w(this, "UpgradeToVideoSuccess received for non-primary call"); + } + + if (call == null) { return; } - mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); + call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); } @Override - public void onUpgradeToVideoFail(Call call) { + public void onUpgradeToVideoFail(int status, Call call) { + Log.d(this, "onUpgradeToVideoFail call=" + call); if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) { - return; + Log.w(this, "UpgradeToVideoFail received for non-primary call"); } - call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED); + if (call == null) { + return; + } - // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval. - mSessionModificationResetHandler.postDelayed(new Runnable() { - @Override - public void run() { - mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); - } - }, SESSION_MODIFICATION_RESET_DELAY_MS); + if (status == VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT) { + call.setSessionModificationState( + Call.SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT); + } else { + call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED); + + final Call modifyCall = call; + // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval. + mSessionModificationResetHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (modifyCall != null) { + modifyCall + .setSessionModificationState(Call.SessionModificationState.NO_REQUEST); + } + } + }, SESSION_MODIFICATION_RESET_DELAY_MS); + } } @Override @@ -599,16 +968,140 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi } /** + * Sets the display video surface size based on peer width and height + * + * @param width peer width + * @param height peer height + */ + + private void setDisplayVideoSize(int width, int height) { + Log.d(this, "setDisplayVideoSize:Received peer width=" + width + " peer height=" + height); + VideoCallUi ui = getUi(); + if (ui == null) { + return; + } + + // Get current display size + Point size = ui.getScreenSize(); + Log.d("VideoCallPresenter", "setDisplayVideoSize: windowmgr width=" + size.x + + " windowmgr height=" + size.y); + if (size.y * width > size.x * height) { + // current display height is too much. Correct it + size.y = (int) (size.x * height / width); + } else if (size.y * width < size.x * height) { + // current display width is too much. Correct it + size.x = (int) (size.y * width / height); + } + ui.setDisplayVideoSize(size.x, size.y); + } + + private static boolean isAudioRouteEnabled(int audioRoute, int audioRouteMask) { + return ((audioRoute & audioRouteMask) != 0); + } + + private static void updateCameraSelection(Call call) { + Log.d(TAG, "updateCameraSelection: call=" + call); + Log.d(TAG, "updateCameraSelection: call=" + toSimpleString(call)); + + final Call activeCall = CallList.getInstance().getActiveCall(); + int cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; + + // this function should never be called with null call object, however if it happens we + // should handle it gracefully. + if (call == null) { + cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; + com.android.incallui.Log.e(TAG, "updateCameraSelection: Call object is null." + + " Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)"); + } + + // Clear camera direction if this is not a video call. + else if (CallUtils.isAudioCall(call)) { + cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; + call.getVideoSettings().setCameraDir(cameraDir); + } + + // If this is a waiting video call, default to active call's camera, + // since we don't want to change the current camera for waiting call + // without user's permission. + else if (CallUtils.isVideoCall(activeCall) && CallUtils.isIncomingVideoCall(call)) { + cameraDir = activeCall.getVideoSettings().getCameraDir(); + } + + // Infer the camera direction from the video state and store it, + // if this is an outgoing video call. + else if (CallUtils.isOutgoingVideoCall(call) && !isCameraDirectionSet(call) ) { + cameraDir = toCameraDirection(call.getVideoState()); + call.getVideoSettings().setCameraDir(cameraDir); + } + + // Use the stored camera dir if this is an outgoing video call for which camera direction + // is set. + else if (CallUtils.isOutgoingVideoCall(call)) { + cameraDir = call.getVideoSettings().getCameraDir(); + } + + // Infer the camera direction from the video state and store it, + // if this is an active video call and camera direction is not set. + else if (CallUtils.isActiveVideoCall(call) && !isCameraDirectionSet(call)) { + cameraDir = toCameraDirection(call.getVideoState()); + call.getVideoSettings().setCameraDir(cameraDir); + } + + // Use the stored camera dir if this is an active video call for which camera direction + // is set. + else if (CallUtils.isActiveVideoCall(call)) { + cameraDir = call.getVideoSettings().getCameraDir(); + } + + // For all other cases infer the camera direction but don't store it in the call object. + else { + cameraDir = toCameraDirection(call.getVideoState()); + } + + com.android.incallui.Log.d(TAG, "updateCameraSelection: Setting camera direction to " + + cameraDir + " Call=" + call); + final InCallCameraManager cameraManager = InCallPresenter.getInstance(). + getInCallCameraManager(); + cameraManager.setUseFrontFacingCamera(cameraDir == + Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING); + } + + private static int toCameraDirection(int videoState) { + return VideoProfile.VideoState.isTransmissionEnabled(videoState) && + !VideoProfile.VideoState.isBidirectional(videoState) + ? Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING + : Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING; + } + + private static boolean isCameraDirectionSet(Call call) { + return CallUtils.isVideoCall(call) && call.getVideoSettings().getCameraDir() + != Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; + } + + private static String toSimpleString(Call call) { + return call == null ? null : call.toSimpleString(); + } + + /** * Defines the VideoCallUI interactions. */ public interface VideoCallUi extends Ui { - void showVideoUi(boolean show); + void showVideoViews(boolean showPreview, boolean showIncoming); + void hideVideoUi(); + void showVideoQualityChanged(int videoQuality); boolean isDisplayVideoSurfaceCreated(); boolean isPreviewVideoSurfaceCreated(); Surface getDisplayVideoSurface(); Surface getPreviewVideoSurface(); + int getCurrentRotation(); void setPreviewSize(int width, int height); + void setPreviewSurfaceSize(int width, int height); + void setDisplayVideoSize(int width, int height); + void setCallDataUsage(Context context, long dataUsage); + void displayCallSessionEvent(int event); + Point getScreenSize(); void cleanupSurfaces(); boolean isActivityRestart(); + void showCallSubstateChanged(int callSubstate); } } |