diff options
Diffstat (limited to 'InCallUI/src/com/android/incallui')
20 files changed, 2221 insertions, 293 deletions
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java index 29747dabd..a066396c9 100644 --- a/InCallUI/src/com/android/incallui/AnswerFragment.java +++ b/InCallUI/src/com/android/incallui/AnswerFragment.java @@ -50,6 +50,9 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente public static final int TARGET_SET_FOR_VIDEO_WITHOUT_SMS = 2; public static final int TARGET_SET_FOR_VIDEO_WITH_SMS = 3; public static final int TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST = 4; + public static final int TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST = 5; + public static final int TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST = 6; + public static final int TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST = 7; /** * The popup showing the list of canned responses. @@ -163,6 +166,33 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente .incoming_call_widget_video_upgrade_request_target_direction_descriptions; handleDrawableResourceId = R.drawable.ic_incall_video_handle; break; + case TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST: + targetResourceId = + R.array.incoming_call_widget_bidirectional_video_accept_reject_request_targets; + targetDescriptionsResourceId = + R.array.incoming_call_widget_video_upgrade_request_target_descriptions; + directionDescriptionsResourceId = R.array + .incoming_call_widget_video_upgrade_request_target_direction_descriptions; + handleDrawableResourceId = R.drawable.ic_incall_video_handle; + break; + case TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST: + targetResourceId = + R.array.incoming_call_widget_video_transmit_accept_reject_request_targets; + targetDescriptionsResourceId = + R.array.incoming_call_widget_video_transmit_request_target_descriptions; + directionDescriptionsResourceId = R.array + .incoming_call_widget_video_upgrade_request_target_direction_descriptions; + handleDrawableResourceId = R.drawable.ic_incall_video_handle; + break; + case TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST: + targetResourceId = + R.array.incoming_call_widget_video_receive_accept_reject_request_targets; + targetDescriptionsResourceId = + R.array.incoming_call_widget_video_receive_request_target_descriptions; + directionDescriptionsResourceId = R.array + .incoming_call_widget_video_upgrade_request_target_direction_descriptions; + handleDrawableResourceId = R.drawable.ic_incall_video_handle; + break; case TARGET_SET_FOR_AUDIO_WITHOUT_SMS: default: targetResourceId = R.array.incoming_call_widget_audio_without_sms_targets; @@ -338,12 +368,13 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente @Override public void onAnswer(int videoState, Context context) { + Log.d(this, "onAnswer videoState=" + videoState + " context=" + context); getPresenter().onAnswer(videoState, context); } @Override - public void onDecline() { - getPresenter().onDecline(); + public void onDecline(Context context) { + getPresenter().onDecline(context); } @Override diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java index e579d643a..208532572 100644 --- a/InCallUI/src/com/android/incallui/AnswerPresenter.java +++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java @@ -18,6 +18,7 @@ package com.android.incallui; import android.content.Context; import android.telecom.TelecomManager; +import android.telecom.VideoProfile; import java.util.List; @@ -35,6 +36,7 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> @Override public void onUiReady(AnswerUi ui) { + Log.d(this, "onUiReady ui=" + ui); super.onUiReady(ui); final CallList calls = CallList.getInstance(); @@ -43,9 +45,12 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> if (call != null) { processIncomingCall(call); } - call = calls.getVideoUpgradeRequestCall(); - if (call != null) { - processVideoUpgradeRequestCall(call); + + Call videoCall = calls.getVideoUpgradeRequestCall(); + Log.d(this, "getVideoUpgradeRequestCall call =" + call); + + if (videoCall != null && call == null) { + processVideoUpgradeRequestCall(videoCall); } // Listen for incoming calls. @@ -67,6 +72,7 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> @Override public void onCallListChange(CallList callList) { + Log.d(this, "onCallListChange callList=" + callList); // no-op } @@ -81,6 +87,13 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> // getting updates here. Log.d(this, "onIncomingCall: " + this); if (getUi() != null) { + Call modifyCall = CallList.getInstance().getVideoUpgradeRequestCall(); + if (modifyCall != null) { + getUi().showAnswerUi(false); + Log.d(this, "declining upgrade request id: "); + CallList.getInstance().removeCallUpdateListener(mCallId, this); + InCallPresenter.getInstance().declineUpgradeRequest(getUi().getContext()); + } if (!call.getId().equals(mCallId)) { // A new call is coming in. processIncomingCall(call); @@ -88,6 +101,31 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> } } + private boolean isVideoUpgradePending(Call call) { + return call.getSessionModificationState() + == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; + } + + @Override + public void onUpgradeToVideo(Call call) { + Log.d(this, "onUpgradeToVideo: " + this + " call=" + call); + if (getUi() == null) { + Log.d(this, "onUpgradeToVideo ui is null"); + return; + } + boolean isUpgradePending = isVideoUpgradePending(call); + InCallPresenter inCallPresenter = InCallPresenter.getInstance(); + if (isUpgradePending + && inCallPresenter.getInCallState() == InCallPresenter.InCallState.INCOMING) { + Log.d(this, "declining upgrade request"); + //If there is incoming call reject upgrade request + inCallPresenter.declineUpgradeRequest(getUi().getContext()); + } else if (isUpgradePending) { + Log.d(this, "process upgrade request as no MT call"); + processVideoUpgradeRequestCall(call); + } + } + private void processIncomingCall(Call call) { mCallId = call.getId(); mCall = call; @@ -102,28 +140,71 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> } private void processVideoUpgradeRequestCall(Call call) { + Log.d(this, " processVideoUpgradeRequestCall call=" + call); mCallId = call.getId(); mCall = call; // Listen for call updates for the current call. CallList.getInstance().addCallUpdateListener(mCallId, this); - getUi().showAnswerUi(true); - getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST); + final int currentVideoState = call.getVideoState(); + final int modifyToVideoState = call.getModifyToVideoState(); + + if (currentVideoState == modifyToVideoState) { + Log.w(this, "processVideoUpgradeRequestCall: Video states are same. Return."); + return; + } + + AnswerUi ui = getUi(); + + if (ui == null) { + Log.e(this, "Ui is null. Can't process upgrade request"); + return; + } + ui.showAnswerUi(true); + ui.showTargets(getUiTarget(currentVideoState, modifyToVideoState)); + + } + + private int getUiTarget(int currentVideoState, int modifyToVideoState) { + if (showVideoUpgradeOptions(currentVideoState, modifyToVideoState)) { + return AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST; + } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.BIDIRECTIONAL)) { + return AnswerFragment.TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST; + } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.TX_ENABLED)) { + return AnswerFragment.TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST; + } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.RX_ENABLED)) { + return AnswerFragment.TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST; + } + return AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST; + } + + private boolean showVideoUpgradeOptions(int currentVideoState, int modifyToVideoState) { + return currentVideoState == VideoProfile.VideoState.AUDIO_ONLY && + isEnabled(modifyToVideoState, VideoProfile.VideoState.BIDIRECTIONAL); + } + + private boolean isEnabled(int videoState, int mask) { + return (videoState & mask) == mask; } @Override public void onCallChanged(Call call) { Log.d(this, "onCallStateChange() " + call + " " + this); if (call.getState() != Call.State.INCOMING) { - // Stop listening for updates. - CallList.getInstance().removeCallUpdateListener(mCallId, this); + boolean isUpgradePending = isVideoUpgradePending(call); + if (!isUpgradePending) { + // Stop listening for updates. + CallList.getInstance().removeCallUpdateListener(mCallId, this); + } - getUi().showAnswerUi(false); + final Call incall = CallList.getInstance().getIncomingCall(); + if (incall != null || isUpgradePending) { + getUi().showAnswerUi(true); + } else { + getUi().showAnswerUi(false); + } - // mCallId will hold the state of the call. We don't clear the mCall variable here as - // it may be useful for sending text messages after phone disconnects. - mCallId = null; mHasTextMessages = false; } else if (!mHasTextMessages) { final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId()); @@ -134,14 +215,14 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> } public void onAnswer(int videoState, Context context) { + Log.d(this, "onAnswer mCallId=" + mCallId + " videoState=" + videoState); if (mCallId == null) { return; } - Log.d(this, "onAnswer " + mCallId); if (mCall.getSessionModificationState() == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { - InCallPresenter.getInstance().acceptUpgradeRequest(context); + InCallPresenter.getInstance().acceptUpgradeRequest(videoState, context); } else { TelecomAdapter.getInstance().answerCall(mCall.getId(), videoState); } @@ -151,9 +232,14 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> * TODO: We are using reject and decline interchangeably. We should settle on * reject since it seems to be more prevalent. */ - public void onDecline() { + public void onDecline(Context context) { Log.d(this, "onDecline " + mCallId); - TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null); + if (mCall.getSessionModificationState() + == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { + InCallPresenter.getInstance().declineUpgradeRequest(context); + } else { + TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null); + } } public void onText() { diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java index fd06e53b3..3d6f2032e 100644 --- a/InCallUI/src/com/android/incallui/Call.java +++ b/InCallUI/src/com/android/incallui/Call.java @@ -17,8 +17,10 @@ package com.android.incallui; import com.android.contacts.common.CallUtil; +import com.android.incallui.CallList.Listener; import android.content.Context; +import android.hardware.camera2.CameraCharacteristics; import android.net.Uri; import android.telecom.CallProperties; import android.telecom.DisconnectCause; @@ -118,8 +120,51 @@ public final class Call { public static final int WAITING_FOR_RESPONSE = 1; public static final int REQUEST_FAILED = 2; public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3; + public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4; + } + + public static class VideoSettings { + public static final int CAMERA_DIRECTION_UNKNOWN = -1; + public static final int CAMERA_DIRECTION_FRONT_FACING = + CameraCharacteristics.LENS_FACING_FRONT; + public static final int CAMERA_DIRECTION_BACK_FACING = + CameraCharacteristics.LENS_FACING_BACK; + + private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN; + + /** + * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, + * the video state of the call should be used to infer the camera direction. + * + * @see {@link CameraCharacteristics#LENS_FACING_FRONT} + * @see {@link CameraCharacteristics#LENS_FACING_BACK} + */ + public void setCameraDir(int cameraDirection) { + if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING + || cameraDirection == CAMERA_DIRECTION_BACK_FACING) { + mCameraDirection = cameraDirection; + } else { + mCameraDirection = CAMERA_DIRECTION_UNKNOWN; + } + } + + /** + * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, + * the video state of the call should be used to infer the camera direction. + * + * @see {@link CameraCharacteristics#LENS_FACING_FRONT} + * @see {@link CameraCharacteristics#LENS_FACING_BACK} + */ + public int getCameraDir() { + return mCameraDirection; + } + + public String toString() { + return "(CameraDir:" + getCameraDir() + ")"; + } } + private static final String ID_PREFIX = Call.class.getSimpleName() + "_"; private static int sIdCounter = 0; @@ -127,12 +172,16 @@ public final class Call { new android.telecom.Call.Listener() { @Override public void onStateChanged(android.telecom.Call call, int newState) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + " newState=" + + newState); update(); } @Override public void onParentChanged(android.telecom.Call call, android.telecom.Call newParent) { + Log.d(this, "TelecommCallListener onParentChanged call=" + call + " newParent=" + + newParent); update(); } @@ -145,29 +194,38 @@ public final class Call { @Override public void onDetailsChanged(android.telecom.Call call, android.telecom.Call.Details details) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + " details=" + + details); update(); } @Override public void onCannedTextResponsesLoaded(android.telecom.Call call, List<String> cannedTextResponses) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + + " cannedTextResponses=" + cannedTextResponses); update(); } @Override public void onPostDialWait(android.telecom.Call call, String remainingPostDialSequence) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + + " remainingPostDialSequence=" + remainingPostDialSequence); update(); } @Override public void onVideoCallChanged(android.telecom.Call call, VideoCall videoCall) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call + " videoCall=" + + videoCall); update(); } @Override public void onCallDestroyed(android.telecom.Call call) { + Log.d(this, "TelecommCallListener onStateChanged call=" + call); call.removeListener(mTelecommCallListener); } @@ -184,6 +242,11 @@ public final class Call { private DisconnectCause mDisconnectCause; private int mSessionModificationState; private final List<String> mChildCallIds = new ArrayList<>(); + private final VideoSettings mVideoSettings = new VideoSettings(); + /** + * mModifyToVideoState is used to store requested upgrade / downgrade video state + */ + private int mModifyToVideoState = VideoProfile.VideoState.AUDIO_ONLY; private InCallVideoCallListener mVideoCallListener; @@ -198,6 +261,14 @@ public final class Call { return mTelecommCall; } + /** + * @return video settings of the call, null if the call is not a video call. + * @see VideoProfile + */ + public VideoSettings getVideoSettings() { + return mVideoSettings; + } + private void update() { int oldState = getState(); updateFromTelecommCall(); @@ -209,7 +280,7 @@ public final class Call { } private void updateFromTelecommCall() { - Log.d(this, "updateFromTelecommCall: " + mTelecommCall); + Log.d(this, "updateFromTelecommCall: " + mTelecommCall.toString()); setState(translateState(mTelecommCall.getState())); setDisconnectCause(mTelecommCall.getDetails().getDisconnectCause()); @@ -370,24 +441,69 @@ public final class Call { return mTelecommCall.getDetails().getVideoState(); } + public int getCallSubstate() { + return mTelecommCall.getDetails().getCallSubstate(); + } + public boolean isVideoCall(Context context) { - // We want to show Video call buttons even if only one direction is enabled - // (That is what is happening when we receive a video call for example) - return CallUtil.isVideoEnabled(context) && ( - VideoProfile.VideoState.isBidirectional(getVideoState()) || - VideoProfile.VideoState.isReceptionEnabled(getVideoState()) || - VideoProfile.VideoState.isTransmissionEnabled(getVideoState())); + return CallUtil.isVideoEnabled(context) && + VideoProfile.VideoState.isVideo(getVideoState()); + } + + /** + * This method is called when we request for a video upgrade or downgrade. This handles the + * session modification state RECEIVED_UPGRADE_TO_VIDEO_REQUEST and sets the video state we + * want to upgrade/downgrade to. + */ + public void setSessionModificationTo(int videoState) { + Log.d(this, "setSessionModificationTo - video state= " + videoState); + if (videoState == getVideoState()) { + mSessionModificationState = Call.SessionModificationState.NO_REQUEST; + Log.w(this,"setSessionModificationTo - Clearing session modification state"); + } else { + mSessionModificationState = + Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; + setModifyToVideoState(videoState); + CallList.getInstance().onUpgradeToVideo(this); + } + + Log.d(this, "setSessionModificationTo - mSessionModificationState=" + + mSessionModificationState + " video state= " + videoState); + update(); } + /** + * This method is called to handle any other session modification states other than + * RECEIVED_UPGRADE_TO_VIDEO_REQUEST. We set the modification state and reset the video state + * when an upgrade request has been completed or failed. + */ public void setSessionModificationState(int state) { + if (state == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { + Log.e(this, + "setSessionModificationState not to be called for RECEIVED_UPGRADE_TO_VIDEO_REQUEST"); + return; + } + boolean hasChanged = mSessionModificationState != state; mSessionModificationState = state; - + Log.d(this, "setSessionModificationState " + state + " mSessionModificationState=" + + mSessionModificationState); + if (state != Call.SessionModificationState.WAITING_FOR_RESPONSE) { + setModifyToVideoState(VideoProfile.VideoState.AUDIO_ONLY); + } if (hasChanged) { update(); } } + private void setModifyToVideoState(int newVideoState) { + mModifyToVideoState = newVideoState; + } + + public int getModifyToVideoState() { + return mModifyToVideoState; + } + public static boolean areSame(Call call1, Call call2) { if (call1 == null && call2 == null) { return true; @@ -406,7 +522,7 @@ public final class Call { @Override public String toString() { return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " + - "videoState:%d]", + "videoState:%d, callSubState:%d, mSessionModificationState:%d, VideoSettings:%s]", mId, State.toString(getState()), android.telecom.Call.Details @@ -414,6 +530,13 @@ public final class Call { mChildCallIds, getParentId(), this.mTelecommCall.getConferenceableCalls(), - mTelecommCall.getDetails().getVideoState()); + mTelecommCall.getDetails().getVideoState(), + mTelecommCall.getDetails().getCallSubstate(), + mSessionModificationState, + getVideoSettings()); + } + + public String toSimpleString() { + return super.toString(); } } diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java index b2d109a6a..8682d65e5 100644 --- a/InCallUI/src/com/android/incallui/CallButtonFragment.java +++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java @@ -16,7 +16,9 @@ package com.android.incallui; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -26,6 +28,8 @@ import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.StateListDrawable; import android.os.Bundle; import android.telecom.AudioState; +import android.telecom.TelecomManager; +import android.telecom.VideoProfile; import android.view.ContextThemeWrapper; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; @@ -36,11 +40,13 @@ import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.PopupMenu; +import android.widget.Toast; import android.widget.PopupMenu.OnDismissListener; import android.widget.PopupMenu.OnMenuItemClickListener; import com.android.contacts.common.util.MaterialColorMapUtils; import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; +import java.util.ArrayList; /** * Fragment for call control buttons @@ -50,6 +56,7 @@ public class CallButtonFragment implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener, View.OnClickListener { private CompoundButton mAudioButton; + private static final int INVALID_INDEX = -1; private ImageButton mChangeToVoiceButton; private CompoundButton mMuteButton; private CompoundButton mShowDialpadButton; @@ -61,6 +68,7 @@ public class CallButtonFragment private ImageButton mMergeButton; private CompoundButton mPauseVideoButton; private ImageButton mOverflowButton; + private ImageButton mManageVideoCallConferenceButton; private PopupMenu mAudioModePopup; private boolean mAudioModePopupVisible; @@ -120,7 +128,9 @@ public class CallButtonFragment mPauseVideoButton.setOnClickListener(this); mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton); mOverflowButton.setOnClickListener(this); - + mManageVideoCallConferenceButton = (ImageButton) parent.findViewById( + R.id.manageVideoCallConferenceButton); + mManageVideoCallConferenceButton.setOnClickListener(this); return parent; } @@ -156,7 +166,8 @@ public class CallButtonFragment getPresenter().addCallClicked(); break; case R.id.changeToVoiceButton: - getPresenter().changeToVoiceClicked(); + // STOPSHIP One way video options + getPresenter().displayModifyCallOptions(); break; case R.id.muteButton: { getPresenter().muteClicked(!mMuteButton.isSelected()); @@ -177,7 +188,8 @@ public class CallButtonFragment getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected()); break; case R.id.changeToVideoButton: - getPresenter().changeToVideoClicked(); + // STOPSHIP One way video options + getPresenter().displayModifyCallOptions(); break; case R.id.switchCameraButton: getPresenter().switchCameraClicked( @@ -190,6 +202,9 @@ public class CallButtonFragment case R.id.overflowButton: mOverflowPopup.show(); break; + case R.id.manageVideoCallConferenceButton: + onManageVideoCallConferenceClicked(); + break; default: isClickHandled = false; Log.wtf(this, "onClick: unexpected"); @@ -329,6 +344,7 @@ public class CallButtonFragment mMergeButton.setEnabled(isEnabled); mPauseVideoButton.setEnabled(isEnabled); mOverflowButton.setEnabled(isEnabled); + mManageVideoCallConferenceButton.setEnabled(isEnabled); } @Override @@ -406,6 +422,10 @@ public class CallButtonFragment mAddCallButton.setVisibility(show ? View.VISIBLE : View.GONE); } + public void showManageConferenceVideoCallButton(boolean show) { + mManageVideoCallConferenceButton.setVisibility(show ? View.VISIBLE : View.GONE); + } + @Override public void showMergeButton(boolean show) { mMergeButton.setVisibility(show ? View.VISIBLE : View.GONE); @@ -431,9 +451,83 @@ public class CallButtonFragment mOverflowButton.setVisibility(show ? View.VISIBLE : View.GONE); } + /**The function is called when Modify Call button gets pressed. The function creates and + * displays modify call options. + */ + public void displayModifyCallOptions() { + CallButtonPresenter.CallButtonUi ui = getUi(); + if (ui == null) { + Log.e(this, "Cannot display ModifyCallOptions as ui is null"); + return; + } + + Context context = getContext(); + if (isTtyModeEnabled()) { + Toast.makeText(context, context.getResources().getString( + R.string.video_call_not_allowed_if_tty_enabled), + Toast.LENGTH_SHORT).show(); + return; + } + + final ArrayList<CharSequence> items = new ArrayList<CharSequence>(); + final ArrayList<Integer> itemToCallType = new ArrayList<Integer>(); + final Resources res = ui.getContext().getResources(); + // Prepare the string array and mapping. + items.add(res.getText(R.string.modify_call_option_voice)); + itemToCallType.add(VideoProfile.VideoState.AUDIO_ONLY); + + items.add(res.getText(R.string.modify_call_option_vt_rx)); + itemToCallType.add(VideoProfile.VideoState.RX_ENABLED); + + items.add(res.getText(R.string.modify_call_option_vt_tx)); + itemToCallType.add(VideoProfile.VideoState.TX_ENABLED); + + items.add(res.getText(R.string.modify_call_option_vt)); + itemToCallType.add(VideoProfile.VideoState.BIDIRECTIONAL); + + AlertDialog.Builder builder = new AlertDialog.Builder(getUi().getContext()); + builder.setTitle(R.string.modify_call_option_title); + final AlertDialog alert; + + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + Toast.makeText(getUi().getContext(), items.get(item), Toast.LENGTH_SHORT).show(); + final int selCallType = itemToCallType.get(item); + Log.v(this, "Videocall: ModifyCall: upgrade/downgrade to " + + fromCallType(selCallType)); + VideoProfile videoProfile = new VideoProfile(selCallType); + getPresenter().changeToVideoClicked(videoProfile); + dialog.dismiss(); + } + }; + int currVideoState = getPresenter().getCurrentVideoState(); + int currUnpausedVideoState = CallUtils.getUnPausedVideoState(currVideoState); + int index = itemToCallType.indexOf(currUnpausedVideoState); + if (index == INVALID_INDEX) { + return; + } + builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), index, listener); + alert = builder.create(); + alert.show(); + } + + public static String fromCallType(int callType) { + switch (callType) { + case VideoProfile.VideoState.BIDIRECTIONAL: + return "VT"; + case VideoProfile.VideoState.TX_ENABLED: + return "VT_TX"; + case VideoProfile.VideoState.RX_ENABLED: + return "VT_RX"; + } + return ""; + } + @Override public void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption, - boolean showHoldMenuOption, boolean showSwapMenuOption) { + boolean showHoldMenuOption, boolean showSwapMenuOption, + boolean showManageConferenceVideoCallOption) { if (mOverflowPopup == null) { final ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), R.style.InCallPopupMenuStyle); @@ -459,6 +553,9 @@ public class CallButtonFragment case R.id.overflow_swap_menu_item: getPresenter().addCallClicked(); break; + case R.id.overflow_manage_conference_menu_item: + onManageVideoCallConferenceClicked(); + break; default: Log.wtf(this, "onMenuItemClick: unexpected overflow menu click"); break; @@ -482,6 +579,8 @@ public class CallButtonFragment menu.findItem(R.id.overflow_resume_menu_item).setVisible( showHoldMenuOption && mHoldButton.isSelected()); menu.findItem(R.id.overflow_swap_menu_item).setVisible(showSwapMenuOption); + menu.findItem(R.id.overflow_manage_conference_menu_item).setVisible( + showManageConferenceVideoCallOption); mOverflowButton.setEnabled(menu.hasVisibleItems()); } @@ -561,6 +660,11 @@ public class CallButtonFragment } } + private void onManageVideoCallConferenceClicked() { + Log.d(this, "onManageVideoCallConferenceClicked"); + InCallPresenter.getInstance().showConferenceCallManager(true); + } + /** * Refreshes the "Audio mode" popup if it's visible. This is useful * (for example) when a wired headset is plugged or unplugged, @@ -789,4 +893,11 @@ public class CallButtonFragment public Context getContext() { return getActivity(); } + + private boolean isTtyModeEnabled() { + return (android.provider.Settings.Secure.getInt( + getContext().getContentResolver(), + android.provider.Settings.Secure.PREFERRED_TTY_MODE, + TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF); + } } diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java index 9fedc95b1..4e840be9f 100644 --- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java +++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java @@ -16,6 +16,7 @@ package com.android.incallui; +import android.app.AlertDialog; import android.content.Context; import android.os.Bundle; import android.telecom.AudioState; @@ -23,6 +24,7 @@ import android.telecom.InCallService.VideoCall; import android.telecom.VideoProfile; import com.android.incallui.AudioModeProvider.AudioModeListener; +import com.android.incallui.InCallCameraManager.Listener; import com.android.incallui.InCallPresenter.CanAddCallListener; import com.android.incallui.InCallPresenter.InCallState; import com.android.incallui.InCallPresenter.InCallStateListener; @@ -36,7 +38,7 @@ import java.util.Objects; */ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi> implements InCallStateListener, AudioModeListener, IncomingCallListener, - InCallDetailsListener, CanAddCallListener { + InCallDetailsListener, CanAddCallListener, Listener { private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted"; private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state"; @@ -44,6 +46,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto private Call mCall; private boolean mAutomaticallyMuted = false; private boolean mPreviousMuteState = false; + private static final int BUTTON_THRESOLD_TO_DISPLAY_OVERFLOW_MENU = 5; public CallButtonPresenter() { } @@ -59,6 +62,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto InCallPresenter.getInstance().addIncomingCallListener(this); InCallPresenter.getInstance().addDetailsListener(this); InCallPresenter.getInstance().addCanAddCallListener(this); + InCallPresenter.getInstance().getInCallCameraManager().addCameraSelectionListener(this); } @Override @@ -69,6 +73,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto AudioModeProvider.getInstance().removeListener(this); InCallPresenter.getInstance().removeIncomingCallListener(this); InCallPresenter.getInstance().removeDetailsListener(this); + InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this); } @Override @@ -248,16 +253,21 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto getUi().displayDialpad(checked /* show */, true /* animate */); } - public void changeToVideoClicked() { + public void displayModifyCallOptions() { + getUi().displayModifyCallOptions(); + } + + public int getCurrentVideoState() { + return mCall.getVideoState(); + } + + public void changeToVideoClicked(VideoProfile videoProfile) { VideoCall videoCall = mCall.getVideoCall(); if (videoCall == null) { return; } - VideoProfile videoProfile = - new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL); videoCall.sendSessionModifyRequest(videoProfile); - mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE); } @@ -277,12 +287,16 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto String cameraId = cameraManager.getActiveCameraId(); if (cameraId != null) { + final int cameraDir = cameraManager.isUsingFrontFacingCamera() + ? Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING + : Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING; + mCall.getVideoSettings().setCameraDir(cameraDir); videoCall.setCamera(cameraId); videoCall.requestCameraCapabilities(); } - getUi().setSwitchCameraButton(!useFrontFacingCamera); } + /** * Stop or start client's video transmission. * @param pause True if pausing the local user's video, or false if starting the local user's @@ -331,6 +345,10 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto ui.enableMute(call.can(android.telecom.Call.Details.CAPABILITY_MUTE)); } + private static int toInteger(boolean b) { + return b ? 1 : 0; + } + /** * Updates the buttons applicable for the UI. * @@ -338,29 +356,17 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto * @param context The context. */ private void updateCallButtons(Call call, Context context) { - if (call.isVideoCall(context)) { + if (CallUtils.isVideoCall(call)) { updateVideoCallButtons(call); - } else { - updateVoiceCallButtons(call); } + updateVoiceCallButtons(call); } private void updateVideoCallButtons(Call call) { Log.v(this, "Showing buttons for video call."); final CallButtonUi ui = getUi(); - // Hide all voice-call-related buttons. - ui.showAudioButton(false); - ui.showDialpadButton(false); - ui.showHoldButton(false); - ui.showSwapButton(false); - ui.showChangeToVideoButton(false); - ui.showAddCallButton(false); - ui.showMergeButton(false); - ui.showOverflowButton(false); - // Show all video-call-related buttons. - ui.showChangeToVoiceButton(true); ui.showSwitchCameraButton(true); ui.showPauseVideoButton(true); @@ -392,6 +398,10 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE)); Log.v(this, "Show add call ", TelecomAdapter.getInstance().canAddCall()); Log.v(this, "Show mute ", call.can(android.telecom.Call.Details.CAPABILITY_MUTE)); + Log.v(this, "Show video call local:", + call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL) + + " remote: " + + call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE)); final boolean canAdd = TelecomAdapter.getInstance().canAddCall(); final boolean enableHoldOption = call.can(android.telecom.Call.Details.CAPABILITY_HOLD); @@ -401,11 +411,13 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto boolean canVideoCall = call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL) && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE); ui.showChangeToVideoButton(canVideoCall); - ui.enableChangeToVideoButton(!isCallOnHold); final boolean showMergeOption = call.can( android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE); final boolean showAddCallOption = canAdd; + final boolean showManageVideoCallConferenceOption = call.can( + android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) + && CallUtils.isVideoCall(call); // Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available: // (1) If the device normally can hold, show HOLD in a disabled state. @@ -415,38 +427,55 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto final boolean showHoldOption = !showSwapOption && (enableHoldOption || supportHold); ui.setHold(isCallOnHold); - // If we show video upgrade and add/merge and hold/swap, the overflow menu is needed. - final boolean isVideoOverflowScenario = canVideoCall - && (showAddCallOption || showMergeOption) && (showHoldOption || showSwapOption); - // If we show hold/swap, add, and merge simultaneously, the overflow menu is needed. - final boolean isOverflowScenario = - (showHoldOption || showSwapOption) && showMergeOption && showAddCallOption; + //Initialize buttonCount = 2. Because speaker and dialpad these two always show in Call UI. + int buttonCount = 2; + buttonCount += toInteger(canVideoCall); + buttonCount += toInteger(showAddCallOption); + buttonCount += toInteger(showMergeOption); + buttonCount += toInteger(showHoldOption); + buttonCount += toInteger(showSwapOption); + buttonCount += toInteger(call.can(android.telecom.Call.Details.CAPABILITY_MUTE)); + buttonCount += toInteger(showManageVideoCallConferenceOption); + + Log.v(this, "show ManageVideoCallConference: " + showManageVideoCallConferenceOption); + Log.v(this, "No of InCall buttons: " + buttonCount + " canVideoCall: " + canVideoCall); + + // Show overflow menu if number of buttons is greater than 5. + final boolean showOverflowMenu = + buttonCount > BUTTON_THRESOLD_TO_DISPLAY_OVERFLOW_MENU; + final boolean isVideoOverflowScenario = canVideoCall && showOverflowMenu; + final boolean isOverflowScenario = !canVideoCall && showOverflowMenu; if (isVideoOverflowScenario) { ui.showHoldButton(false); ui.showSwapButton(false); ui.showAddCallButton(false); ui.showMergeButton(false); + ui.showManageConferenceVideoCallButton(false); ui.configureOverflowMenu( showMergeOption, showAddCallOption /* showAddMenuOption */, showHoldOption && enableHoldOption /* showHoldMenuOption */, - showSwapOption); + showSwapOption, + showManageVideoCallConferenceOption); ui.showOverflowButton(true); } else { if (isOverflowScenario) { ui.showAddCallButton(false); ui.showMergeButton(false); + ui.showManageConferenceVideoCallButton(false); ui.configureOverflowMenu( showMergeOption, showAddCallOption /* showAddMenuOption */, false /* showHoldMenuOption */, - false /* showSwapMenuOption */); + false /* showSwapMenuOption */, + showManageVideoCallConferenceOption); } else { ui.showMergeButton(showMergeOption); ui.showAddCallButton(showAddCallOption); + ui.showManageConferenceVideoCallButton(showManageVideoCallConferenceOption); } ui.showOverflowButton(isOverflowScenario); @@ -500,16 +529,27 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto void showSwitchCameraButton(boolean show); void setSwitchCameraButton(boolean isBackFacingCamera); void showAddCallButton(boolean show); + void showManageConferenceVideoCallButton(boolean show); void showMergeButton(boolean show); void showPauseVideoButton(boolean show); void setPauseVideoButton(boolean isPaused); void showOverflowButton(boolean show); void displayDialpad(boolean on, boolean animate); + void displayModifyCallOptions(); boolean isDialpadVisible(); void setAudio(int mode); void setSupportedAudio(int mask); void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption, - boolean showHoldMenuOption, boolean showSwapMenuOption); + boolean showHoldMenuOption, boolean showSwapMenuOption, + boolean showManageConferenceVideoCallOption); Context getContext(); } + + @Override + public void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera) { + if (getUi() == null) { + return; + } + getUi().setSwitchCameraButton(!isUsingFrontFacingCamera); + } } diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java index 6550eecdc..9f43502e0 100644 --- a/InCallUI/src/com/android/incallui/CallCardFragment.java +++ b/InCallUI/src/com/android/incallui/CallCardFragment.java @@ -557,7 +557,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mCallStateIcon.setVisibility(View.GONE); } - if (VideoProfile.VideoState.isBidirectional(videoState) + if (VideoProfile.VideoState.isVideo(videoState) || (state == Call.State.ACTIVE && sessionModificationState == Call.SessionModificationState.WAITING_FOR_RESPONSE)) { mCallStateVideoCallIcon.setVisibility(View.VISIBLE); @@ -683,6 +683,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } else if (sessionModificationState == Call.SessionModificationState.WAITING_FOR_RESPONSE) { callStateLabel = context.getString(R.string.card_title_video_call_requesting); + } else if (VideoProfile.VideoState.isVideo(videoState) && + VideoProfile.VideoState.isPaused(videoState)) { + callStateLabel = context.getString(R.string.card_title_video_call_paused); } else if (VideoProfile.VideoState.isBidirectional(videoState)) { callStateLabel = context.getString(R.string.card_title_video_call); } diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index 3a4c46a60..7eae5c602 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -320,7 +320,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> return false; } - return mPrimary.can(Details.CAPABILITY_MANAGE_CONFERENCE); + return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) + && !mPrimary.isVideoCall(mContext); } private void setCallbackNumber() { diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java index db43b1657..f2d04cf11 100644 --- a/InCallUI/src/com/android/incallui/CallList.java +++ b/InCallUI/src/com/android/incallui/CallList.java @@ -72,7 +72,9 @@ public class CallList implements InCallPhoneListener { @Override public void onCallAdded(Phone phone, android.telecom.Call telecommCall) { Call call = new Call(telecommCall); - if (call.getState() == Call.State.INCOMING) { + Log.d(this, "onCallAdded: callState=" + call.getState()); + if (call.getState() == Call.State.INCOMING || + call.getState() == Call.State.CALL_WAITING) { onIncoming(call, call.getCannedSmsResponses()); } else { onUpdate(call); @@ -135,6 +137,12 @@ public class CallList implements InCallPhoneListener { } } + public void onUpgradeToVideo(Call call){ + Log.d(this, "onUpgradeToVideo call=" + call); + for (Listener listener : mListeners) { + listener.onUpgradeToVideo(call); + } + } /** * Called when a single call has changed. */ @@ -539,7 +547,11 @@ public class CallList implements InCallPhoneListener { * incoming calls. */ public void onIncomingCall(Call call); - + /** + * Called when a new modify call request comes in + * This is the only method that gets called for modify requests. + */ + public void onUpgradeToVideo(Call call); /** * Called anytime there are changes to the call list. The change can be switching call * states, updating information, etc. This method will NOT be called for new incoming diff --git a/InCallUI/src/com/android/incallui/CallUtils.java b/InCallUI/src/com/android/incallui/CallUtils.java new file mode 100644 index 000000000..80b553aa7 --- /dev/null +++ b/InCallUI/src/com/android/incallui/CallUtils.java @@ -0,0 +1,90 @@ +/* Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import android.telecom.VideoProfile; + +import com.google.common.base.Preconditions; + +public class CallUtils { + + public static boolean isVideoCall(Call call) { + return call != null && VideoProfile.VideoState.isVideo(call.getVideoState()); + } + + public static boolean isIncomingVideoCall(Call call) { + if (!CallUtils.isVideoCall(call)) { + return false; + } + final int state = call.getState(); + return (state == Call.State.INCOMING) || (state == Call.State.CALL_WAITING); + } + + public static boolean isActiveVideoCall(Call call) { + return CallUtils.isVideoCall(call) && call.getState() == Call.State.ACTIVE; + } + + public static boolean isOutgoingVideoCall(Call call) { + if (!CallUtils.isVideoCall(call)) { + return false; + } + final int state = call.getState(); + return Call.State.isDialing(state) || state == Call.State.CONNECTING + || state == Call.State.PRE_DIAL_WAIT; + } + + public static boolean isAudioCall(Call call) { + return call != null && VideoProfile.VideoState.isAudioOnly(call.getVideoState()); + } + + // TODO (ims-vt) Check if special handling is needed for CONF calls. + public static boolean canVideoPause(Call call) { + return isVideoCall(call) && call.getState() == Call.State.ACTIVE; + } + + public static VideoProfile makeVideoPauseProfile(Call call) { + Preconditions.checkNotNull(call); + Preconditions.checkState(!VideoProfile.VideoState.isAudioOnly(call.getVideoState())); + return new VideoProfile(getPausedVideoState(call.getVideoState())); + } + + public static VideoProfile makeVideoUnPauseProfile(Call call) { + Preconditions.checkNotNull(call); + return new VideoProfile(getUnPausedVideoState(call.getVideoState())); + } + + public static int getUnPausedVideoState(int videoState) { + return videoState & (~VideoProfile.VideoState.PAUSED); + } + + public static int getPausedVideoState(int videoState) { + return videoState | VideoProfile.VideoState.PAUSED; + } + +} diff --git a/InCallUI/src/com/android/incallui/GlowPadWrapper.java b/InCallUI/src/com/android/incallui/GlowPadWrapper.java index b50fdd8c2..584ce65de 100644 --- a/InCallUI/src/com/android/incallui/GlowPadWrapper.java +++ b/InCallUI/src/com/android/incallui/GlowPadWrapper.java @@ -108,7 +108,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger @Override public void onTrigger(View v, int target) { - Log.d(this, "onTrigger()"); + Log.d(this, "onTrigger() view=" + v + " target=" + target); final int resId = getResourceIdForTarget(target); switch (resId) { case R.drawable.ic_lockscreen_answer: @@ -116,7 +116,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger mTargetTriggered = true; break; case R.drawable.ic_lockscreen_decline: - mAnswerListener.onDecline(); + mAnswerListener.onDecline(getContext()); mTargetTriggered = true; break; case R.drawable.ic_lockscreen_text: @@ -128,6 +128,14 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger mAnswerListener.onAnswer(VideoProfile.VideoState.BIDIRECTIONAL, getContext()); mTargetTriggered = true; break; + case R.drawable.ic_lockscreen_answer_tx_video: + mAnswerListener.onAnswer(VideoProfile.VideoState.TX_ENABLED, getContext()); + mTargetTriggered = true; + break; + case R.drawable.ic_lockscreen_answer_rx_video: + mAnswerListener.onAnswer(VideoProfile.VideoState.RX_ENABLED, getContext()); + mTargetTriggered = true; + break; case R.drawable.ic_toolbar_video_off: InCallPresenter.getInstance().declineUpgradeRequest(getContext()); mTargetTriggered = true; @@ -154,7 +162,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger public interface AnswerListener { void onAnswer(int videoState, Context context); - void onDecline(); + void onDecline(Context context); void onText(); } } diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java index f75c33b23..873c288a4 100644 --- a/InCallUI/src/com/android/incallui/InCallActivity.java +++ b/InCallUI/src/com/android/incallui/InCallActivity.java @@ -105,10 +105,9 @@ public class InCallActivity extends Activity { }; /** - * Stores the current orientation of the activity. Used to determine if a change in orientation - * has occurred. + * Used to determine if a change in orientation has occurred. */ - private int mCurrentOrientation; + private static int sCurrentOrientation = Configuration.ORIENTATION_UNDEFINED; @Override protected void onCreate(Bundle icicle) { @@ -143,9 +142,8 @@ public class InCallActivity extends Activity { internalResolveIntent(getIntent()); - mCurrentOrientation = getResources().getConfiguration().orientation; - mIsLandscape = getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; + mIsLandscape = getResources().getConfiguration().orientation == + Configuration.ORIENTATION_LANDSCAPE; final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; @@ -193,6 +191,11 @@ public class InCallActivity extends Activity { // setting activity should be last thing in setup process InCallPresenter.getInstance().setActivity(this); + + // It is possible that the activity restarted because orientation changed. + // Notify listeners if orientation changed. + doOrientationChanged(getResources().getConfiguration().orientation); + InCallPresenter.getInstance().onActivityStarted(); } @Override @@ -244,6 +247,9 @@ public class InCallActivity extends Activity { @Override protected void onStop() { Log.d(this, "onStop()..."); + + InCallPresenter.getInstance().updateIsChangingConfigurations(); + InCallPresenter.getInstance().onActivityStopped(); super.onStop(); } @@ -251,6 +257,7 @@ public class InCallActivity extends Activity { protected void onDestroy() { Log.d(this, "onDestroy()... this = " + this); InCallPresenter.getInstance().unsetActivity(this); + InCallPresenter.getInstance().updateIsChangingConfigurations(); super.onDestroy(); } @@ -446,15 +453,22 @@ public class InCallActivity extends Activity { InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config); Log.d(this, "onConfigurationChanged "+config.orientation); + doOrientationChanged(config.orientation); + super.onConfigurationChanged(config); + } + + + private void doOrientationChanged(int orientation) { + Log.d(this, "doOrientationChanged prevOrientation=" + sCurrentOrientation + + " newOrientation=" + orientation); // Check to see if the orientation changed to prevent triggering orientation change events // for other configuration changes. - if (config.orientation != mCurrentOrientation) { - mCurrentOrientation = config.orientation; + if (orientation != sCurrentOrientation) { + sCurrentOrientation = orientation; InCallPresenter.getInstance().onDeviceRotationChange( getWindowManager().getDefaultDisplay().getRotation()); - InCallPresenter.getInstance().onDeviceOrientationChange(mCurrentOrientation); + InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation); } - super.onConfigurationChanged(config); } public CallButtonFragment getCallButtonFragment() { diff --git a/InCallUI/src/com/android/incallui/InCallApp.java b/InCallUI/src/com/android/incallui/InCallApp.java index d6f4f42de..a273d7805 100644 --- a/InCallUI/src/com/android/incallui/InCallApp.java +++ b/InCallUI/src/com/android/incallui/InCallApp.java @@ -81,7 +81,9 @@ public class InCallApp extends Application { } else if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) { InCallPresenter.getInstance().hangUpOngoingCall(context); } else if (action.equals(ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST)) { - InCallPresenter.getInstance().acceptUpgradeRequest(context); + //TODO: Change calltype after adding support for TX and RX + InCallPresenter.getInstance().acceptUpgradeRequest( + VideoProfile.VideoState.BIDIRECTIONAL, context); } else if (action.equals(ACTION_DECLINE_VIDEO_UPGRADE_REQUEST)) { InCallPresenter.getInstance().declineUpgradeRequest(context); } diff --git a/InCallUI/src/com/android/incallui/InCallCameraManager.java b/InCallUI/src/com/android/incallui/InCallCameraManager.java index ded9387f3..b7ec079af 100644 --- a/InCallUI/src/com/android/incallui/InCallCameraManager.java +++ b/InCallUI/src/com/android/incallui/InCallCameraManager.java @@ -25,12 +25,22 @@ import android.hardware.camera2.params.StreamConfigurationMap; import android.util.Size; import java.lang.String; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; /** * Used to track which camera is used for outgoing video. */ public class InCallCameraManager { + public interface Listener { + void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera); + } + + private final Set<Listener> mCameraSelectionListeners = Collections. + newSetFromMap(new ConcurrentHashMap<Listener, Boolean>(8,0.9f,1)); + /** * The camera ID for the front facing camera. */ @@ -73,6 +83,9 @@ public class InCallCameraManager { */ public void setUseFrontFacingCamera(boolean useFrontFacingCamera) { mUseFrontFacingCamera = useFrontFacingCamera; + for (Listener listener : mCameraSelectionListeners) { + listener.onActiveCameraSelectionChanged(mUseFrontFacingCamera); + } } /** @@ -148,4 +161,16 @@ public class InCallCameraManager { } } } + + public void addCameraSelectionListener(Listener listener) { + if (listener != null) { + mCameraSelectionListeners.add(listener); + } + } + + public void removeCameraSelectionListener(Listener listener) { + if (listener != null) { + mCameraSelectionListeners.remove(listener); + } + } } diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java index 7c71d2c86..74c85db8a 100644 --- a/InCallUI/src/com/android/incallui/InCallPresenter.java +++ b/InCallUI/src/com/android/incallui/InCallPresenter.java @@ -34,6 +34,8 @@ import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.Surface; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import com.google.common.base.Preconditions; @@ -156,7 +158,6 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { */ private boolean mIsActivityPreviouslyStarted = false; - /** * Whether or not to wait for the circular reveal animation to be started, to avoid stopping * the circular reveal animation activity before the animation is initiated. @@ -175,6 +176,14 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { */ private boolean mServiceBound = false; + /** + * When configuration changes Android kills the current activity and starts a new one. + * The flag is used to check if full clean up is necessary (activity is stopped and new + * activity won't be started), or if a new activity will be started right after the current one + * is destroyed, and therefore no need in release all resources. + */ + private boolean mIsChangingConfigurations = false; + private Phone mPhone; private Handler mHandler = new Handler(); @@ -243,6 +252,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { // will kick off an update and the whole process can start. mCallList.addListener(this); + VideoPauseController.getInstance().setUp(this); + Log.d(this, "Finished InCallPresenter.setUp"); } @@ -258,6 +269,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { Log.d(this, "tearDown"); mServiceConnected = false; attemptCleanup(); + + VideoPauseController.getInstance().tearDown(); } private void attemptFinishActivity() { @@ -398,9 +411,12 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { if (callList == null) { return; } + Log.d(this, "onCallListChange callList=" + callList.toString() ); InCallState newState = getPotentialStateFromCallList(callList); InCallState oldState = mInCallState; + Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState); newState = startOrFinishUi(newState); + Log.d(this, "onCallListChange newState changed to " + newState); // Set the new state before announcing it to the world Log.i(this, "Phone switching state: " + oldState + " -> " + newState); @@ -437,6 +453,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } + @Override + public void onUpgradeToVideo(Call call) { + //NO-OP + } /** * Called when a call becomes disconnected. Called everytime an existing call * changes from being connected (incoming/outgoing/active) to disconnected. @@ -631,33 +651,36 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } - public void acceptUpgradeRequest(Context context) { + public void acceptUpgradeRequest(int videoState, Context context) { + Log.d(this, " acceptUpgradeRequest videoState " + videoState); // Bail if we have been shut down and the call list is null. if (mCallList == null) { StatusBarNotifier.clearInCallNotification(context); + Log.e(this, " acceptUpgradeRequest mCallList is empty so returning"); return; } Call call = mCallList.getVideoUpgradeRequestCall(); if (call != null) { - VideoProfile videoProfile = - new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL); + VideoProfile videoProfile = new VideoProfile(videoState); call.getVideoCall().sendSessionModifyResponse(videoProfile); call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); } } public void declineUpgradeRequest(Context context) { + Log.d(this, " declineUpgradeRequest"); // Bail if we have been shut down and the call list is null. if (mCallList == null) { StatusBarNotifier.clearInCallNotification(context); + Log.e(this, " declineUpgradeRequest mCallList is empty so returning"); return; } Call call = mCallList.getVideoUpgradeRequestCall(); if (call != null) { VideoProfile videoProfile = - new VideoProfile(VideoProfile.VideoState.AUDIO_ONLY); + new VideoProfile(call.getVideoState()); call.getVideoCall().sendSessionModifyResponse(videoProfile); call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); } @@ -685,6 +708,20 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { return mIsActivityPreviouslyStarted; } + public boolean isChangingConfigurations() { + return mIsChangingConfigurations; + } + + /*package*/ + void updateIsChangingConfigurations() { + mIsChangingConfigurations = false; + if (mInCallActivity != null) { + mIsChangingConfigurations = mInCallActivity.isChangingConfigurations(); + } + Log.d(this, "IsChangingConfigurations=" + mIsChangingConfigurations); + } + + /** * Called when the activity goes in/out of the foreground. */ @@ -716,6 +753,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { mIsActivityPreviouslyStarted = true; } else { CircularRevealActivity.sendClearDisplayBroadcast(mContext); + updateIsChangingConfigurations(); } for (InCallUiListener listener : mInCallUiListeners) { @@ -731,6 +769,26 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { return mInCallUiListeners.remove(listener); } + /*package*/ + void onActivityStarted() { + Log.d(this, "onActivityStarted"); + notifyVideoPauseController(true); + } + + /*package*/ + void onActivityStopped() { + Log.d(this, "onActivityStopped"); + notifyVideoPauseController(false); + } + + private void notifyVideoPauseController(boolean showing) { + Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" + + mIsChangingConfigurations); + if (!mIsChangingConfigurations) { + VideoPauseController.getInstance().onUiShowing(showing); + } + } + /** * Brings the app into the foreground if possible. */ @@ -1088,6 +1146,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { if (shouldCleanup) { mIsActivityPreviouslyStarted = false; + mIsChangingConfigurations = false; // blow away stale contact info so that we get fresh data on // the next set of calls @@ -1119,6 +1178,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { mListeners.clear(); mIncomingCallListeners.clear(); + mDetailsListeners.clear(); + mCanAddCallListeners.clear(); + mOrientationListeners.clear(); + mInCallEventListeners.clear(); Log.d(this, "Finished InCallPresenter.CleanUp"); } @@ -1252,7 +1315,20 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { * @param rotation The device rotation. */ public void onDeviceRotationChange(int rotation) { + Log.d(this, "onDeviceRotationChange: rotation=" + rotation); // First translate to rotation in degrees. + if (mCallList != null) { + mCallList.notifyCallsOfDeviceRotation(toRotationAngle(rotation)); + } else { + Log.w(this, "onDeviceRotationChange: CallList is null."); + } + } + + /** + * Converts rotation constants to rotation in degrees. + * @param rotation Rotation constants. + */ + public static int toRotationAngle(int rotation) { int rotationAngle; switch (rotation) { case Surface.ROTATION_0: @@ -1270,8 +1346,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { default: rotationAngle = 0; } - - mCallList.notifyCallsOfDeviceRotation(rotationAngle); + return rotationAngle; } /** @@ -1292,6 +1367,11 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { * and landscape. {@Code False} if the in-call UI should be locked in portrait. */ public void setInCallAllowsOrientationChange(boolean allowOrientationChange) { + if (mInCallActivity == null) { + Log.e(this, "InCallActivity is null. Can't set requested orientation."); + return; + } + if (!allowOrientationChange) { mInCallActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); } else { @@ -1299,6 +1379,21 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } + public void enableScreenTimeout(boolean enable) { + Log.v(this, "enableScreenTimeout: value=" + enable); + if (mInCallActivity == null) { + Log.e(this, "enableScreenTimeout: InCallActivity is null."); + return; + } + + final Window window = mInCallActivity.getWindow(); + if (enable) { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + /** * Returns the space available beside the call card. * diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java index 245fc7316..cf2b859e0 100644 --- a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java +++ b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java @@ -18,6 +18,7 @@ package com.android.incallui; import android.telecom.CameraCapabilities; import android.telecom.Connection; +import android.telecom.Connection.VideoProvider; import android.telecom.InCallService.VideoCall; import android.telecom.VideoProfile; @@ -47,53 +48,50 @@ public class InCallVideoCallListener extends VideoCall.Listener { */ @Override public void onSessionModifyRequestReceived(VideoProfile videoProfile) { - int previousVideoState = mCall.getVideoState(); - int newVideoState = videoProfile.getVideoState(); + Log.d(this, " onSessionModifyRequestReceived videoProfile=" + videoProfile); + int previousVideoState = CallUtils.getUnPausedVideoState(mCall.getVideoState()); + int newVideoState = CallUtils.getUnPausedVideoState(videoProfile.getVideoState()); - boolean wasVideoCall = VideoProfile.VideoState.isBidirectional(previousVideoState); - boolean isVideoCall = VideoProfile.VideoState.isBidirectional(newVideoState); - - boolean wasPaused = VideoProfile.VideoState.isPaused(previousVideoState); - boolean isPaused = VideoProfile.VideoState.isPaused(newVideoState); + boolean wasVideoCall = VideoProfile.VideoState.isVideo(previousVideoState); + boolean isVideoCall = VideoProfile.VideoState.isVideo(newVideoState); // Check for upgrades to video and downgrades to audio. - if (!wasVideoCall && isVideoCall) { - InCallVideoCallListenerNotifier.getInstance().upgradeToVideoRequest(mCall); - } else if (wasVideoCall && !isVideoCall) { + if (wasVideoCall && !isVideoCall) { InCallVideoCallListenerNotifier.getInstance().downgradeToAudio(mCall); + } else if (previousVideoState != newVideoState) { + InCallVideoCallListenerNotifier.getInstance().upgradeToVideoRequest(mCall, + newVideoState); } - - boolean pause = !wasPaused && isPaused; - InCallVideoCallListenerNotifier.getInstance().peerPausedStateChanged(mCall, pause); } /** * Handles a session modification response. * - * @param status Status of the session modify request. Valid values are - * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, - * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, - * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID} + * @param status Status of the session modify request. Valid values are + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID} * @param requestedProfile * @param responseProfile The actual profile changes made by the peer device. */ @Override - public void onSessionModifyResponseReceived( - int status, VideoProfile requestedProfile, VideoProfile responseProfile) { - boolean modifySucceeded = - requestedProfile.getVideoState() == responseProfile.getVideoState(); - boolean isVideoCall = - VideoProfile.VideoState.isBidirectional(responseProfile.getVideoState()); - - if (modifySucceeded && isVideoCall) { - // Local Upgrade success - InCallVideoCallListenerNotifier.getInstance().upgradeToVideoSuccess(mCall); - } else if (!modifySucceeded || status != Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { - // Remote didn't accept invitation in bidirectional state or failure - InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(mCall); - } else if (modifySucceeded && !isVideoCall) { - // Local Downgrade success (should always be successful) - InCallVideoCallListenerNotifier.getInstance().downgradeToAudio(mCall); + public void onSessionModifyResponseReceived(int status, VideoProfile requestedProfile, + VideoProfile responseProfile) { + Log.d(this, "onSessionModifyResponseReceived status=" + status + " requestedProfile=" + + requestedProfile + " responseProfile=" + responseProfile); + if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { + InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(status, mCall); + } else if (requestedProfile != null && responseProfile != null) { + boolean modifySucceeded = requestedProfile.getVideoState() == + responseProfile.getVideoState(); + boolean isVideoCall = VideoProfile.VideoState.isVideo(responseProfile.getVideoState()); + if (modifySucceeded && isVideoCall) { + InCallVideoCallListenerNotifier.getInstance().upgradeToVideoSuccess(mCall); + } else if (!modifySucceeded) { + InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(status, mCall); + } + } else { + Log.d(this, "onSessionModifyResponseReceived request and response Profiles are null"); } } @@ -104,6 +102,7 @@ public class InCallVideoCallListener extends VideoCall.Listener { */ @Override public void onCallSessionEvent(int event) { + InCallVideoCallListenerNotifier.getInstance().callSessionEvent(event); } /** @@ -118,13 +117,25 @@ public class InCallVideoCallListener extends VideoCall.Listener { } /** + * Handles a change to the video quality of the call. + * + * @param videoQuality The updated video call quality. + */ + @Override + public void onVideoQualityChanged(int videoQuality) { + InCallVideoCallListenerNotifier.getInstance().videoQualityChanged(mCall, videoQuality); + } + + /** * Handles a change to the call data usage. No implementation as the in-call UI does not * display data usage. * * @param dataUsage The updated data usage. */ @Override - public void onCallDataUsageChanged(int dataUsage) { + public void onCallDataUsageChanged(long dataUsage) { + Log.d(this, "onCallDataUsageChanged: dataUsage = " + dataUsage); + InCallVideoCallListenerNotifier.getInstance().callDataUsageChanged(dataUsage); } /** diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java b/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java index 9f3f062cd..818ed032a 100644 --- a/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java +++ b/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java @@ -123,12 +123,13 @@ public class InCallVideoCallListenerNotifier { /** * Inform listeners of an upgrade to video request for a call. - * * @param call The call. + * @param videoState The video state we want to upgrade to. */ - public void upgradeToVideoRequest(Call call) { + public void upgradeToVideoRequest(Call call, int videoState) { + Log.d(this, "upgradeToVideoRequest call = " + call + " new video state = " + videoState); for (SessionModificationListener listener : mSessionModificationListeners) { - listener.onUpgradeToVideoRequest(call); + listener.onUpgradeToVideoRequest(call, videoState); } } @@ -148,9 +149,9 @@ public class InCallVideoCallListenerNotifier { * * @param call The call. */ - public void upgradeToVideoFail(Call call) { + public void upgradeToVideoFail(int status, Call call) { for (SessionModificationListener listener : mSessionModificationListeners) { - listener.onUpgradeToVideoFail(call); + listener.onUpgradeToVideoFail(status, call); } } @@ -166,6 +167,17 @@ public class InCallVideoCallListenerNotifier { } /** + * Inform listeners of a call session event. + * + * @param event The call session event. + */ + public void callSessionEvent(int event) { + for (VideoEventListener listener : mVideoEventListeners) { + listener.onCallSessionEvent(event); + } + } + + /** * Inform listeners of a downgrade to audio. * * @param call The call. @@ -178,6 +190,18 @@ public class InCallVideoCallListenerNotifier { } /** + * Inform listeners of any change in the video quality of the call + * + * @param call The call. + * @param videoQuality The updated video quality of the call. + */ + public void videoQualityChanged(Call call, int videoQuality) { + for (VideoEventListener listener : mVideoEventListeners) { + listener.onVideoQualityChanged(call, videoQuality); + } + } + + /** * Inform listeners of a change to peer dimensions. * * @param call The call. @@ -204,6 +228,17 @@ public class InCallVideoCallListenerNotifier { } /** + * Inform listeners of a change to call data usage. + * + * @param dataUsage data usage value + */ + public void callDataUsageChanged(long dataUsage) { + for (VideoEventListener listener : mVideoEventListeners) { + listener.onCallDataUsageChange(dataUsage); + } + } + + /** * Listener interface for any class that wants to be notified of upgrade to video and downgrade * to audio session modification requests. */ @@ -212,8 +247,9 @@ public class InCallVideoCallListenerNotifier { * Called when a peer request is received to upgrade an audio-only call to a video call. * * @param call The call the request was received for. + * @param videoState The video state that the request wants to upgrade to. */ - public void onUpgradeToVideoRequest(Call call); + public void onUpgradeToVideoRequest(Call call, int videoState); /** * Called when a request to a peer to upgrade an audio-only call to a video call is @@ -230,7 +266,7 @@ public class InCallVideoCallListenerNotifier { * * @param call The call the request was successful for. */ - public void onUpgradeToVideoFail(Call call); + public void onUpgradeToVideoFail(int status, Call call); /** * Called when a call has been downgraded to audio-only. @@ -242,7 +278,7 @@ public class InCallVideoCallListenerNotifier { /** * Listener interface for any class that wants to be notified of video events, including pause - * and un-pause of peer video. + * and un-pause of peer video, video quality changes. */ public interface VideoEventListener { /** @@ -253,6 +289,29 @@ public class InCallVideoCallListenerNotifier { * otherwise. */ public void onPeerPauseStateChanged(Call call, boolean paused); + + /** + * Called when the video quality changes. + * + * @param call The call whose video quality changes. + * @param videoCallQuality - values are QUALITY_HIGH, MEDIUM, LOW and UNKNOWN. + */ + public void onVideoQualityChanged(Call call, int videoCallQuality); + + /* + * Called when call data usage value is requested or when call data usage value is updated + * because of a call state change + * + * @param dataUsage call data usage value + */ + public void onCallDataUsageChange(long dataUsage); + + /** + * Called when call session event is raised. + * + * @param event The call session event. + */ + public void onCallSessionEvent(int event); } /** diff --git a/InCallUI/src/com/android/incallui/Log.java b/InCallUI/src/com/android/incallui/Log.java index 07a0e61ca..5bc74b1a6 100644 --- a/InCallUI/src/com/android/incallui/Log.java +++ b/InCallUI/src/com/android/incallui/Log.java @@ -31,7 +31,7 @@ public class Log { // Generic tag for all In Call logging public static final String TAG = "InCall"; - public static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */ + public static final boolean FORCE_DEBUG = true; /* STOPSHIP if true */ public static final boolean DEBUG = FORCE_DEBUG || android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); public static final boolean VERBOSE = FORCE_DEBUG || diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java index 7859a17bc..c4d10c569 100644 --- a/InCallUI/src/com/android/incallui/VideoCallFragment.java +++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java @@ -16,10 +16,14 @@ package com.android.incallui; +import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.SurfaceTexture; import android.os.Bundle; +import android.telecom.Connection; +import android.telecom.VideoProfile; import android.view.Display; import android.view.LayoutInflater; import android.view.Surface; @@ -28,6 +32,9 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.view.ViewTreeObserver; +import android.widget.Toast; + +import com.google.common.base.Objects; /** * Fragment containing video calling surfaces. @@ -50,11 +57,23 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, */ public static final int SURFACE_PREVIEW = 2; + /** + * Used to indicate that the UI rotation is unknown. + */ + public static final int ORIENTATION_UNKNOWN = -1; + + /** + * Invalid resource id. + */ + public static final int INVALID_RESOURCE_ID = -1; + + // Static storage used to retain the video surfaces across Activity restart. // TextureViews are not parcelable, so it is not possible to store them in the saved state. private static boolean sVideoSurfacesInUse = false; private static VideoCallSurface sPreviewSurface = null; private static VideoCallSurface sDisplaySurface = null; + private static Point sDisplaySize = null; /** * {@link ViewStub} holding the video call surfaces. This is the parent for the @@ -84,26 +103,20 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, private boolean mIsLandscape; /** - * The width of the surface. - */ - private int mWidth = DIMENSIONS_NOT_SET; - - /** - * The height of the surface. - */ - private int mHeight = DIMENSIONS_NOT_SET; - - /** * Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and * {@link Surface}. Used to manage the lifecycle of these objects across device orientation * changes. */ - private class VideoCallSurface implements TextureView.SurfaceTextureListener, + private static class VideoCallSurface implements TextureView.SurfaceTextureListener, View.OnClickListener { private int mSurfaceId; + private VideoCallPresenter mPresenter; private TextureView mTextureView; private SurfaceTexture mSavedSurfaceTexture; private Surface mSavedSurface; + private boolean mIsDoneWithSurface; + private int mWidth = DIMENSIONS_NOT_SET; + private int mHeight = DIMENSIONS_NOT_SET; /** * Creates an instance of a {@link VideoCallSurface}. @@ -111,8 +124,9 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, * @param surfaceId The surface ID of the surface. * @param textureView The {@link TextureView} for the surface. */ - public VideoCallSurface(int surfaceId, TextureView textureView) { - this(surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET); + public VideoCallSurface(VideoCallPresenter presenter, int surfaceId, + TextureView textureView) { + this(presenter, surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET); } /** @@ -123,7 +137,11 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, * @param width The width of the surface. * @param height The height of the surface. */ - public VideoCallSurface(int surfaceId, TextureView textureView, int width, int height) { + public VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView, + int width, int height) { + Log.d(this, "VideoCallSurface: surfaceId=" + surfaceId + + " width=" + width + " height=" + height); + mPresenter = presenter; mWidth = width; mHeight = height; mSurfaceId = surfaceId; @@ -142,9 +160,23 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, mTextureView.setSurfaceTextureListener(this); mTextureView.setOnClickListener(this); - if (mSavedSurfaceTexture != null) { + final boolean areSameSurfaces = + Objects.equal(mSavedSurfaceTexture, mTextureView.getSurfaceTexture()); + Log.d(this, "recreateView: SavedSurfaceTexture=" + mSavedSurfaceTexture + + " areSameSurfaces=" + areSameSurfaces); + if (mSavedSurfaceTexture != null && !areSameSurfaces) { mTextureView.setSurfaceTexture(mSavedSurfaceTexture); + if (createSurface(mWidth, mHeight)) { + onSurfaceCreated(); + } } + mIsDoneWithSurface = false; + } + + public void resetPresenter(VideoCallPresenter presenter) { + Log.d(this, "resetPresenter: CurrentPresenter=" + mPresenter + " NewPresenter=" + + presenter); + mPresenter = presenter; } /** @@ -162,17 +194,31 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, // Where there is no saved {@link SurfaceTexture} available, use the newly created one. // If a saved {@link SurfaceTexture} is available, we are re-creating after an // orientation change. + Log.d(this, " onSurfaceTextureAvailable mSurfaceId=" + mSurfaceId + " surfaceTexture=" + + surfaceTexture + " width=" + width + + " height=" + height + " mSavedSurfaceTexture=" + mSavedSurfaceTexture); + Log.d(this, " onSurfaceTextureAvailable VideoCallPresenter=" + mPresenter); if (mSavedSurfaceTexture == null) { mSavedSurfaceTexture = surfaceTexture; surfaceCreated = createSurface(width, height); } else { // A saved SurfaceTexture was found. + Log.d(this, " onSurfaceTextureAvailable: Replacing with cached surface..."); + mTextureView.setSurfaceTexture(mSavedSurfaceTexture); surfaceCreated = true; } // Inform presenter that the surface is available. if (surfaceCreated) { - getPresenter().onSurfaceCreated(mSurfaceId); + onSurfaceCreated(); + } + } + + private void onSurfaceCreated() { + if (mPresenter != null) { + mPresenter.onSurfaceCreated(mSurfaceId); + } else { + Log.e(this, "onSurfaceTextureAvailable: Presenter is null"); } } @@ -200,17 +246,30 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, /** * Destroying the surface texture; inform the presenter so it can null the surfaces. */ - if (mSavedSurfaceTexture == null) { - getPresenter().onSurfaceDestroyed(mSurfaceId); + Log.d(this, " onSurfaceTextureDestroyed mSurfaceId=" + mSurfaceId + " surfaceTexture=" + + surfaceTexture + " SavedSurfaceTexture=" + mSavedSurfaceTexture + + " SavedSurface=" + mSavedSurface); + Log.d(this, " onSurfaceTextureDestroyed VideoCallPresenter=" + mPresenter); + + // Notify presenter if it is not null. + onSurfaceDestroyed(); + + if (mIsDoneWithSurface) { + onSurfaceReleased(); if (mSavedSurface != null) { mSavedSurface.release(); mSavedSurface = null; } } + return mIsDoneWithSurface; + } - // The saved SurfaceTexture will be null if we're shutting down, so we want to - // return "true" in that case (indicating that TextureView can release the ST). - return (mSavedSurfaceTexture == null); + private void onSurfaceDestroyed() { + if (mPresenter != null) { + mPresenter.onSurfaceDestroyed(mSurfaceId); + } else { + Log.e(this, "onSurfaceTextureDestroyed: Presenter is null."); + } } /** @@ -236,7 +295,15 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, * change in video state. Releases and clears out the saved surface and surface textures. */ public void setDoneWithSurface() { + Log.d(this, "setDoneWithSurface: SavedSurface=" + mSavedSurface + + " SavedSurfaceTexture=" + mSavedSurfaceTexture); + mIsDoneWithSurface = true; + if (mTextureView != null && mTextureView.isAvailable()) { + return; + } + if (mSavedSurface != null) { + onSurfaceReleased(); mSavedSurface.release(); mSavedSurface = null; } @@ -246,6 +313,14 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, } } + private void onSurfaceReleased() { + if (mPresenter != null) { + mPresenter.onSurfaceReleased(mSurfaceId); + } else { + Log.d(this, "setDoneWithSurface: Presenter is null."); + } + } + /** * Retrieves the saved surface instance. * @@ -262,10 +337,12 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, * @param height The height of the surface, in pixels. */ public void setSurfaceDimensions(int width, int height) { + Log.d(this, "setSurfaceDimensions, width=" + width + " height=" + height); mWidth = width; mHeight = height; if (mSavedSurfaceTexture != null) { + Log.d(this, "setSurfaceDimensions, mSavedSurfaceTexture is NOT equal to null."); createSurface(width, height); } } @@ -276,9 +353,10 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, * @param height The height of the surface to create. */ private boolean createSurface(int width, int height) { + Log.d(this, "createSurface mSavedSurfaceTexture=" + mSavedSurfaceTexture + + " mSurfaceId =" + mSurfaceId + " mWidth " + width + " mHeight=" + height); if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET && mSavedSurfaceTexture != null) { - mSavedSurfaceTexture.setDefaultBufferSize(width, height); mSavedSurface = new Surface(mSavedSurfaceTexture); return true; @@ -294,7 +372,11 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, */ @Override public void onClick(View view) { - getPresenter().onSurfaceClick(mSurfaceId); + if (mPresenter != null) { + mPresenter.onSurfaceClick(mSurfaceId); + } else { + Log.e(this, "onClick: Presenter is null."); + } } }; @@ -316,6 +398,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, mIsLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape); getPresenter().init(getActivity()); } @@ -376,9 +459,13 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, // so we need to translate it in the -X direction. final boolean isLayoutRtl = InCallPresenter.isRtl(); + ViewGroup.LayoutParams params = displayVideo.getLayoutParams(); float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard(); + Log.d(this, "centerDisplayView: IsLandscape= " + mIsLandscape + " Layout width: " + + params.width + " height: " + params.height + " spaceBesideCallCard: " + + spaceBesideCallCard); if (mIsLandscape) { - float videoViewTranslation = displayVideo.getWidth() / 2 + float videoViewTranslation = params.width / 2 - spaceBesideCallCard / 2; if (isLayoutRtl) { displayVideo.setTranslationX(-videoViewTranslation); @@ -386,7 +473,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, displayVideo.setTranslationX(videoViewTranslation); } } else { - float videoViewTranslation = displayVideo.getHeight() / 2 + float videoViewTranslation = params.height / 2 - spaceBesideCallCard / 2; displayVideo.setTranslationY(videoViewTranslation); } @@ -401,6 +488,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + Log.d(this, "onViewCreated: VideoSurfacesInUse=" + sVideoSurfacesInUse); mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub); @@ -412,13 +500,34 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, } } + @Override + public void onStop() { + super.onStop(); + Log.d(this, "onStop:"); + } + + @Override + public void onPause() { + super.onPause(); + Log.d(this, "onPause:"); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + Log.d(this, "onDestroyView:"); + } + /** * Creates the presenter for the {@link VideoCallFragment}. * @return The presenter instance. */ @Override public VideoCallPresenter createPresenter() { - return new VideoCallPresenter(); + Log.d(this, "createPresenter"); + VideoCallPresenter presenter = new VideoCallPresenter(); + onPresenterChanged(presenter); + return presenter; } /** @@ -430,33 +539,144 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, } /** - * Toggles visibility of the video UI. + * Inflate video surfaces. * * @param show {@code True} if the video surfaces should be shown. */ - @Override - public void showVideoUi(boolean show) { + private void inflateVideoUi(boolean show) { int visibility = show ? View.VISIBLE : View.GONE; getView().setVisibility(visibility); if (show) { inflateVideoCallViews(); - } else { - cleanupSurfaces(); } - if (mVideoViews != null ) { + if (mVideoViews != null) { mVideoViews.setVisibility(visibility); } } /** + * Show or hide preview and incoming video views + */ + public void showVideoViews(boolean showPreview, boolean showIncoming) { + inflateVideoUi(true); + + View incomingVideoView = mVideoViews.findViewById(R.id.incomingVideo); + View previewVideoView = mVideoViews.findViewById(R.id.previewVideo); + + if (incomingVideoView != null) { + incomingVideoView.setVisibility(showIncoming ? View.VISIBLE : View.INVISIBLE); + } + if (previewVideoView != null) { + previewVideoView.setVisibility(showPreview ? View.VISIBLE : View.INVISIBLE); + } + } + + /** + * Hide all video views. + */ + public void hideVideoUi() { + inflateVideoUi(false); + } + + /** + * Displays a message on the UI that the video call quality has changed. + * + */ + @Override + public void showVideoQualityChanged(int videoQuality) { + Log.d(this, "showVideoQualityChanged. Video quality changed to " + videoQuality); + + final Context context = getActivity(); + if (context == null) { + Log.e(this, "showVideoQualityChanged - Activity is null. Return"); + return; + } + + final Resources resources = context.getResources(); + + int videoQualityResourceId = R.string.video_quality_unknown; + switch (videoQuality) { + case VideoProfile.QUALITY_HIGH: + videoQualityResourceId = R.string.video_quality_high; + break; + case VideoProfile.QUALITY_MEDIUM: + videoQualityResourceId = R.string.video_quality_medium; + break; + case VideoProfile.QUALITY_LOW: + videoQualityResourceId = R.string.video_quality_low; + break; + default: + break; + } + + String videoQualityChangedText = resources.getString(R.string.video_quality_changed) + + resources.getString(videoQualityResourceId); + + Toast.makeText(context, videoQualityChangedText, Toast.LENGTH_SHORT).show(); + } + + /** + * Displays a message on the UI that the call substate has changed. + * + */ + @Override + public void showCallSubstateChanged(int callSubstate) { + Log.d(this, "showCallSubstateChanged - call substate changed to " + callSubstate); + + final Context context = getActivity(); + if (context == null) { + Log.e(this, "showCallSubstateChanged - Activity is null. Return"); + return; + } + + final Resources resources = context.getResources(); + + String callSubstateChangedText = ""; + + if (isEnabled(Connection.SUBSTATE_AUDIO_CONNECTED_SUSPENDED, callSubstate)) { + callSubstateChangedText += + resources.getString(R.string.call_substate_connected_suspended_audio); + } + + if (isEnabled(Connection.SUBSTATE_VIDEO_CONNECTED_SUSPENDED, callSubstate)) { + callSubstateChangedText += + resources.getString(R.string.call_substate_connected_suspended_video); + } + + if (isEnabled(Connection.SUBSTATE_AVP_RETRY, callSubstate)) { + callSubstateChangedText += + resources.getString(R.string.call_substate_avp_retry); + } + + if (isNotEnabled(Connection.SUBSTATE_ALL, callSubstate)) { + callSubstateChangedText = resources.getString(R.string.call_substate_call_resumed); + } + + if (!callSubstateChangedText.isEmpty()) { + String callSubstateLabelText = resources.getString(R.string.call_substate_label); + Toast.makeText(context, callSubstateLabelText + callSubstateChangedText, + Toast.LENGTH_SHORT).show(); + } + } + + boolean isEnabled(int mask, int callSubstate) { + return (mask & callSubstate) == mask; + } + + boolean isNotEnabled(int mask, int callSubstate) { + return (mask & callSubstate) == 0; + } + + /** * Cleans up the video telephony surfaces. Used when the presenter indicates a change to an * audio-only state. Since the surfaces are static, it is important to ensure they are cleaned * up promptly. */ @Override public void cleanupSurfaces() { + Log.d(this, "cleanupSurfaces"); if (sDisplaySurface != null) { sDisplaySurface.setDoneWithSurface(); sDisplaySurface = null; @@ -468,8 +688,19 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, sVideoSurfacesInUse = false; } + private void onPresenterChanged(VideoCallPresenter presenter) { + Log.d(this, "onPresenterChanged: Presenter=" + presenter); + if (sDisplaySurface != null) { + sDisplaySurface.resetPresenter(presenter);; + } + if (sPreviewSurface != null) { + sPreviewSurface.resetPresenter(presenter); + } + } + @Override public boolean isActivityRestart() { + Log.d(this, "isActivityRestart " + mIsActivityRestart); return mIsActivityRestart; } @@ -478,7 +709,9 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, */ @Override public boolean isDisplayVideoSurfaceCreated() { - return sDisplaySurface != null && sDisplaySurface.getSurface() != null; + boolean ret = sDisplaySurface != null && sDisplaySurface.getSurface() != null; + Log.d(this, " isDisplayVideoSurfaceCreated returns " + ret); + return ret; } /** @@ -486,7 +719,9 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, */ @Override public boolean isPreviewVideoSurfaceCreated() { - return sPreviewSurface != null && sPreviewSurface.getSurface() != null; + boolean ret = sPreviewSurface != null && sPreviewSurface.getSurface() != null; + Log.d(this, " isPreviewVideoSurfaceCreated returns " + ret); + return ret; } /** @@ -516,6 +751,7 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, */ @Override public void setPreviewSize(int width, int height) { + Log.d(this, "setPreviewSize: width=" + width + " height=" + height); if (sPreviewSurface != null) { TextureView preview = sPreviewSurface.getTextureView(); @@ -528,15 +764,123 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, params.height = height; preview.setLayoutParams(params); + int rotation = InCallPresenter.toRotationAngle(getCurrentRotation()); + int rotationAngle = 360 - rotation; + preview.setRotation(rotationAngle); + Log.d(this, "setPreviewSize: rotation=" + rotation + + " rotationAngle=" + rotationAngle); + + } + } + + @Override + public void setPreviewSurfaceSize(int width, int height) { + final boolean isPreviewSurfaceAvailable = sPreviewSurface != null; + Log.d(this, "setPreviewSurfaceSize: width=" + width + " height=" + height + + " isPreviewSurfaceAvailable=" + isPreviewSurfaceAvailable); + if (isPreviewSurfaceAvailable) { sPreviewSurface.setSurfaceDimensions(width, height); } } /** + * returns UI's current orientation. + */ + @Override + public int getCurrentRotation() { + try { + return getActivity().getWindowManager().getDefaultDisplay().getRotation(); + } catch (Exception e) { + Log.e(this, "getCurrentRotation: Retrieving current rotation failed. Ex=" + e); + } + return ORIENTATION_UNKNOWN; + } + + /** + * Changes the dimensions of the display video surface. Called when the dimensions change due to + * a peer resolution update + * + * @param width The new width. + * @param height The new height. + */ + @Override + public void setDisplayVideoSize(int width, int height) { + Log.d(this, "setDisplayVideoSize: width=" + width + " height=" + height); + if (sDisplaySurface != null) { + TextureView displayVideo = sDisplaySurface.getTextureView(); + if (displayVideo == null) { + Log.e(this, "Display Video texture view is null. Bail out"); + return; + } + sDisplaySize = new Point(width, height); + setSurfaceSizeAndTranslation(displayVideo, sDisplaySize); + } else { + Log.e(this, "Display Video Surface is null. Bail out"); + } + } + + /** + * Sets the call's data usage value + * + * @param context the current context + * @param dataUsage the data usage value + */ + @Override + public void setCallDataUsage(Context context, long dataUsage) { + Log.d(this, "setDataUsage: dataUsage = " + dataUsage); + Toast.makeText(context, "dataUsage=" + dataUsage, Toast.LENGTH_LONG).show(); + } + + private int fromCallSessionEvent(int event) { + switch (event) { + case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE: + return R.string.player_stopped; + case Connection.VideoProvider.SESSION_EVENT_RX_RESUME: + return R.string.player_started; + case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE: + return R.string.camera_not_ready; + case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY: + return R.string.camera_ready; + default: + return R.string.unknown_call_session_event; + } + } + + /** + * Sets the call's data usage value + * + * @param context the current context + * @param event the call session event + */ + @Override + public void displayCallSessionEvent(int event) { + Log.d(this, "displayCallSessionEvent: event = " + event); + Context context = getActivity(); + String msg = context.getResources().getString(fromCallSessionEvent(event)); + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); + } + + /** + * Determines the size of the device screen. + * + * @return {@link Point} specifying the width and height of the screen. + */ + @Override + public Point getScreenSize() { + // Get current screen size. + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + + return size; + } + + /** * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary, * and creates {@link VideoCallSurface} instances to track the surfaces. */ private void inflateVideoCallViews() { + Log.d(this, "inflateVideoCallViews"); if (mVideoViews == null ) { mVideoViews = mVideoViewsStub.inflate(); } @@ -544,16 +888,20 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, if (mVideoViews != null) { TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo); - Point screenSize = getScreenSize(); + Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse); + //If peer adjusted screen size is not available, set screen size to default display size + Point screenSize = sDisplaySize == null ? getScreenSize() : sDisplaySize; setSurfaceSizeAndTranslation(displaySurface, screenSize); if (!sVideoSurfacesInUse) { // Where the video surfaces are not already in use (first time creating them), // setup new VideoCallSurface instances to track them. - sDisplaySurface = new VideoCallSurface(SURFACE_DISPLAY, + Log.d(this, " inflateVideoCallViews screenSize" + screenSize); + + sDisplaySurface = new VideoCallSurface(getPresenter(), SURFACE_DISPLAY, (TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x, screenSize.y); - sPreviewSurface = new VideoCallSurface(SURFACE_PREVIEW, + sPreviewSurface = new VideoCallSurface(getPresenter(), SURFACE_PREVIEW, (TextureView) mVideoViews.findViewById(R.id.previewVideo)); sVideoSurfacesInUse = true; } else { @@ -580,28 +928,15 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, params.width = size.x; params.height = size.y; textureView.setLayoutParams(params); + Log.d(this, "setSurfaceSizeAndTranslation: Size=" + size + "IsLayoutComplete=" + + mIsLayoutComplete + "IsLandscape=" + mIsLandscape); // It is only possible to center the display view if layout of the views has completed. // It is only after layout is complete that the dimensions of the Call Card has been // established, which is a prerequisite to centering the view. // Incoming video calls will center the view - if (mIsLayoutComplete && ((mIsLandscape && textureView.getTranslationX() == 0) || ( - !mIsLandscape && textureView.getTranslationY() == 0))) { + if (mIsLayoutComplete) { centerDisplayView(textureView); } } - - /** - * Determines the size of the device screen. - * - * @return {@link Point} specifying the width and height of the screen. - */ - private Point getScreenSize() { - // Get current screen size. - Display display = getActivity().getWindowManager().getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - - return size; - } } diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java index 38d7c9556..486a38da3 100644 --- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java +++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java @@ -18,10 +18,14 @@ package com.android.incallui; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Point; import android.os.Handler; import android.telecom.AudioState; import android.telecom.CameraCapabilities; +import android.telecom.Connection; +import android.telecom.Connection.VideoProvider; import android.telecom.InCallService.VideoCall; +import android.telecom.VideoProfile; import android.view.Surface; import com.android.contacts.common.CallUtil; @@ -31,10 +35,14 @@ import com.android.incallui.InCallPresenter.InCallStateListener; import com.android.incallui.InCallPresenter.IncomingCallListener; import com.android.incallui.InCallVideoCallListenerNotifier.SurfaceChangeListener; import com.android.incallui.InCallVideoCallListenerNotifier.VideoEventListener; +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.TelephonyProperties; import com.google.common.base.Preconditions; import java.util.Objects; +import android.os.SystemProperties; + /** * Logic related to the {@link VideoCallFragment} and for managing changes to the video calling * surfaces based on other user interface events and incoming events from the @@ -61,6 +69,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi IncomingCallListener, InCallOrientationListener, InCallStateListener, InCallDetailsListener, SurfaceChangeListener, VideoEventListener, InCallVideoCallListenerNotifier.SessionModificationListener { + public static final String TAG = "VideoCallPresenter"; /** * Determines the device orientation (portrait/lanscape). @@ -122,7 +131,12 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi /** * Determines if the current UI state represents a video call. */ - private boolean mIsVideoCall; + private int mCurrentVideoState; + + /** + * Call's current state + */ + private int mCurrentCallState = Call.State.INVALID; /** * Determines the device orientation (portrait/lanscape). @@ -142,7 +156,15 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi /** * Saves the audio mode which was selected prior to going into a video call. */ - private int mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; + private static int sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; + + private static boolean mIsVideoMode = false; + + /** + * Stores the current call substate. + */ + private int mCurrentCallSubstate; + /** Handler which resets request state to NO_REQUEST after an interval. */ private Handler mSessionModificationResetHandler; @@ -168,9 +190,11 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi @Override public void onUiReady(VideoCallUi ui) { super.onUiReady(ui); + Log.d(this, "onUiReady:"); // Register for call state changes last InCallPresenter.getInstance().addListener(this); + InCallPresenter.getInstance().addDetailsListener(this); InCallPresenter.getInstance().addIncomingCallListener(this); InCallPresenter.getInstance().addOrientationListener(this); // To get updates of video call details changes @@ -180,7 +204,8 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi InCallVideoCallListenerNotifier.getInstance().addSurfaceChangeListener(this); InCallVideoCallListenerNotifier.getInstance().addVideoEventListener(this); InCallVideoCallListenerNotifier.getInstance().addSessionModificationListener(this); - mIsVideoCall = false; + mCurrentVideoState = VideoProfile.VideoState.AUDIO_ONLY; + mCurrentCallState = Call.State.INVALID; } /** @@ -191,41 +216,44 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi @Override public void onUiUnready(VideoCallUi ui) { super.onUiUnready(ui); + Log.d(this, "onUiUnready:"); InCallPresenter.getInstance().removeListener(this); + InCallPresenter.getInstance().removeDetailsListener(this); InCallPresenter.getInstance().removeIncomingCallListener(this); InCallPresenter.getInstance().removeOrientationListener(this); + InCallVideoCallListenerNotifier.getInstance().removeSurfaceChangeListener(this); InCallVideoCallListenerNotifier.getInstance().removeVideoEventListener(this); InCallVideoCallListenerNotifier.getInstance().removeSessionModificationListener(this); } /** - * @return The {@link VideoCall}. - */ - private VideoCall getVideoCall() { - return mVideoCall; - } - - /** * Handles the creation of a surface in the {@link VideoCallFragment}. * * @param surface The surface which was created. */ public void onSurfaceCreated(int surface) { - final VideoCallUi ui = getUi(); + Log.d(this, "onSurfaceCreated surface=" + surface + " mVideoCall=" + mVideoCall); + Log.d(this, "onSurfaceCreated PreviewSurfaceState=" + mPreviewSurfaceState); + Log.d(this, "onSurfaceCreated presenter=" + this); + final VideoCallUi ui = getUi(); if (ui == null || mVideoCall == null) { + Log.w(this, "onSurfaceCreated: Error bad state VideoCallUi=" + ui + " mVideoCall=" + + mVideoCall); return; } // If the preview surface has just been created and we have already received camera // capabilities, but not yet set the surface, we will set the surface now. - if (surface == VideoCallFragment.SURFACE_PREVIEW && - mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) { - - mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; - mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); + if (surface == VideoCallFragment.SURFACE_PREVIEW ) { + if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) { + mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; + mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); + } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()){ + enableCamera(mVideoCall, true); + } } else if (surface == VideoCallFragment.SURFACE_DISPLAY) { mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); } @@ -245,12 +273,15 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi /** * Handles the destruction of a surface in the {@link VideoCallFragment}. + * Note: The surface is being released, that is, it is no longer valid. * * @param surface The surface which was destroyed. */ - public void onSurfaceDestroyed(int surface) { - final VideoCallUi ui = getUi(); - if (ui == null || mVideoCall == null) { + public void onSurfaceReleased(int surface) { + Log.d(this, "onSurfaceReleased: mSurfaceId=" + surface); + if ( mVideoCall == null) { + Log.w(this, "onSurfaceReleased: VideoCall is null. mSurfaceId=" + + surface); return; } @@ -258,11 +289,43 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi mVideoCall.setDisplaySurface(null); } else if (surface == VideoCallFragment.SURFACE_PREVIEW) { mVideoCall.setPreviewSurface(null); - // Also disable camera as preview is closed - mVideoCall.setCamera(null); + enableCamera(mVideoCall, false); + } + } + + /** + * Called by {@link VideoCallFragment} when the surface is detached from UI (TextureView). + * Note: The surface will be cached by {@link VideoCallFragment}, so we don't immediately + * null out incoming video surface. + * @see VideoCallPresenter#onSurfaceReleased(int) + * + * @param surface The surface which was detached. + */ + public void onSurfaceDestroyed(int surface) { + Log.d(this, "onSurfaceDestroyed: mSurfaceId=" + surface); + if (mVideoCall == null) { + return; + } + + final boolean isChangingConfigurations = + InCallPresenter.getInstance().isChangingConfigurations(); + Log.d(this, "onSurfaceDestroyed: isChangingConfigurations=" + isChangingConfigurations); + + if (surface == VideoCallFragment.SURFACE_PREVIEW) { + if (!isChangingConfigurations) { + enableCamera(mVideoCall, false); + } else { + Log.w(this, "onSurfaceDestroyed: Activity is being destroyed due " + + "to configuration changes. Not closing the camera."); + } } } + private void toggleFullScreen() { + mIsFullScreen = !mIsFullScreen; + InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen); + } + /** * Handles clicks on the video surfaces by toggling full screen state. * Informs the {@link InCallPresenter} of the change so that it can inform the @@ -271,8 +334,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * @param surfaceId The video surface receiving the click. */ public void onSurfaceClick(int surfaceId) { - mIsFullScreen = !mIsFullScreen; - InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen); + toggleFullScreen(); } @@ -298,41 +360,152 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi @Override public void onStateChange(InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, CallList callList) { - // Bail if video calling is disabled for the device. - if (!CallUtil.isVideoEnabled(mContext)) { - return; - } + Log.d(this, "onStateChange oldState" + oldState + " newState=" + newState + + " isVideoMode=" + isVideoMode()); if (newState == InCallPresenter.InCallState.NO_CALLS) { - exitVideoMode(); + updateAudioMode(false); + + if (isVideoMode()) { + exitVideoMode(); + } + + cleanupSurfaces(); } // Determine the primary active call). Call primary = null; if (newState == InCallPresenter.InCallState.INCOMING) { - primary = callList.getIncomingCall(); + // We don't want to replace active video call (primary call) + // with a waiting call, since user may choose to ignore/decline the waiting call and + // this should have no impact on current active video call, that is, we should not + // change the camera or UI unless the waiting VT call becomes active. + primary = callList.getActiveCall(); + if (!CallUtils.isActiveVideoCall(primary)) { + primary = callList.getIncomingCall(); + } } else if (newState == InCallPresenter.InCallState.OUTGOING) { primary = callList.getOutgoingCall(); + } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) { + primary = callList.getPendingOutgoingCall(); } else if (newState == InCallPresenter.InCallState.INCALL) { primary = callList.getActiveCall(); } final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary); + Log.d(this, "onStateChange primaryChanged=" + primaryChanged); + Log.d(this, "onStateChange primary= " + primary); + Log.d(this, "onStateChange mPrimaryCall = " + mPrimaryCall); if (primaryChanged) { - mPrimaryCall = primary; - - if (primary != null) { - checkForVideoCallChange(); - mIsVideoCall = mPrimaryCall.isVideoCall(mContext); - if (mIsVideoCall) { - enterVideoMode(); - } else { - exitVideoMode(); - } - } else if (primary == null) { - // If no primary call, ensure we exit video state and clean up the video surfaces. - exitVideoMode(); + onPrimaryCallChanged(primary); + } else if(mPrimaryCall!=null) { + updateVideoCall(primary); + } + updateCallCache(primary); + } + + private void checkForVideoStateChange(Call call) { + final boolean isVideoCall = CallUtils.isVideoCall(call); + final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState(); + + Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall + + " hasVideoStateChanged=" + + hasVideoStateChanged + " isVideoMode=" + isVideoMode()); + + if (!hasVideoStateChanged) { return;} + + updateCameraSelection(call); + + if (isVideoCall) { + enterVideoMode(call.getVideoCall(), call.getVideoState()); + } else if (isVideoMode()) { + exitVideoMode(); + } + } + + private void checkForCallStateChange(Call call) { + final boolean isVideoCall = CallUtils.isVideoCall(call); + final boolean hasCallStateChanged = mCurrentCallState != call.getState(); + + Log.d(this, "checkForCallStateChange: isVideoCall= " + isVideoCall + + " hasCallStateChanged=" + + hasCallStateChanged + " isVideoMode=" + isVideoMode()); + + if (!hasCallStateChanged) { return; } + + final InCallCameraManager cameraManager = InCallPresenter.getInstance(). + getInCallCameraManager(); + + String prevCameraId = cameraManager.getActiveCameraId(); + + updateCameraSelection(call); + + String newCameraId = cameraManager.getActiveCameraId(); + + if (!Objects.equals(prevCameraId, newCameraId) && CallUtils.isActiveVideoCall(call)) { + enableCamera(call.getVideoCall(), true); + } + } + + private void checkForCallSubstateChange(Call call) { + if (mCurrentCallSubstate != call.getCallSubstate()) { + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "Error VideoCallUi is null. Return."); + return; } + mCurrentCallSubstate = call.getCallSubstate(); + // Display a call substate changed message on UI. + ui.showCallSubstateChanged(mCurrentCallSubstate); + } + } + + private void cleanupSurfaces() { + final VideoCallUi ui = getUi(); + if (ui == null) { + Log.w(this, "cleanupSurfaces"); + return; + } + ui.cleanupSurfaces(); + } + + private void onPrimaryCallChanged(Call newPrimaryCall) { + final boolean isVideoCall = CallUtils.isVideoCall(newPrimaryCall); + final boolean isVideoMode = isVideoMode(); + + Log.d(this, "onPrimaryCallChanged: isVideoCall=" + isVideoCall + " isVideoMode=" + + isVideoMode); + + if (!isVideoCall && isVideoMode) { + // Terminate video mode if new primary call is not a video call + // and we are currently in video mode. + Log.d(this, "onPrimaryCallChanged: Exiting video mode..."); + exitVideoMode(); + } else if (isVideoCall) { + Log.d(this, "onPrimaryCallChanged: Entering video mode..."); + + updateCameraSelection(newPrimaryCall); + enterVideoMode(newPrimaryCall.getVideoCall(), newPrimaryCall.getVideoState()); + } + } + + private boolean isVideoMode() { + return mIsVideoMode; + } + + private void updateCallCache(Call call) { + if (call == null) { + mCurrentVideoState = VideoProfile.VideoState.AUDIO_ONLY; + mCurrentCallSubstate = Connection.SUBSTATE_NONE; + mCurrentCallState = Call.State.INVALID; + mVideoCall = null; + mPrimaryCall = null; + } else { + mCurrentVideoState = call.getVideoState(); + mCurrentCallSubstate = call.getCallSubstate(); + mVideoCall = call.getVideoCall(); + mCurrentCallState = call.getState(); + mPrimaryCall = call; } } @@ -345,115 +518,214 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onDetailsChanged(Call call, android.telecom.Call.Details details) { + Log.d(this, " onDetailsChanged call=" + call + " details=" + details + " mPrimaryCall=" + + mPrimaryCall); // If the details change is not for the currently active call no update is required. if (!call.equals(mPrimaryCall)) { + Log.d(this," onDetailsChanged: Details not for current active call so returning. "); return; } - checkForVideoStateChange(); + updateVideoCall(call); + checkForCallSubstateChange(call); + + updateCallCache(call); } - /** - * Checks for a change to the video call and changes it if required. - */ - private void checkForVideoCallChange() { - VideoCall videoCall = mPrimaryCall.getTelecommCall().getVideoCall(); - if (!Objects.equals(videoCall, mVideoCall)) { - changeVideoCall(videoCall); - } + private void updateVideoCall(Call call) { + checkForVideoCallChange(call); + checkForVideoStateChange(call); + checkForCallStateChange(call); } /** - * Checks to see if the current video state has changed and updates the UI if required. + * Checks for a change to the video call and changes it if required. */ - private void checkForVideoStateChange() { - boolean newVideoState = mPrimaryCall.isVideoCall(mContext); - - // Check if video state changed - if (mIsVideoCall != newVideoState) { - mIsVideoCall = newVideoState; - - if (mIsVideoCall) { - enterVideoMode(); - } else { - exitVideoMode(); - } + private void checkForVideoCallChange(Call call) { + final VideoCall videoCall = call.getTelecommCall().getVideoCall(); + Log.d(this, "checkForVideoCallChange: videoCall=" + videoCall + " mVideoCall=" + + mVideoCall); + if (!Objects.equals(videoCall, mVideoCall)) { + changeVideoCall(call); } } /** - * Handles a change to the video call. Sets the surfaces on the previous call to null and sets + * Handles a change to the video call. Sets the surfaces on the previous call to null and sets * the surfaces on the new video call accordingly. * * @param videoCall The new video call. */ - private void changeVideoCall(VideoCall videoCall) { + private void changeVideoCall(Call call) { + final VideoCall videoCall = call.getTelecommCall().getVideoCall(); + Log.d(this, "changeVideoCall to videoCall=" + videoCall + " mVideoCall=" + mVideoCall); // Null out the surfaces on the previous video call. if (mVideoCall != null) { - mVideoCall.setDisplaySurface(null); - mVideoCall.setPreviewSurface(null); + // Log.d(this, "Null out the surfaces on the previous video call."); + // mVideoCall.setDisplaySurface(null); + // mVideoCall.setPreviewSurface(null); } + final boolean hasChanged = mVideoCall == null && videoCall != null; + mVideoCall = videoCall; + if (mVideoCall == null || call == null) { + Log.d(this, "Video call or primary call is null. Return"); + return; + } + + if (CallUtils.isVideoCall(call) && hasChanged) { + enterVideoMode(call.getVideoCall(), call.getVideoState()); + } + } + + private static boolean isCameraRequired(int videoState) { + return VideoProfile.VideoState.isBidirectional(videoState) || + VideoProfile.VideoState.isTransmissionEnabled(videoState); + } + + private boolean isCameraRequired() { + return mPrimaryCall != null ? isCameraRequired(mPrimaryCall.getVideoState()) : false; } /** * Enters video mode by showing the video surfaces and making other adjustments (eg. audio). * TODO(vt): Need to adjust size and orientation of preview surface here. */ - private void enterVideoMode() { + private void enterVideoMode(VideoCall videoCall, int newVideoState) { + Log.d(this, "enterVideoMode videoCall= " + videoCall + " videoState: " + newVideoState); VideoCallUi ui = getUi(); if (ui == null) { + Log.e(this, "Error VideoCallUi is null so returning"); return; } - ui.showVideoUi(true); + showVideoUi(newVideoState); InCallPresenter.getInstance().setInCallAllowsOrientationChange(true); // Communicate the current camera to telephony and make a request for the camera // capabilities. - if (mVideoCall != null) { - // Do not reset the surfaces if we just restarted the activity due to an orientation - // change. - if (ui.isActivityRestart()) { - return; + if (videoCall != null) { + if (ui.isDisplayVideoSurfaceCreated()) { + Log.d(this, "Calling setDisplaySurface with " + ui.getDisplayVideoSurface()); + videoCall.setDisplaySurface(ui.getDisplayVideoSurface()); } - mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET; + final int rotation = ui.getCurrentRotation(); + if (rotation != VideoCallFragment.ORIENTATION_UNKNOWN) { + videoCall.setDeviceOrientation(InCallPresenter.toRotationAngle(rotation)); + } + + enableCamera(videoCall, isCameraRequired(newVideoState)); + } + mCurrentVideoState = newVideoState; + updateAudioMode(true); + + mIsVideoMode = true; + } + + //TODO: Move this into Telecom. InCallUI should not be this close to audio functionality. + private void updateAudioMode(boolean enableSpeaker) { + if (!isSpeakerEnabledForVideoCalls()) { + Log.d(this, "Speaker is disabled. Can't update audio mode"); + return; + } + + final TelecomAdapter telecomAdapter = TelecomAdapter.getInstance(); + final boolean isPrevAudioModeValid = + sPrevVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID; + + Log.d(this, "Is previous audio mode valid = " + isPrevAudioModeValid + " enableSpeaker is " + + enableSpeaker); + + // Set audio mode to previous mode if enableSpeaker is false. + if (isPrevAudioModeValid && !enableSpeaker) { + telecomAdapter.setAudioRoute(sPrevVideoAudioMode); + sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; + return; + } + + int currentAudioMode = AudioModeProvider.getInstance().getAudioMode(); + + // Set audio mode to speaker if enableSpeaker is true and bluetooth or headset are not + // connected and it's a video call. + if (!isAudioRouteEnabled(currentAudioMode, + AudioState.ROUTE_BLUETOOTH | AudioState.ROUTE_WIRED_HEADSET) && + !isPrevAudioModeValid && enableSpeaker && CallUtils.isVideoCall(mPrimaryCall)) { + sPrevVideoAudioMode = currentAudioMode; + + Log.d(this, "Routing audio to speaker"); + telecomAdapter.setAudioRoute(AudioState.ROUTE_SPEAKER); + } + } + + private static boolean isSpeakerEnabledForVideoCalls() { + return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT, + PhoneConstants.AUDIO_OUTPUT_DEFAULT) == + PhoneConstants.AUDIO_OUTPUT_ENABLE_SPEAKER); + } + + private void enableCamera(VideoCall videoCall, boolean isCameraRequired) { + Log.d(this, "enableCamera: VideoCall=" + videoCall + " enabling=" + isCameraRequired); + if (videoCall == null) { + Log.w(this, "enableCamera: VideoCall is null."); + return; + } + + if (isCameraRequired) { InCallCameraManager cameraManager = InCallPresenter.getInstance(). getInCallCameraManager(); - mVideoCall.setCamera(cameraManager.getActiveCameraId()); - mVideoCall.requestCameraCapabilities(); + videoCall.setCamera(cameraManager.getActiveCameraId()); + mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET; - if (ui.isDisplayVideoSurfaceCreated()) { - mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); - } + videoCall.requestCameraCapabilities(); + } else { + mPreviewSurfaceState = PreviewSurfaceState.NONE; + videoCall.setCamera(null); } - - mPreVideoAudioMode = AudioModeProvider.getInstance().getAudioMode(); - TelecomAdapter.getInstance().setAudioRoute(AudioState.ROUTE_SPEAKER); } /** - * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). + * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). */ private void exitVideoMode() { + Log.d(this, "exitVideoMode"); + + InCallPresenter.getInstance().setInCallAllowsOrientationChange(false); + + showVideoUi(VideoProfile.VideoState.AUDIO_ONLY); + enableCamera(mVideoCall, false); + + Log.d(this, "exitVideoMode mIsFullScreen: " + mIsFullScreen); + if (mIsFullScreen) { + toggleFullScreen(); + } + + mIsVideoMode = false; + } + + /** + * Show video Ui depends on video state. + */ + private void showVideoUi(int videoState) { VideoCallUi ui = getUi(); if (ui == null) { + Log.e(this, "showVideoUi, VideoCallUi is null returning"); return; } - InCallPresenter.getInstance().setInCallAllowsOrientationChange(false); - ui.showVideoUi(false); - if (mVideoCall != null) { - // Also disable camera otherwise it will be already in use for next upgrade - mVideoCall.setCamera(null); + if (VideoProfile.VideoState.isBidirectional(videoState)) { + ui.showVideoViews(true, true); + } else if (VideoProfile.VideoState.isTransmissionEnabled(videoState)) { + ui.showVideoViews(true, false); + } else if (VideoProfile.VideoState.isReceptionEnabled(videoState)) { + ui.showVideoViews(false, true); + } else { + ui.hideVideoUi(); } - if (mPreVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID) { - TelecomAdapter.getInstance().setAudioRoute(mPreVideoAudioMode); - mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; - } + InCallPresenter.getInstance().enableScreenTimeout( + VideoProfile.VideoState.isAudioOnly(videoState)); } /** @@ -481,11 +753,43 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onUpdatePeerDimensions(Call call, int width, int height) { + Log.d(this, "onUpdatePeerDimensions: width= " + width + " height= " + height); + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "VideoCallUi is null. Bail out"); + return; + } if (!call.equals(mPrimaryCall)) { + Log.e(this, "Current call is not equal to primary call. Bail out"); return; } - // TODO(vt): Change display surface aspect ratio. + // Change size of display surface to match the peer aspect ratio + if (width > 0 && height > 0) { + setDisplayVideoSize(width, height); + } + } + + /** + * Handles any video quality changes in the call. + * + * @param call The call which experienced a video quality change. + * @param videoQuality The new video call quality. + */ + @Override + public void onVideoQualityChanged(Call call, int videoQuality) { + if (!call.equals(mPrimaryCall)) { + return; + } + + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "Error VideoCallUi is null. Return."); + return; + } + + // Display a video quality changed message on UI. + ui.showVideoQualityChanged(videoQuality); } /** @@ -498,16 +802,21 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onCameraDimensionsChange(Call call, int width, int height) { + Log.d(this, "onCameraDimensionsChange call=" + call + " width=" + width + " height=" + + height); VideoCallUi ui = getUi(); if (ui == null) { + Log.e(this, "onCameraDimensionsChange ui is null"); return; } if (!call.equals(mPrimaryCall)) { + Log.e(this, "Call is not primary call"); return; } mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED; + ui.setPreviewSurfaceSize(width, height); // Configure the preview surface to the correct aspect ratio. float aspectRatio = 1.0f; @@ -525,45 +834,105 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi } /** - * Handles hanges to the device orientation. + * Called when call session event is raised. + * + * @param event The call session event. + */ + @Override + public void onCallSessionEvent(int event) { + Log.d(this, "onCallSessionEvent event =" + event); + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "onCallSessionEvent: VideoCallUi is null"); + return; + } + ui.displayCallSessionEvent(event); + } + + /** + * Handles a change to the call data usage + * + * @param dataUsage call data usage value + */ + @Override + public void onCallDataUsageChange(long dataUsage) { + Log.d(this, "onCallDataUsageChange dataUsage=" + dataUsage); + VideoCallUi ui = getUi(); + if (ui == null) { + Log.e(this, "onCallDataUsageChange: VideoCallUi is null"); + return; + } + ui.setCallDataUsage(mContext, dataUsage); + } + + /** + * Handles changes to the device orientation. * See: {@link Configuration.ORIENTATION_LANDSCAPE}, {@link Configuration.ORIENTATION_PORTRAIT} * @param orientation The device orientation. */ @Override public void onDeviceOrientationChanged(int orientation) { + Log.d(this, "onDeviceOrientationChanged: orientation=" + orientation); mDeviceOrientation = orientation; } @Override - public void onUpgradeToVideoRequest(Call call) { - mPrimaryCall.setSessionModificationState( - Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST); + public void onUpgradeToVideoRequest(Call call, int videoState) { + Log.d(this, "onUpgradeToVideoRequest call = " + call + " new video state = " + videoState); + if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) { + Log.w(this, "UpgradeToVideoRequest received for non-primary call"); + } + + if (call == null) { + return; + } + + call.setSessionModificationTo(videoState); } @Override public void onUpgradeToVideoSuccess(Call call) { + Log.d(this, "onUpgradeToVideoSuccess call=" + call); if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) { + Log.w(this, "UpgradeToVideoSuccess received for non-primary call"); + } + + if (call == null) { return; } - mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); + call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); } @Override - public void onUpgradeToVideoFail(Call call) { + public void onUpgradeToVideoFail(int status, Call call) { + Log.d(this, "onUpgradeToVideoFail call=" + call); if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) { - return; + Log.w(this, "UpgradeToVideoFail received for non-primary call"); } - call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED); + if (call == null) { + return; + } - // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval. - mSessionModificationResetHandler.postDelayed(new Runnable() { - @Override - public void run() { - mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); - } - }, SESSION_MODIFICATION_RESET_DELAY_MS); + if (status == VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT) { + call.setSessionModificationState( + Call.SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT); + } else { + call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED); + + final Call modifyCall = call; + // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval. + mSessionModificationResetHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (modifyCall != null) { + modifyCall + .setSessionModificationState(Call.SessionModificationState.NO_REQUEST); + } + } + }, SESSION_MODIFICATION_RESET_DELAY_MS); + } } @Override @@ -599,16 +968,140 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi } /** + * Sets the display video surface size based on peer width and height + * + * @param width peer width + * @param height peer height + */ + + private void setDisplayVideoSize(int width, int height) { + Log.d(this, "setDisplayVideoSize:Received peer width=" + width + " peer height=" + height); + VideoCallUi ui = getUi(); + if (ui == null) { + return; + } + + // Get current display size + Point size = ui.getScreenSize(); + Log.d("VideoCallPresenter", "setDisplayVideoSize: windowmgr width=" + size.x + + " windowmgr height=" + size.y); + if (size.y * width > size.x * height) { + // current display height is too much. Correct it + size.y = (int) (size.x * height / width); + } else if (size.y * width < size.x * height) { + // current display width is too much. Correct it + size.x = (int) (size.y * width / height); + } + ui.setDisplayVideoSize(size.x, size.y); + } + + private static boolean isAudioRouteEnabled(int audioRoute, int audioRouteMask) { + return ((audioRoute & audioRouteMask) != 0); + } + + private static void updateCameraSelection(Call call) { + Log.d(TAG, "updateCameraSelection: call=" + call); + Log.d(TAG, "updateCameraSelection: call=" + toSimpleString(call)); + + final Call activeCall = CallList.getInstance().getActiveCall(); + int cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; + + // this function should never be called with null call object, however if it happens we + // should handle it gracefully. + if (call == null) { + cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; + com.android.incallui.Log.e(TAG, "updateCameraSelection: Call object is null." + + " Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)"); + } + + // Clear camera direction if this is not a video call. + else if (CallUtils.isAudioCall(call)) { + cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; + call.getVideoSettings().setCameraDir(cameraDir); + } + + // If this is a waiting video call, default to active call's camera, + // since we don't want to change the current camera for waiting call + // without user's permission. + else if (CallUtils.isVideoCall(activeCall) && CallUtils.isIncomingVideoCall(call)) { + cameraDir = activeCall.getVideoSettings().getCameraDir(); + } + + // Infer the camera direction from the video state and store it, + // if this is an outgoing video call. + else if (CallUtils.isOutgoingVideoCall(call) && !isCameraDirectionSet(call) ) { + cameraDir = toCameraDirection(call.getVideoState()); + call.getVideoSettings().setCameraDir(cameraDir); + } + + // Use the stored camera dir if this is an outgoing video call for which camera direction + // is set. + else if (CallUtils.isOutgoingVideoCall(call)) { + cameraDir = call.getVideoSettings().getCameraDir(); + } + + // Infer the camera direction from the video state and store it, + // if this is an active video call and camera direction is not set. + else if (CallUtils.isActiveVideoCall(call) && !isCameraDirectionSet(call)) { + cameraDir = toCameraDirection(call.getVideoState()); + call.getVideoSettings().setCameraDir(cameraDir); + } + + // Use the stored camera dir if this is an active video call for which camera direction + // is set. + else if (CallUtils.isActiveVideoCall(call)) { + cameraDir = call.getVideoSettings().getCameraDir(); + } + + // For all other cases infer the camera direction but don't store it in the call object. + else { + cameraDir = toCameraDirection(call.getVideoState()); + } + + com.android.incallui.Log.d(TAG, "updateCameraSelection: Setting camera direction to " + + cameraDir + " Call=" + call); + final InCallCameraManager cameraManager = InCallPresenter.getInstance(). + getInCallCameraManager(); + cameraManager.setUseFrontFacingCamera(cameraDir == + Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING); + } + + private static int toCameraDirection(int videoState) { + return VideoProfile.VideoState.isTransmissionEnabled(videoState) && + !VideoProfile.VideoState.isBidirectional(videoState) + ? Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING + : Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING; + } + + private static boolean isCameraDirectionSet(Call call) { + return CallUtils.isVideoCall(call) && call.getVideoSettings().getCameraDir() + != Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; + } + + private static String toSimpleString(Call call) { + return call == null ? null : call.toSimpleString(); + } + + /** * Defines the VideoCallUI interactions. */ public interface VideoCallUi extends Ui { - void showVideoUi(boolean show); + void showVideoViews(boolean showPreview, boolean showIncoming); + void hideVideoUi(); + void showVideoQualityChanged(int videoQuality); boolean isDisplayVideoSurfaceCreated(); boolean isPreviewVideoSurfaceCreated(); Surface getDisplayVideoSurface(); Surface getPreviewVideoSurface(); + int getCurrentRotation(); void setPreviewSize(int width, int height); + void setPreviewSurfaceSize(int width, int height); + void setDisplayVideoSize(int width, int height); + void setCallDataUsage(Context context, long dataUsage); + void displayCallSessionEvent(int event); + Point getScreenSize(); void cleanupSurfaces(); boolean isActivityRestart(); + void showCallSubstateChanged(int callSubstate); } } diff --git a/InCallUI/src/com/android/incallui/VideoPauseController.java b/InCallUI/src/com/android/incallui/VideoPauseController.java new file mode 100644 index 000000000..727780e8f --- /dev/null +++ b/InCallUI/src/com/android/incallui/VideoPauseController.java @@ -0,0 +1,389 @@ +/* Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import android.os.SystemProperties; +import android.telecom.VideoProfile; +import com.android.incallui.Call.State; +import com.android.incallui.InCallPresenter.InCallState; +import com.android.incallui.InCallPresenter.InCallStateListener; +import com.android.incallui.InCallPresenter.IncomingCallListener; +import com.android.incallui.InCallVideoCallListenerNotifier.SessionModificationListener; +import com.android.internal.util.Preconditions; + +/** + * The class is responsible for generating video pause/resume request. + */ +class VideoPauseController implements InCallStateListener, IncomingCallListener, + SessionModificationListener { + private static final String TAG = "VideoCallPauseController:"; + + private class CallContext { + public CallContext(Call call) { + Preconditions.checkNotNull(call); + update(call); + } + + public void update(Call call) { + mCall = Preconditions.checkNotNull(call); + mState = call.getState(); + mId = call.getId(); + mVideoState = call.getVideoState(); + } + + public int getState() { + return mState; + } + + public String getId() { + return mId; + } + + public int getVideoState() { + return mVideoState; + } + + public String toString() { + return String.format("CallContext {CallId=%s, State=%s, VideoState=", + mId, mState, mVideoState); + } + + public Call getCall() { + return mCall; + } + + private int mState = State.INVALID; + private String mId; + private int mVideoState; + private Call mCall; + } + + private InCallPresenter mInCallPresenter; + private static VideoPauseController sVideoPauseController; + + private CallContext mPrimaryCallContext = null; // Context of primary call, if any. + private boolean mIsInBackground = false; // True if UI is not visible, false otherwise. + private int mVideoPauseMode = VIDEO_PAUSE_MODE_DISABLED; + + /** + * Stores current video pause mode. + * 0 - Video Pause is disabled. + * 1 - Video Pause is enabled. + */ + private static final String PROPERTY_VIDEO_PAUSE_MODE = "persist.radio.videopause.mode"; + private static int VIDEO_PAUSE_MODE_DISABLED = 0; + private static int VIDEO_PAUSE_MODE_ENABLED = 1; + + private VideoPauseController() { + mVideoPauseMode = SystemProperties.getInt(PROPERTY_VIDEO_PAUSE_MODE, + VIDEO_PAUSE_MODE_DISABLED); + if (mVideoPauseMode != VIDEO_PAUSE_MODE_ENABLED) { // Validate the mode before using. + mVideoPauseMode = VIDEO_PAUSE_MODE_DISABLED; + } + } + + /*package*/ + static synchronized VideoPauseController getInstance() { + if (sVideoPauseController == null) { + sVideoPauseController = new VideoPauseController(); + } + return sVideoPauseController; + } + + public void setUp(InCallPresenter inCallPresenter) { + if (!isVideoPausedEnabled()) { + return; + } + + log("setUp..."); + mInCallPresenter = Preconditions.checkNotNull(inCallPresenter); + mInCallPresenter.addListener(this); + mInCallPresenter.addIncomingCallListener(this); + InCallVideoCallListenerNotifier.getInstance().addSessionModificationListener(this); + } + + public void tearDown() { + if (!isVideoPausedEnabled()) { + return; + } + + log("tearDown..."); + InCallVideoCallListenerNotifier.getInstance().removeSessionModificationListener(this); + mInCallPresenter.removeListener(this); + mInCallPresenter.removeIncomingCallListener(this); + clear(); + } + + private void clear() { + mInCallPresenter = null; + mPrimaryCallContext = null; + mIsInBackground = false; + } + + /** + * The function gets called when call state changes. + * @param state Phone state. + * @param callList List of current call. + */ + @Override + public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { + log("onStateChange, OldState=" + oldState + " NewState=" + newState); + + Call call = null; + if (newState == InCallState.INCOMING) { + call = callList.getIncomingCall(); + } else if (newState == InCallState.WAITING_FOR_ACCOUNT) { + call = callList.getWaitingForAccountCall(); + } else if (newState == InCallState.PENDING_OUTGOING) { + call = callList.getPendingOutgoingCall(); + } else if (newState == InCallState.OUTGOING) { + call = callList.getOutgoingCall(); + } else { + call = callList.getActiveCall(); + } + + boolean hasPrimaryCallChanged = !areSame(call, mPrimaryCallContext); + boolean canVideoPause = CallUtils.canVideoPause(call); + log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged); + log("onStateChange, canVideoPause=" + canVideoPause); + log("onStateChange, IsInBackground=" + mIsInBackground); + + if (hasPrimaryCallChanged) { + onPrimaryCallChanged(call); + return; + } + + if (isOutgoing(mPrimaryCallContext) && canVideoPause && mIsInBackground) { + // Bring UI to foreground if outgoing request becomes active while UI is in + // background. + bringToForeground(); + } else if (!isVideoCall(mPrimaryCallContext) && canVideoPause && mIsInBackground) { + // Bring UI to foreground if VoLTE call becomes active while UI is in + // background. + bringToForeground(); + } + + updatePrimaryCallContext(call); + } + + private void onPrimaryCallChanged(Call call) { + log("onPrimaryCallChanged: New call = " + call); + log("onPrimaryCallChanged: Old call = " + mPrimaryCallContext); + log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground); + + Preconditions.checkState(!areSame(call, mPrimaryCallContext)); + final boolean canVideoPause = CallUtils.canVideoPause(call); + + if (isWaitingCall(mPrimaryCallContext) && canVideoPause && !mIsInBackground) { + // Send resume request for the active call, if user rejects incoming + // call and UI is in foreground. + sendRequest(call, true); + } else if (isWaitingCall(call) && canVideoPause(mPrimaryCallContext)) { + // Send pause request if there is an active video call, and we just received a new + // incoming call. + sendRequest(mPrimaryCallContext.getCall(), false); + } else if (isOutgoing(mPrimaryCallContext) && canVideoPause && !mIsInBackground) { + // Send resume request for the active call, if user ends outgoing call + // and UI is in foreground. + sendRequest(call, true); + } + + updatePrimaryCallContext(call); + } + + /** + * The function gets called when InCallUI receives a new incoming call. + */ + @Override + public void onIncomingCall(InCallState oldState, InCallState newState, Call call) { + log("onIncomingCall, OldState=" + oldState + " NewState=" + newState + " Call=" + call); + + if (areSame(call, mPrimaryCallContext)) { + return; + } + + onPrimaryCallChanged(call); + } + + private void updatePrimaryCallContext(Call call) { + if (call == null) { + mPrimaryCallContext = null; + } else if (mPrimaryCallContext != null) { + mPrimaryCallContext.update(call); + } else { + mPrimaryCallContext = new CallContext(call); + } + } + + /** + * Called when UI goes in/out of the foreground. + * @param showing true if UI is in the foreground, false otherwise. + */ + public void onUiShowing(boolean showing) { + if (!isVideoPausedEnabled() || mInCallPresenter == null) { + return; + } + + final boolean notify = mInCallPresenter.getInCallState() == InCallState.INCALL; + if (showing) { + onResume(notify); + } else { + onPause(notify); + } + } + + @Override + public void onUpgradeToVideoRequest(Call call, int videoState) { + } + + @Override + public void onUpgradeToVideoSuccess(Call call) { + } + + @Override + public void onUpgradeToVideoFail(int status, Call call) { + // TODO (ims-vt) Automatically bring in call ui to foreground. + } + + @Override + public void onDowngradeToAudio(Call call) { + } + + /** + * Called when UI becomes visible. This will send resume request for current video call, if any. + */ + private void onResume(boolean notify) { + log("onResume: notify=" + notify); + + mIsInBackground = false; + if (canVideoPause(mPrimaryCallContext) && notify) { + sendRequest(mPrimaryCallContext.getCall(), true); + } else { + log("onResume. Ignoring..."); + } + } + + /** + * Called when UI becomes invisible. This will send pause request for current video call, if any. + */ + private void onPause(boolean notify) { + log("onPause: notify=" + notify); + + mIsInBackground = true; + if (canVideoPause(mPrimaryCallContext) && notify) { + sendRequest(mPrimaryCallContext.getCall(), false); + } else { + log("onPause, Ignoring..."); + } + } + + private void bringToForeground() { + if (mInCallPresenter != null) { + log("Bringing UI to foreground"); + mInCallPresenter.bringToForeground(false); + } else { + loge("InCallPresenter is null. Cannot bring UI to foreground"); + } + } + + /** + * Sends Pause/Resume request. + * @param call Call to be paused/resumed. + * @param resume If true resume request will be sent, otherwise pause request. + */ + private void sendRequest(Call call, boolean resume) { + if (resume) { + log("sending resume request, call=" + call); + call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoUnPauseProfile(call)); + } else { + log("sending pause request, call=" + call); + call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoPauseProfile(call)); + } + } + + private boolean isVideoPausedEnabled() { + return mVideoPauseMode != VIDEO_PAUSE_MODE_DISABLED; + } + + private static boolean areSame(Call call, CallContext callContext) { + if (call == null && callContext == null) { + return true; + } else if (call == null || callContext == null) { + return false; + } + return call.getId().equals(callContext.getId()); + } + + private static boolean areSame(CallContext callContext, Call call) { + return areSame(call, callContext); + } + + private static boolean canVideoPause(CallContext callContext) { + return isVideoCall(callContext) && callContext.getState() == Call.State.ACTIVE; + } + + private static boolean isVideoCall(CallContext callContext) { + return callContext != null && VideoProfile.VideoState.isVideo(callContext.getVideoState()); + } + + /** + * Returns true if call is in incoming/waiting state, false otherwise. + */ + private static boolean isWaitingCall(CallContext call) { + return call != null && (call.getState() == Call.State.CALL_WAITING + || call.getState() == Call.State.INCOMING); + } + + private static boolean isWaitingCall(Call call) { + return call != null && (call.getState() == Call.State.CALL_WAITING + || call.getState() == Call.State.INCOMING); + } + + /** + * Returns true if the call is outgoing, false otherwise + */ + private static boolean isOutgoing(CallContext call) { + return call != null && Call.State.isDialing(call.getState()); + } + + /** + * Returns true if the call is on hold, false otherwise + */ + private static boolean isHolding(CallContext call) { + return call != null && call.getState() == Call.State.ONHOLD; + } + + private void log(String msg) { + Log.d(this, TAG + msg); + } + + private void loge(String msg) { + Log.e(this, TAG + msg); + } +} |