diff options
Diffstat (limited to 'InCallUI/src')
16 files changed, 710 insertions, 418 deletions
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java index 07cbfd573..d68a316c9 100644 --- a/InCallUI/src/com/android/incallui/AnswerFragment.java +++ b/InCallUI/src/com/android/incallui/AnswerFragment.java @@ -21,6 +21,7 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; +import android.telecom.VideoProfile; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; @@ -49,10 +50,7 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente public static final int TARGET_SET_FOR_AUDIO_WITH_SMS = 1; 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; + public static final int TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST = 4; /** * The popup showing the list of canned responses. @@ -124,12 +122,21 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente * Sets targets on the glowpad according to target set identified by the parameter. * @param targetSet Integer identifying the set of targets to use. */ - @Override public void showTargets(int targetSet) { + showTargets(targetSet, VideoProfile.VideoState.BIDIRECTIONAL); + } + + /** + * Sets targets on the glowpad according to target set identified by the parameter. + * @param targetSet Integer identifying the set of targets to use. + */ + @Override + public void showTargets(int targetSet, int videoState) { final int targetResourceId; final int targetDescriptionsResourceId; final int directionDescriptionsResourceId; final int handleDrawableResourceId; + mGlowpad.setVideoState(videoState); switch (targetSet) { case TARGET_SET_FOR_AUDIO_WITH_SMS: @@ -156,39 +163,13 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente R.array.incoming_call_widget_video_with_sms_direction_descriptions; handleDrawableResourceId = R.drawable.ic_incall_video_handle; break; - case TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST: - targetResourceId = R.array.incoming_call_widget_video_upgrade_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_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST: + case TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST: targetResourceId = - R.array.incoming_call_widget_bidirectional_video_accept_reject_request_targets; + R.array.incoming_call_widget_video_request_targets; targetDescriptionsResourceId = - R.array.incoming_call_widget_video_upgrade_request_target_descriptions; + R.array.incoming_call_widget_video_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; + .incoming_call_widget_video_request_target_direction_descriptions; handleDrawableResourceId = R.drawable.ic_incall_video_handle; break; case TARGET_SET_FOR_AUDIO_WITHOUT_SMS: @@ -376,6 +357,11 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente } @Override + public void onDeclineUpgradeRequest(Context context) { + InCallPresenter.getInstance().declineUpgradeRequest(context); + } + + @Override public void onText() { getPresenter().onText(); } diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java index 33cdba4c0..9decb7b21 100644 --- a/InCallUI/src/com/android/incallui/AnswerPresenter.java +++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java @@ -98,6 +98,17 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> public void onDisconnect(Call call) { // no-op } + + public void onSessionModificationStateChange(int sessionModificationState) { + boolean isUpgradePending = sessionModificationState == + Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; + + if (!isUpgradePending) { + // Stop listening for updates. + CallList.getInstance().removeCallUpdateListener(mCallId, this); + showAnswerUi(false); + } + } private boolean isVideoUpgradePending(Call call) { return call.getSessionModificationState() @@ -167,27 +178,15 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> return; } - showAnswerUi(true); - getUi().showTargets(getUiTarget(currentVideoState, modifyToVideoState)); - - } + AnswerUi ui = getUi(); - 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; + if (ui == null) { + Log.e(this, "Ui is null. Can't process upgrade request"); + return; } - 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); + showAnswerUi(true); + ui.showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST, + modifyToVideoState); } private boolean isEnabled(int videoState, int mask) { @@ -221,15 +220,16 @@ 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; } if (mCall.getSessionModificationState() == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { + Log.d(this, "onAnswer (upgradeCall) mCallId=" + mCallId + " videoState=" + videoState); InCallPresenter.getInstance().acceptUpgradeRequest(videoState, context); } else { + Log.d(this, "onAnswer (answerCall) mCallId=" + mCallId + " videoState=" + videoState); TelecomAdapter.getInstance().answerCall(mCall.getId(), videoState); } } @@ -267,17 +267,14 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> } private void configureAnswerTargetsForSms(Call call, List<String> textMsgs) { - if (getUi() == null) { - return; - } - - final Context context = getUi().getContext(); - mHasTextMessages = textMsgs != null; boolean withSms = call.can(android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT) && mHasTextMessages; - if (call.isVideoCall(context)) { + + // Only present the user with the option to answer as a video call if the incoming call is + // a bi-directional video call. + if (VideoProfile.VideoState.isBidirectional((call.getVideoState()))) { if (withSms) { getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITH_SMS); getUi().configureMessageDialog(textMsgs); @@ -297,6 +294,7 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> interface AnswerUi extends Ui { public void onShowAnswerUi(boolean shown); public void showTargets(int targetSet); + public void showTargets(int targetSet, int videoState); public void showMessageDialog(); public void configureMessageDialog(List<String> textResponses); public Context getContext(); diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java index 5cf0f9b1a..9f58fbed5 100644 --- a/InCallUI/src/com/android/incallui/Call.java +++ b/InCallUI/src/com/android/incallui/Call.java @@ -124,6 +124,7 @@ public class Call { 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 final int REQUEST_REJECTED = 5; } public static class VideoSettings { @@ -509,11 +510,8 @@ public class Call { mSessionModificationState = state; Log.d(this, "setSessionModificationState " + state + " mSessionModificationState=" + mSessionModificationState); - if (state != Call.SessionModificationState.WAITING_FOR_RESPONSE) { - setModifyToVideoState(VideoProfile.VideoState.AUDIO_ONLY); - } if (hasChanged) { - update(); + CallList.getInstance().onSessionModificationStateChange(this, state); } } @@ -549,7 +547,7 @@ public class Call { } return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " + - "videoState:%d, mSessionModificationState:%d, VideoSettings:%s]", + "videoState:%s, mSessionModificationState:%d, VideoSettings:%s]", mId, State.toString(getState()), android.telecom.Call.Details @@ -557,7 +555,8 @@ public class Call { mChildCallIds, getParentId(), this.mTelecommCall.getConferenceableCalls(), - mTelecommCall.getDetails().getVideoState(), + VideoProfile.VideoState.videoStateToString( + mTelecommCall.getDetails().getVideoState()), mSessionModificationState, getVideoSettings()); } diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java index b3cadcfec..9def35694 100644 --- a/InCallUI/src/com/android/incallui/CallButtonFragment.java +++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java @@ -18,9 +18,7 @@ package com.android.incallui; import static com.android.incallui.CallButtonFragment.Buttons.*; -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; @@ -30,8 +28,6 @@ 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.util.SparseIntArray; import android.view.ContextThemeWrapper; import android.view.HapticFeedbackConstants; @@ -43,12 +39,10 @@ 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.MaterialPalette; -import java.util.ArrayList; /** * Fragment for call control buttons @@ -58,7 +52,7 @@ public class CallButtonFragment implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener, View.OnClickListener { private static final int INVALID_INDEX = -1; - private static final int BUTTON_MAX_VISIBLE = 5; + private int mButtonMaxVisible; // The button is currently visible in the UI private static final int BUTTON_VISIBLE = 1; // The button is hidden in the UI @@ -68,24 +62,22 @@ public class CallButtonFragment public interface Buttons { public static final int BUTTON_AUDIO = 0; - public static final int BUTTON_DOWNGRADE_TO_VOICE = 1; - public static final int BUTTON_MUTE = 2; - public static final int BUTTON_DIALPAD = 3; - public static final int BUTTON_HOLD = 4; - public static final int BUTTON_SWAP = 5; - public static final int BUTTON_UPGRADE_TO_VIDEO = 6; - public static final int BUTTON_SWITCH_CAMERA = 7; - public static final int BUTTON_ADD_CALL = 8; - public static final int BUTTON_MERGE = 9; - public static final int BUTTON_PAUSE_VIDEO = 10; - public static final int BUTTON_MANAGE_VIDEO_CONFERENCE = 11; - public static final int BUTTON_COUNT = 12; + public static final int BUTTON_MUTE = 1; + public static final int BUTTON_DIALPAD = 2; + public static final int BUTTON_HOLD = 3; + public static final int BUTTON_SWAP = 4; + public static final int BUTTON_UPGRADE_TO_VIDEO = 5; + public static final int BUTTON_SWITCH_CAMERA = 6; + public static final int BUTTON_ADD_CALL = 7; + public static final int BUTTON_MERGE = 8; + public static final int BUTTON_PAUSE_VIDEO = 9; + public static final int BUTTON_MANAGE_VIDEO_CONFERENCE = 10; + public static final int BUTTON_COUNT = 11; } private SparseIntArray mButtonVisibilityMap = new SparseIntArray(BUTTON_COUNT); private CompoundButton mAudioButton; - private ImageButton mChangeToVoiceButton; private CompoundButton mMuteButton; private CompoundButton mShowDialpadButton; private CompoundButton mHoldButton; @@ -129,6 +121,8 @@ public class CallButtonFragment for (int i = 0; i < BUTTON_COUNT; i++) { mButtonVisibilityMap.put(i, BUTTON_HIDDEN); } + + mButtonMaxVisible = getResources().getInteger(R.integer.call_card_max_buttons); } @Override @@ -138,8 +132,6 @@ public class CallButtonFragment mAudioButton = (CompoundButton) parent.findViewById(R.id.audioButton); mAudioButton.setOnClickListener(this); - mChangeToVoiceButton = (ImageButton) parent.findViewById(R.id.changeToVoiceButton); - mChangeToVoiceButton. setOnClickListener(this); mMuteButton = (CompoundButton) parent.findViewById(R.id.muteButton); mMuteButton.setOnClickListener(this); mShowDialpadButton = (CompoundButton) parent.findViewById(R.id.dialpadButton); @@ -196,10 +188,6 @@ public class CallButtonFragment case R.id.addButton: getPresenter().addCallClicked(); break; - case R.id.changeToVoiceButton: - // STOPSHIP One way video options - getPresenter().displayModifyCallOptions(); - break; case R.id.muteButton: { getPresenter().muteClicked(!mMuteButton.isSelected()); break; @@ -219,8 +207,7 @@ public class CallButtonFragment getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected()); break; case R.id.changeToVideoButton: - // STOPSHIP One way video options - getPresenter().displayModifyCallOptions(); + getPresenter().changeToVideoClicked(); break; case R.id.switchCameraButton: getPresenter().switchCameraClicked( @@ -271,7 +258,6 @@ public class CallButtonFragment } ImageButton[] normalButtons = { - mChangeToVoiceButton, mSwapButton, mChangeToVideoButton, mAddCallButton, @@ -358,7 +344,6 @@ public class CallButtonFragment mIsEnabled = isEnabled; mAudioButton.setEnabled(isEnabled); - mChangeToVoiceButton.setEnabled(isEnabled); mMuteButton.setEnabled(isEnabled); mShowDialpadButton.setEnabled(isEnabled); mHoldButton.setEnabled(isEnabled); @@ -389,8 +374,6 @@ public class CallButtonFragment switch (id) { case BUTTON_AUDIO: return mAudioButton; - case BUTTON_DOWNGRADE_TO_VOICE: - return mChangeToVoiceButton; case BUTTON_MUTE: return mMuteButton; case BUTTON_DIALPAD: @@ -444,74 +427,6 @@ public class CallButtonFragment } } - /**The function is called when Modify Call button gets pressed. The function creates and - * displays modify call options. - */ - @Override - public void displayModifyCallOptions() { - CallButtonPresenter.CallButtonUi ui = getUi(); - if (ui == null) { - Log.e(this, "Cannot display ModifyCallOptions as ui is null"); - return; - } - - Context context = getContext(); - - 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 ""; - } - private void addToOverflowMenu(int id, View button, PopupMenu menu) { button.setVisibility(View.GONE); menu.getMenu().add(Menu.NONE, id, Menu.NONE, button.getContentDescription()); @@ -539,7 +454,7 @@ public class CallButtonFragment final View button = getButtonById(i); if (visibility == BUTTON_VISIBLE) { visibleCount++; - if (visibleCount <= BUTTON_MAX_VISIBLE) { + if (visibleCount <= mButtonMaxVisible) { button.setVisibility(View.VISIBLE); prevVisibleButton = button; prevVisibleId = i; diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java index 82c576f79..d788a1097 100644 --- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java +++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java @@ -79,6 +79,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto InCallPresenter.getInstance().removeIncomingCallListener(this); InCallPresenter.getInstance().removeDetailsListener(this); InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this); + InCallPresenter.getInstance().removeCanAddCallListener(this); } @Override @@ -258,20 +259,16 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto getUi().displayDialpad(checked /* show */, true /* animate */); } - public void displayModifyCallOptions() { - getUi().displayModifyCallOptions(); - } - - public int getCurrentVideoState() { - return mCall.getVideoState(); - } - - public void changeToVideoClicked(VideoProfile videoProfile) { + public void changeToVideoClicked() { VideoCall videoCall = mCall.getVideoCall(); if (videoCall == null) { return; } + int currVideoState = mCall.getVideoState(); + int currUnpausedVideoState = CallUtils.getUnPausedVideoState(currVideoState); + currUnpausedVideoState |= VideoProfile.VideoState.BIDIRECTIONAL; + VideoProfile videoProfile = new VideoProfile(currUnpausedVideoState); videoCall.sendSessionModifyRequest(videoProfile); mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE); } @@ -316,15 +313,16 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto if (pause) { videoCall.setCamera(null); VideoProfile videoProfile = new VideoProfile( - mCall.getVideoState() | VideoProfile.VideoState.PAUSED); + mCall.getVideoState() & ~VideoProfile.VideoState.TX_ENABLED); videoCall.sendSessionModifyRequest(videoProfile); } else { InCallCameraManager cameraManager = InCallPresenter.getInstance(). getInCallCameraManager(); videoCall.setCamera(cameraManager.getActiveCameraId()); VideoProfile videoProfile = new VideoProfile( - mCall.getVideoState() & ~VideoProfile.VideoState.PAUSED); + mCall.getVideoState() | VideoProfile.VideoState.TX_ENABLED); videoCall.sendSessionModifyRequest(videoProfile); + mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE); } getUi().setVideoPaused(pause); } @@ -354,7 +352,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto * @param call The active call. */ private void updateButtonsState(Call call) { - Log.v(this, "Showing buttons for voice call."); + Log.v(this, "updateButtonsState"); final CallButtonUi ui = getUi(); final boolean isVideo = CallUtils.isVideoCall(call); @@ -373,9 +371,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto final boolean showAddCall = TelecomAdapter.getInstance().canAddCall(); final boolean showMerge = call.can( android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE); - // TODO: This button is currently being used for both upgrade and downgrade scenarios. - // It should be split into BUTTON_DOWNGRADE_TO_VOICE AND BUTTON_UPGRADE_TO_VIDEO - final boolean showUpgradeToVideo = isVideo || + final boolean showUpgradeToVideo = !isVideo && (call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX) && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX)); @@ -388,7 +384,6 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto ui.showButton(BUTTON_MUTE, showMute); ui.showButton(BUTTON_ADD_CALL, showAddCall); ui.showButton(BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo); - ui.showButton(BUTTON_DOWNGRADE_TO_VOICE, false); ui.showButton(BUTTON_SWITCH_CAMERA, isVideo); ui.showButton(BUTTON_PAUSE_VIDEO, isVideo); ui.showButton(BUTTON_DIALPAD, !isVideo); @@ -436,7 +431,6 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto void setAudio(int mode); void setSupportedAudio(int mask); void displayDialpad(boolean on, boolean animate); - void displayModifyCallOptions(); boolean isDialpadVisible(); /** diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java index 691589ca0..499f46417 100644 --- a/InCallUI/src/com/android/incallui/CallCardFragment.java +++ b/InCallUI/src/com/android/incallui/CallCardFragment.java @@ -31,6 +31,8 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.os.Trace; +import android.os.Handler; +import android.os.Looper; import android.telecom.DisconnectCause; import android.telecom.VideoProfile; import android.telephony.PhoneNumberUtils; @@ -63,6 +65,38 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr implements CallCardPresenter.CallCardUi { private static final String TAG = "CallCardFragment"; + /** + * Internal class which represents the call state label which is to be applied. + */ + private class CallStateLabel { + private CharSequence mCallStateLabel; + private boolean mIsAutoDismissing; + + public CallStateLabel(CharSequence callStateLabel, boolean isAutoDismissing) { + mCallStateLabel = callStateLabel; + mIsAutoDismissing = isAutoDismissing; + } + + public CharSequence getCallStateLabel() { + return mCallStateLabel; + } + + /** + * Determines if the call state label should auto-dismiss. + * + * @return {@code true} if the call state label should auto-dismiss. + */ + public boolean isAutoDismissing() { + return mIsAutoDismissing; + } + }; + + /** + * The duration of time (in milliseconds) a call state label should remain visible before + * resetting to its previous value. + */ + private static final long CALL_STATE_LABEL_RESET_DELAY_MS = 3000; + private AnimatorSet mAnimatorSet; private int mShrinkAnimationDuration; private int mFabNormalDiameter; @@ -119,6 +153,13 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr private MaterialPalette mCurrentThemeColors; + /** + * Call state label to set when an auto-dismissing call state label is dismissed. + */ + private CharSequence mPostResetCallStateLabel; + private boolean mCallStateLabelResetPending = false; + private Handler mHandler; + @Override public CallCardPresenter.CallCardUi getUi() { return this; @@ -133,6 +174,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mHandler = new Handler(Looper.getMainLooper()); mShrinkAnimationDuration = getResources().getInteger(R.integer.shrink_animation_duration); mVideoAnimationDuration = getResources().getInteger(R.integer.video_animation_duration); mFloatingActionButtonVerticalOffset = getResources().getDimensionPixelOffset( @@ -269,6 +311,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr // Retrieve here since at fragment creation time the incoming video view is not inflated. final View videoView = getView().findViewById(R.id.incomingVideo); + if (videoView == null) { + return; + } // Determine how much space there is below or to the side of the call card. final float spaceBesideCallCard = getSpaceBesideCallCard(); @@ -492,15 +537,16 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr boolean isWifi, boolean isConference) { boolean isGatewayCall = !TextUtils.isEmpty(gatewayNumber); - CharSequence callStateLabel = getCallStateLabelFromState(state, videoState, + CallStateLabel callStateLabel = getCallStateLabelFromState(state, videoState, sessionModificationState, disconnectCause, connectionLabel, isGatewayCall, isWifi, isConference); - Log.v(this, "setCallState " + callStateLabel); + Log.v(this, "setCallState " + callStateLabel.getCallStateLabel()); + Log.v(this, "AutoDismiss " + callStateLabel.isAutoDismissing()); Log.v(this, "DisconnectCause " + disconnectCause.toString()); Log.v(this, "gateway " + connectionLabel + gatewayNumber); - if (TextUtils.equals(callStateLabel, mCallStateLabel.getText())) { + if (TextUtils.equals(callStateLabel.getCallStateLabel(), mCallStateLabel.getText())) { // Nothing to do if the labels are the same if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) { mCallStateLabel.clearAnimation(); @@ -510,24 +556,13 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } // Update the call state label and icon. - if (!TextUtils.isEmpty(callStateLabel)) { - mCallStateLabel.setText(callStateLabel); - mCallStateLabel.setAlpha(1); - mCallStateLabel.setVisibility(View.VISIBLE); - + setCallStateLabel(callStateLabel); + if (!TextUtils.isEmpty(callStateLabel.getCallStateLabel())) { if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) { mCallStateLabel.clearAnimation(); } else { mCallStateLabel.startAnimation(mPulseAnimation); } - } else { - Animation callStateLabelAnimation = mCallStateLabel.getAnimation(); - if (callStateLabelAnimation != null) { - callStateLabelAnimation.cancel(); - } - mCallStateLabel.setText(null); - mCallStateLabel.setAlpha(0); - mCallStateLabel.setVisibility(View.GONE); } if (callStateIcon != null) { @@ -538,7 +573,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mCallStateIcon.setImageDrawable(callStateIcon); if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED - || TextUtils.isEmpty(callStateLabel)) { + || TextUtils.isEmpty(callStateLabel.getCallStateLabel())) { mCallStateIcon.clearAnimation(); } else { mCallStateIcon.startAnimation(mPulseAnimation); @@ -569,7 +604,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr if (state == Call.State.INCOMING) { if (callStateLabel != null) { - getView().announceForAccessibility(callStateLabel); + getView().announceForAccessibility(callStateLabel.getCallStateLabel()); } if (mPrimaryName.getText() != null) { getView().announceForAccessibility(mPrimaryName.getText()); @@ -577,6 +612,50 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } } + private void setCallStateLabel(CallStateLabel callStateLabel) { + Log.v(this, "setCallStateLabel : label = " + callStateLabel.getCallStateLabel()); + + if (callStateLabel.isAutoDismissing()) { + mCallStateLabelResetPending = true; + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + Log.v(this, "restoringCallStateLabel : label = " + + mPostResetCallStateLabel); + changeCallStateLabel(mPostResetCallStateLabel); + mCallStateLabelResetPending = false; + } + }, CALL_STATE_LABEL_RESET_DELAY_MS); + + changeCallStateLabel(callStateLabel.getCallStateLabel()); + } else { + // Keep track of the current call state label; used when resetting auto dismissing + // call state labels. + mPostResetCallStateLabel = callStateLabel.getCallStateLabel(); + + if (!mCallStateLabelResetPending) { + changeCallStateLabel(callStateLabel.getCallStateLabel()); + } + } + } + + private void changeCallStateLabel(CharSequence callStateLabel) { + Log.v(this, "changeCallStateLabel : label = " + callStateLabel); + if (!TextUtils.isEmpty(callStateLabel)) { + mCallStateLabel.setText(callStateLabel); + mCallStateLabel.setAlpha(1); + mCallStateLabel.setVisibility(View.VISIBLE); + } else { + Animation callStateLabelAnimation = mCallStateLabel.getAnimation(); + if (callStateLabelAnimation != null) { + callStateLabelAnimation.cancel(); + } + mCallStateLabel.setText(null); + mCallStateLabel.setAlpha(0); + mCallStateLabel.setVisibility(View.GONE); + } + } + @Override public void setCallbackNumber(String callbackNumber, boolean isEmergencyCall) { if (mInCallMessageLabel == null) { @@ -670,7 +749,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr * * TODO: Move this to the CallCardPresenter. */ - private CharSequence getCallStateLabelFromState(int state, int videoState, + private CallStateLabel getCallStateLabelFromState(int state, int videoState, int sessionModificationState, DisconnectCause disconnectCause, String label, boolean isGatewayCall, boolean isWifi, boolean isConference) { final Context context = getView().getContext(); @@ -678,6 +757,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr boolean hasSuggestedLabel = label != null; boolean isAccount = hasSuggestedLabel && !isGatewayCall; + boolean isAutoDismissing = false; switch (state) { case Call.State.IDLE: @@ -689,15 +769,20 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr if ((isAccount || isWifi || isConference) && hasSuggestedLabel) { callStateLabel = label; } else if (sessionModificationState + == Call.SessionModificationState.REQUEST_REJECTED) { + callStateLabel = context.getString(R.string.card_title_video_call_rejected); + isAutoDismissing = true; + } else if (sessionModificationState == Call.SessionModificationState.REQUEST_FAILED) { callStateLabel = context.getString(R.string.card_title_video_call_error); + isAutoDismissing = true; } else if (sessionModificationState == Call.SessionModificationState.WAITING_FOR_RESPONSE) { callStateLabel = context.getString(R.string.card_title_video_call_requesting); - } else if (CallUtils.isVideoCall(videoState) && - VideoProfile.VideoState.isPaused(videoState)) { - callStateLabel = context.getString(R.string.card_title_video_call_paused); - } else if (VideoProfile.VideoState.isBidirectional(videoState)) { + } else if (sessionModificationState + == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { + callStateLabel = context.getString(R.string.card_title_video_call_requesting); + } else if (CallUtils.isVideoCall(videoState)) { callStateLabel = context.getString(R.string.card_title_video_call); } break; @@ -721,7 +806,8 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr callStateLabel = label; } else if (isAccount) { callStateLabel = context.getString(R.string.incoming_via_template, label); - } else if (VideoProfile.VideoState.isBidirectional(videoState)) { + } else if (VideoProfile.VideoState.isTransmissionEnabled(videoState) || + VideoProfile.VideoState.isReceptionEnabled(videoState)) { callStateLabel = context.getString(R.string.notification_incoming_video_call); } else { callStateLabel = context.getString(R.string.card_title_incoming_call); @@ -748,7 +834,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr default: Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state); } - return callStateLabel; + return new CallStateLabel(callStateLabel, isAutoDismissing); } private void showAndInitializeSecondaryCallInfo(boolean hasProvider) { diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index ee13e061f..b233ad56b 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -48,6 +48,7 @@ import com.android.incallui.InCallPresenter.IncomingCallListener; import com.android.incalluibind.ObjectFactory; import java.lang.ref.WeakReference; +import java.util.Objects; import com.google.common.base.Preconditions; @@ -58,7 +59,7 @@ import com.google.common.base.Preconditions; */ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> implements InCallStateListener, IncomingCallListener, InCallDetailsListener, - InCallEventListener { + InCallEventListener, CallList.CallUpdateListener { public interface EmergencyCallListener { public void onCallUpdated(BaseFragment fragment, boolean isEmergency); @@ -75,8 +76,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> private ContactCacheEntry mPrimaryContactInfo; private ContactCacheEntry mSecondaryContactInfo; private CallTimer mCallTimer; - private Context mContext; + private boolean mSpinnerShowing = false; public static class ContactLookupCallback implements ContactInfoCacheCallback { private final WeakReference<CallCardPresenter> mCallCardPresenter; @@ -121,6 +122,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> // Call may be null if disconnect happened already. if (call != null) { mPrimary = call; + CallList.getInstance().addCallUpdateListener(call.getId(), this); // start processing lookups right away. if (!call.isConferenceCall()) { @@ -158,6 +160,9 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> InCallPresenter.getInstance().removeIncomingCallListener(this); InCallPresenter.getInstance().removeDetailsListener(this); InCallPresenter.getInstance().removeInCallEventListener(this); + if (mPrimary != null) { + CallList.getInstance().removeCallUpdateListener(mPrimary.getId(), this); + } mPrimary = null; mPrimaryContactInfo = null; @@ -204,6 +209,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> final boolean secondaryChanged = !Call.areSame(mSecondary, secondary); mSecondary = secondary; + Call previousPrimary = mPrimary; mPrimary = primary; // Refresh primary call information if either: @@ -212,6 +218,11 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> if (mPrimary != null && (primaryChanged || ui.isManageConferenceVisible() != shouldShowManageConference())) { // primary call has changed + if (previousPrimary != null) { + CallList.getInstance().removeCallUpdateListener(previousPrimary.getId(), this); + } + CallList.getInstance().addCallUpdateListener(mPrimary.getId(), this); + mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mPrimary, mPrimary.getState() == Call.State.INCOMING); updatePrimaryDisplayInfo(); @@ -262,7 +273,6 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> } maybeShowManageConferenceCallButton(); - maybeShowProgressSpinner(); // Hide the end call button instantly if we're receiving an incoming call. getUi().setEndCallButtonEnabled(shouldShowEndCallButton(mPrimary, callState), @@ -279,6 +289,32 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> } } + @Override + public void onCallChanged(Call call) { + // No-op; specific call updates handled elsewhere. + } + + /** + * Handles a change to the session modification state for a call. Triggers showing the progress + * spinner, as well as updating the call state label. + * + * @param sessionModificationState The new session modification state. + */ + @Override + public void onSessionModificationStateChange(int sessionModificationState) { + Log.d(this, "onSessionModificationStateChange : sessionModificationState = " + + sessionModificationState); + + if (mPrimary == null) { + return; + } + maybeShowProgressSpinner(mPrimary.getState(), sessionModificationState); + getUi().setEndCallButtonEnabled(sessionModificationState != + Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST, + true /* shouldAnimate */); + updatePrimaryCallState(); + } + private String getSubscriptionNumber() { // If it's an emergency call, and they're not populating the callback number, // then try to fall back to the phone sub info (to hopefully get the SIM's @@ -322,11 +358,21 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> getUi().showManageConferenceCallButton(shouldShowManageConference()); } - private void maybeShowProgressSpinner() { - final boolean show = mPrimary != null && mPrimary.getSessionModificationState() - == Call.SessionModificationState.WAITING_FOR_RESPONSE - && mPrimary.getState() == Call.State.ACTIVE; - getUi().setProgressSpinnerVisible(show); + /** + * Determines if a pending session modification exists for the current call. If so, the + * progress spinner is shown, and the call state is updated. + * + * @param callState The call state. + * @param sessionModificationState The session modification state. + */ + private void maybeShowProgressSpinner(int callState, int sessionModificationState) { + final boolean show = sessionModificationState == + Call.SessionModificationState.WAITING_FOR_RESPONSE + && callState == Call.State.ACTIVE; + if (show != mSpinnerShowing) { + getUi().setProgressSpinnerVisible(show); + mSpinnerShowing = show; + } } /** @@ -657,7 +703,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> } private boolean hasOutgoingGatewayCall() { - // We only display the gateway information while STATE_DIALING so return false for any othe + // We only display the gateway information while STATE_DIALING so return false for any other // call state. // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which // is also called after a contact search completes (call is not present yet). Split the diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java index d89fead9a..bec81ec57 100644 --- a/InCallUI/src/com/android/incallui/CallList.java +++ b/InCallUI/src/com/android/incallui/CallList.java @@ -142,6 +142,21 @@ public class CallList { Trace.endSection(); } + /** + * Called when a single call has changed session modification state. + * + * @param call The call. + * @param sessionModificationState The new session modification state. + */ + public void onSessionModificationStateChange(Call call, int sessionModificationState) { + final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId()); + if (listeners != null) { + for (CallUpdateListener listener : listeners) { + listener.onSessionModificationStateChange(sessionModificationState); + } + } + } + public void notifyCallUpdateListeners(Call call) { final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId()); if (listeners != null) { @@ -501,7 +516,12 @@ public class CallList { */ public void notifyCallsOfDeviceRotation(int rotation) { for (Call call : mCallById.values()) { - if (call.getVideoCall() != null) { + // First, ensure a VideoCall is set on the call so that the change can be sent to the + // provider (a VideoCall can be present for a call that does not currently have video, + // but can be upgraded to video). + // Second, ensure that the call videoState has video enabled (there is no need to set + // device orientation on a voice call which has not yet been upgraded to video). + if (call.getVideoCall() != null && CallUtils.isVideoCall(call)) { call.getVideoCall().setDeviceOrientation(rotation); } } @@ -556,10 +576,19 @@ public class CallList { * that will get called upon disconnection. */ public void onDisconnect(Call call); + + } public interface CallUpdateListener { // TODO: refactor and limit arg to be call state. Caller info is not needed. public void onCallChanged(Call call); + + /** + * Notifies of a change to the session modification state for a call. + * + * @param sessionModificationState The new session modification state. + */ + public void onSessionModificationStateChange(int sessionModificationState); } } diff --git a/InCallUI/src/com/android/incallui/CallUtils.java b/InCallUI/src/com/android/incallui/CallUtils.java index 79cf4c7e7..0e0c4c6f5 100644 --- a/InCallUI/src/com/android/incallui/CallUtils.java +++ b/InCallUI/src/com/android/incallui/CallUtils.java @@ -39,9 +39,8 @@ public class CallUtils { } public static boolean isVideoCall(int videoState) { - return VideoProfile.VideoState.isBidirectional(videoState) - && VideoProfile.VideoState.isTransmissionEnabled(videoState) - && VideoProfile.VideoState.isReceptionEnabled(videoState); + return VideoProfile.VideoState.isTransmissionEnabled(videoState) + || VideoProfile.VideoState.isReceptionEnabled(videoState); } public static boolean isIncomingVideoCall(Call call) { diff --git a/InCallUI/src/com/android/incallui/GlowPadWrapper.java b/InCallUI/src/com/android/incallui/GlowPadWrapper.java index 58a5f30ea..177669668 100644 --- a/InCallUI/src/com/android/incallui/GlowPadWrapper.java +++ b/InCallUI/src/com/android/incallui/GlowPadWrapper.java @@ -49,6 +49,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger private AnswerListener mAnswerListener; private boolean mPingEnabled = true; private boolean mTargetTriggered = false; + private int mVideoState = VideoProfile.VideoState.BIDIRECTIONAL; public GlowPadWrapper(Context context) { super(context); @@ -125,11 +126,11 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger break; case R.drawable.ic_videocam: case R.drawable.ic_lockscreen_answer_video: - mAnswerListener.onAnswer(VideoProfile.VideoState.BIDIRECTIONAL, getContext()); + mAnswerListener.onAnswer(mVideoState, getContext()); mTargetTriggered = true; break; - case R.drawable.ic_toolbar_video_off: - InCallPresenter.getInstance().declineUpgradeRequest(getContext()); + case R.drawable.ic_lockscreen_decline_video: + mAnswerListener.onDeclineUpgradeRequest(getContext()); mTargetTriggered = true; break; default: @@ -152,9 +153,19 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger mAnswerListener = listener; } + /** + * Sets the video state represented by the "video" icon on the glow pad. + * + * @param videoState The new video state. + */ + public void setVideoState(int videoState) { + mVideoState = videoState; + } + public interface AnswerListener { void onAnswer(int videoState, Context context); void onDecline(Context context); + void onDeclineUpgradeRequest(Context context); void onText(); } } diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java index 5ecb96e3f..289bf6388 100644 --- a/InCallUI/src/com/android/incallui/InCallActivity.java +++ b/InCallUI/src/com/android/incallui/InCallActivity.java @@ -24,19 +24,21 @@ import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; -import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Point; +import android.hardware.SensorManager; import android.os.Bundle; import android.os.Trace; import android.telecom.DisconnectCause; import android.telecom.PhoneAccountHandle; import android.text.TextUtils; +import android.view.Display; import android.view.MenuItem; +import android.view.OrientationEventListener; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.KeyEvent; @@ -122,10 +124,13 @@ public class InCallActivity extends Activity implements FragmentDisplayManager { } }; + /** Listener for orientation changes. */ + private OrientationEventListener mOrientationEventListener; + /** - * Used to determine if a change in orientation has occurred. + * Used to determine if a change in rotation has occurred. */ - private static int sCurrentOrientation = Configuration.ORIENTATION_UNDEFINED; + private static int sPreviousRotation = -1; @Override protected void onCreate(Bundle icicle) { @@ -193,6 +198,28 @@ public class InCallActivity extends Activity implements FragmentDisplayManager { dialogFragment.setListener(mSelectAcctListener); } } + + mOrientationEventListener = new OrientationEventListener(this, + SensorManager.SENSOR_DELAY_NORMAL) { + @Override + public void onOrientationChanged(int orientation) { + // Orientation is the current device orientation in degrees. Ultimately we want + // the rotation (in fixed 90 degree intervals). + Display display = getWindowManager().getDefaultDisplay(); + int newRotation = display.getRotation(); + if (newRotation != sPreviousRotation) { + doOrientationChanged(newRotation); + } + } + }; + + if (mOrientationEventListener.canDetectOrientation()) { + Log.v(this, "Orientation detection enabled."); + mOrientationEventListener.enable(); + } else { + Log.v(this, "Orientation detection disabled."); + mOrientationEventListener.disable(); + } Log.d(this, "onCreate(): exit"); } @@ -215,9 +242,6 @@ public class InCallActivity extends Activity implements FragmentDisplayManager { // setting activity should be last thing in setup process InCallPresenter.getInstance().setActivity(this); - // It is possible that the activity restarted because orientation changed. - // Notify listeners if orientation changed. - doOrientationChanged(getResources().getConfiguration().orientation); InCallPresenter.getInstance().onActivityStarted(); } @@ -357,7 +381,7 @@ public class InCallActivity extends Activity implements FragmentDisplayManager { // in-call UI: if ((mConferenceManagerFragment == null || !mConferenceManagerFragment.isVisible()) - && !mCallCardFragment.isVisible()) { + && (mCallCardFragment == null || !mCallCardFragment.isVisible())) { return; } @@ -474,20 +498,19 @@ public class InCallActivity extends Activity implements FragmentDisplayManager { } /** - * Handles changes in device orientation. + * Handles changes in device rotation. * - * @param orientation The new device orientation. + * @param rotation The new device rotation. */ - 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 + private void doOrientationChanged(int rotation) { + Log.d(this, "doOrientationChanged prevOrientation=" + sPreviousRotation + + " newOrientation=" + rotation); + // Check to see if the rotation changed to prevent triggering rotation change events // for other configuration changes. - if (orientation != sCurrentOrientation) { - sCurrentOrientation = orientation; - InCallPresenter.getInstance().onDeviceRotationChange( - getWindowManager().getDefaultDisplay().getRotation()); - InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation); + if (rotation != sPreviousRotation) { + sPreviousRotation = rotation; + InCallPresenter.getInstance().onDeviceRotationChange(rotation); + InCallPresenter.getInstance().onDeviceOrientationChange(sPreviousRotation); } } diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java b/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java index ba4ab660d..ede1129cd 100644 --- a/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java +++ b/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java @@ -80,6 +80,19 @@ public class InCallVideoCallCallback extends VideoCall.Callback { Log.d(this, "onSessionModifyResponseReceived status=" + status + " requestedProfile=" + requestedProfile + " responseProfile=" + responseProfile); if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { + // Report the reason the upgrade failed as the new session modification state. + if (status == VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT) { + mCall.setSessionModificationState( + Call.SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT); + } else { + if (status == VideoProvider.SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE) { + mCall.setSessionModificationState( + Call.SessionModificationState.REQUEST_REJECTED); + } else { + mCall.setSessionModificationState( + Call.SessionModificationState.REQUEST_FAILED); + } + } InCallVideoCallCallbackNotifier.getInstance().upgradeToVideoFail(status, mCall); } else if (requestedProfile != null && responseProfile != null) { boolean modifySucceeded = requestedProfile.getVideoState() == @@ -95,6 +108,8 @@ public class InCallVideoCallCallback extends VideoCall.Callback { } else { Log.d(this, "onSessionModifyResponseReceived request and response Profiles are null"); } + // Finally clear the outstanding request. + mCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); } /** diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java index 86f997575..a54536ce6 100644 --- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java +++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java @@ -45,7 +45,9 @@ import com.android.incallui.InCallPresenter.InCallState; /** * This class adds Notifications to the status bar for the in-call experience. */ -public class StatusBarNotifier implements InCallPresenter.InCallStateListener { +public class StatusBarNotifier implements InCallPresenter.InCallStateListener, + CallList.CallUpdateListener { + // notification types private static final int IN_CALL_NOTIFICATION = 1; @@ -58,6 +60,8 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { private int mSavedContent = 0; private Bitmap mSavedLargeIcon; private String mSavedContentTitle; + private String mCallId = null; + private InCallState mInCallState; public StatusBarNotifier(Context context, ContactInfoCache contactInfoCache) { Preconditions.checkNotNull(context); @@ -74,7 +78,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { @Override public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { Log.d(this, "onStateChange"); - + mInCallState = newState; updateNotification(newState, callList); } @@ -146,6 +150,12 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { final boolean isIncoming = (call.getState() == Call.State.INCOMING || call.getState() == Call.State.CALL_WAITING); + if (mCallId != null) { + CallList.getInstance().removeCallUpdateListener(mCallId, this); + } + mCallId = call.getId(); + CallList.getInstance().addCallUpdateListener(call.getId(), this); + // we make a call to the contact info cache to query for supplemental data to what the // call provides. This includes the contact name and photo. // This callback will always get called immediately and synchronously with whatever data @@ -187,7 +197,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { // Check if data has changed; if nothing is different, don't issue another notification. final int iconResId = getIconToDisplay(call); - final Bitmap largeIcon = getLargeIconToDisplay(contactInfo, call); + Bitmap largeIcon = getLargeIconToDisplay(contactInfo, call); final int contentResId = getContentString(call); final String contentTitle = getContentTitle(contactInfo, call); @@ -195,6 +205,10 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { return; } + if (largeIcon != null) { + largeIcon = getRoundedIcon(largeIcon); + } + /* * Nothing more to check...build and send it. */ @@ -342,15 +356,18 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) { largeIcon = ((BitmapDrawable) contactInfo.photo).getBitmap(); } + return largeIcon; + } - if (largeIcon != null) { - final int height = (int) mContext.getResources().getDimension( - android.R.dimen.notification_large_icon_height); - final int width = (int) mContext.getResources().getDimension( - android.R.dimen.notification_large_icon_width); - largeIcon = BitmapUtil.getRoundedBitmap(largeIcon, width, height); + private Bitmap getRoundedIcon(Bitmap bitmap) { + if (bitmap == null) { + return null; } - return largeIcon; + final int height = (int) mContext.getResources().getDimension( + android.R.dimen.notification_large_icon_height); + final int width = (int) mContext.getResources().getDimension( + android.R.dimen.notification_large_icon_width); + return BitmapUtil.getRoundedBitmap(bitmap, width, height); } /** @@ -574,4 +591,25 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { return PendingIntent.getBroadcast(context, 0, intent, 0); } + @Override + public void onCallChanged(Call call) { + // no-op + } + + /** + * Responds to changes in the session modification state for the call by dismissing the + * status bar notification as required. + * + * @param sessionModificationState The new session modification state. + */ + @Override + public void onSessionModificationStateChange(int sessionModificationState) { + if (sessionModificationState == Call.SessionModificationState.NO_REQUEST) { + if (mCallId != null) { + CallList.getInstance().removeCallUpdateListener(mCallId, this); + } + + updateNotification(mInCallState, CallList.getInstance()); + } + } } diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java index c285ff3fe..24e9e8d76 100644 --- a/InCallUI/src/com/android/incallui/VideoCallFragment.java +++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java @@ -16,15 +16,10 @@ package com.android.incallui; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Matrix; 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; @@ -33,7 +28,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.view.ViewTreeObserver; -import android.widget.Toast; +import android.widget.FrameLayout; +import android.widget.ImageView; import com.google.common.base.Objects; @@ -65,12 +61,6 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, */ 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; @@ -91,6 +81,21 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, private View mVideoViews; /** + * The {@link FrameLayout} containing the preview surface. + */ + private View mPreviewVideoContainer; + + /** + * Icon shown to indicate that the outgoing camera has been turned off. + */ + private View mCameraOff; + + /** + * {@link ImageView} containing the user's profile photo. + */ + private ImageView mPreviewPhoto; + + /** * {@code True} when the layout of the activity has been completed. */ private boolean mIsLayoutComplete = false; @@ -534,19 +539,21 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, } /** - * Show or hide preview and incoming video views + * Hides and shows the incoming video view and changes the outgoing video view's state based on + * whether outgoing view is enabled or not. */ - public void showVideoViews(boolean showPreview, boolean showIncoming) { + public void showVideoViews(boolean previewPaused, 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); + if (mCameraOff != null) { + mCameraOff.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE); + } + if (mPreviewPhoto != null) { + mPreviewPhoto.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE); } } @@ -558,43 +565,6 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, } /** - * 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(); - } - - /** * 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. @@ -613,6 +583,11 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, sVideoSurfacesInUse = false; } + @Override + public ImageView getPreviewPhotoView() { + return mPreviewPhoto; + } + private void onPresenterChanged(VideoCallPresenter presenter) { Log.d(this, "onPresenterChanged: Presenter=" + presenter); if (sDisplaySurface != null) { @@ -678,11 +653,19 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, return; } + // Set the dimensions of both the video surface and the FrameLayout containing it. ViewGroup.LayoutParams params = preview.getLayoutParams(); params.width = width; params.height = height; preview.setLayoutParams(params); + if (mPreviewVideoContainer != null) { + ViewGroup.LayoutParams containerParams = mPreviewVideoContainer.getLayoutParams(); + containerParams.width = width; + containerParams.height = height; + mPreviewVideoContainer.setLayoutParams(containerParams); + } + // The width and height are interchanged outside of this method based on the current // orientation, so we can transform using "width", which will be either the width or // the height. @@ -739,47 +722,6 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, } /** - * 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. @@ -818,6 +760,10 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, } if (mVideoViews != null) { + mPreviewVideoContainer = mVideoViews.findViewById(R.id.previewVideoContainer); + mCameraOff = mVideoViews.findViewById(R.id.previewCameraOff); + mPreviewPhoto = (ImageView) mVideoViews.findViewById(R.id.previewProfilePhoto); + TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo); Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse); diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java index f11f328e6..ba82c01fc 100644 --- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java +++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java @@ -18,8 +18,13 @@ package com.android.incallui; import android.content.Context; import android.content.res.Configuration; +import android.database.Cursor; import android.graphics.Point; +import android.net.Uri; +import android.os.AsyncTask; import android.os.Handler; +import android.os.Looper; +import android.provider.ContactsContract; import android.telecom.AudioState; import android.telecom.CameraCapabilities; import android.telecom.Connection; @@ -27,15 +32,17 @@ import android.telecom.Connection.VideoProvider; import android.telecom.InCallService.VideoCall; import android.telecom.VideoProfile; import android.view.Surface; +import android.view.View; +import android.widget.ImageView; import com.android.contacts.common.CallUtil; +import com.android.contacts.common.ContactPhotoManager; import com.android.incallui.InCallPresenter.InCallDetailsListener; import com.android.incallui.InCallPresenter.InCallOrientationListener; import com.android.incallui.InCallPresenter.InCallStateListener; import com.android.incallui.InCallPresenter.IncomingCallListener; import com.android.incallui.InCallVideoCallCallbackNotifier.SurfaceChangeListener; import com.android.incallui.InCallVideoCallCallbackNotifier.VideoEventListener; -import com.google.common.base.Preconditions; import java.util.Objects; @@ -70,6 +77,22 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi public static final boolean DEBUG = false; /** + * Runnable which is posted to schedule automatically entering fullscreen mode. + */ + private Runnable mAutoFullscreenRunnable = new Runnable() { + @Override + public void run() { + if (mAutoFullScreenPending) { + Log.v(this, "Automatically entering fullscreen mode."); + setFullScreen(true); + mAutoFullScreenPending = false; + } else { + Log.v(this, "Skipping scheduled fullscreen mode."); + } + } + }; + + /** * Determines the device orientation (portrait/lanscape). */ public int getDeviceOrientation() { @@ -158,9 +181,38 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi private static boolean mIsVideoMode = false; - /** Handler which resets request state to NO_REQUEST after an interval. */ - private Handler mSessionModificationResetHandler; - private static final long SESSION_MODIFICATION_RESET_DELAY_MS = 3000; + /** + * Contact photo manager to retrieve cached contact photo information. + */ + private ContactPhotoManager mContactPhotoManager = null; + + /** + * The URI for the user's profile photo, or {@code null} if not specified. + */ + private ContactInfoCache.ContactCacheEntry mProfileInfo = null; + + /** + * UI thread handler used for delayed task execution. + */ + private Handler mHandler; + + /** + * Determines whether video calls should automatically enter full screen mode after + * {@link #mAutoFullscreenTimeoutMillis} milliseconds. + */ + private boolean mIsAutoFullscreenEnabled = false; + + /** + * Determines the number of milliseconds after which a video call will automatically enter + * fullscreen mode. Requires {@link #mIsAutoFullscreenEnabled} to be {@code true}. + */ + private int mAutoFullscreenTimeoutMillis = 0; + + /** + * Determines if the countdown is currently running to automatically enter full screen video + * mode. + */ + private boolean mAutoFullScreenPending = false; /** * Initializes the presenter. @@ -168,10 +220,14 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * @param context The current context. */ public void init(Context context) { - mContext = Preconditions.checkNotNull(context); + mContext = context; mMinimumVideoDimension = mContext.getResources().getDimension( R.dimen.video_preview_small_dimension); - mSessionModificationResetHandler = new Handler(); + mHandler = new Handler(Looper.getMainLooper()); + mIsAutoFullscreenEnabled = mContext.getResources() + .getBoolean(R.bool.video_call_auto_fullscreen); + mAutoFullscreenTimeoutMillis = mContext.getResources().getInteger( + R.integer.video_call_auto_fullscreen_timeout); } /** @@ -315,6 +371,26 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi private void toggleFullScreen() { mIsFullScreen = !mIsFullScreen; + Log.v(this, "toggleFullScreen = " + mIsFullScreen); + // Ensure we cancel any scheduled auto activation of fullscreen mode as the user's setting + // should override any automatic operation. + cancelAutoFullScreen(); + InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen); + } + + /** + * Sets the current full screen mode. + * + * @param isFullScreen {@code true} if full screen mode should be active, {@code false} + * otherwise. + */ + public void setFullScreen(boolean isFullScreen) { + Log.v(this, "setFullScreen = " + isFullScreen); + if (mIsFullScreen == isFullScreen) { + Log.v(this, "setFullScreen ignored as already in that state."); + return; + } + mIsFullScreen = isFullScreen; InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen); } @@ -367,21 +443,29 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi // Determine the primary active call). Call primary = null; + + // Determine the call which is the focus of the user's attention. In the case of an + // incoming call waiting call, the primary call is still the active video call, however + // the determination of whether we should be in fullscreen mode is based on the type of the + // incoming call, not the active video call. + Call currentCall = null; + if (newState == InCallPresenter.InCallState.INCOMING) { // 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(); + currentCall = callList.getIncomingCall(); if (!CallUtils.isActiveVideoCall(primary)) { primary = callList.getIncomingCall(); } } else if (newState == InCallPresenter.InCallState.OUTGOING) { - primary = callList.getOutgoingCall(); + currentCall = primary = callList.getOutgoingCall(); } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) { - primary = callList.getPendingOutgoingCall(); + currentCall = primary = callList.getPendingOutgoingCall(); } else if (newState == InCallPresenter.InCallState.INCALL) { - primary = callList.getActiveCall(); + currentCall = primary = callList.getActiveCall(); } final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary); @@ -390,10 +474,17 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi Log.d(this, "onStateChange mPrimaryCall = " + mPrimaryCall); if (primaryChanged) { onPrimaryCallChanged(primary); - } else if(mPrimaryCall!=null) { + } else if (mPrimaryCall != null) { updateVideoCall(primary); } updateCallCache(primary); + + // If the call context changed, potentially exit fullscreen or schedule auto enter of + // fullscreen mode. + // If the current call context is no longer a video call, exit fullscreen mode. + maybeExitFullscreen(currentCall); + // Schedule auto-enter of fullscreen mode if the current call context is a video call + maybeAutoEnterFullscreen(currentCall); } private void checkForVideoStateChange(Call call) { @@ -401,8 +492,10 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState(); Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall - + " hasVideoStateChanged=" + - hasVideoStateChanged + " isVideoMode=" + isVideoMode()); + + " hasVideoStateChanged=" + hasVideoStateChanged + " isVideoMode=" + + isVideoMode() + " previousVideoState: " + + VideoProfile.VideoState.videoStateToString(mCurrentVideoState) + " newVideoState: " + + VideoProfile.VideoState.videoStateToString(call.getVideoState())); if (!hasVideoStateChanged) { return; @@ -610,6 +703,8 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi updateAudioMode(true); mIsVideoMode = true; + + maybeAutoEnterFullscreen(call); } //TODO: Move this into Telecom. InCallUI should not be this close to audio functionality. @@ -624,7 +719,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi sPrevVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID; Log.d(this, "Is previous audio mode valid = " + isPrevAudioModeValid + " enableSpeaker is " - + enableSpeaker); + + enableSpeaker); // Set audio mode to previous mode if enableSpeaker is false. if (isPrevAudioModeValid && !enableSpeaker) { @@ -682,9 +777,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi enableCamera(mVideoCall, false); Log.d(this, "exitVideoMode mIsFullScreen: " + mIsFullScreen); - if (mIsFullScreen) { - toggleFullScreen(); - } + setFullScreen(false); mIsVideoMode = false; } @@ -712,6 +805,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi ui.showVideoViews(true, false); } else if (VideoProfile.VideoState.isReceptionEnabled(videoState)) { ui.showVideoViews(false, !isPaused && isCallActive); + loadProfilePhotoAsync(); } else { ui.hideVideoUi(); } @@ -770,18 +864,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @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); + // No-op } /** @@ -851,13 +934,28 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @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; + StringBuilder sb = new StringBuilder(); + sb.append("onCallSessionEvent = "); + + switch (event) { + case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE: + sb.append("rx_pause"); + break; + case Connection.VideoProvider.SESSION_EVENT_RX_RESUME: + sb.append("rx_resume"); + break; + case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE: + sb.append("camera_failure"); + break; + case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY: + sb.append("camera_ready"); + break; + default: + sb.append("unknown event = "); + sb.append(event); + break; } - ui.displayCallSessionEvent(event); + Log.d(this, sb.toString()); } /** @@ -868,12 +966,6 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi @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); } /** @@ -917,8 +1009,6 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi if (call == null) { return; } - - call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); } @Override @@ -931,25 +1021,6 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi if (call == null) { return; } - - 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 @@ -991,7 +1062,6 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * @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(); @@ -1013,6 +1083,65 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi ui.setDisplayVideoSize(size.x, size.y); } + /** + * Exits fullscreen mode if the current call context has changed to a non-video call. + * + * @param call The call. + */ + protected void maybeExitFullscreen(Call call) { + if (call == null) { + return; + } + + if (!CallUtils.isVideoCall(call) || call.getState() == Call.State.INCOMING) { + setFullScreen(false); + } + } + + /** + * Schedules auto-entering of fullscreen mode. + * Will not enter full screen mode if any of the following conditions are met: + * 1. No call + * 2. Call is not active + * 3. Call is not video call + * 4. Already in fullscreen mode + * + * @param call The current call. + */ + protected void maybeAutoEnterFullscreen(Call call) { + if (!mIsAutoFullscreenEnabled) { + return; + } + + if (call == null || ( + call != null && (call.getState() != Call.State.ACTIVE || + !CallUtils.isVideoCall(call)) || mIsFullScreen)) { + // Ensure any previously scheduled attempt to enter fullscreen is cancelled. + cancelAutoFullScreen(); + return; + } + + if (mAutoFullScreenPending) { + Log.v(this, "maybeAutoEnterFullscreen : already pending."); + return; + } + Log.v(this, "maybeAutoEnterFullscreen : scheduled"); + mAutoFullScreenPending = true; + mHandler.postDelayed(mAutoFullscreenRunnable, mAutoFullscreenTimeoutMillis); + } + + /** + * Cancels pending auto fullscreen mode. + */ + public void cancelAutoFullScreen() { + if (!mAutoFullScreenPending) { + Log.v(this, "cancelAutoFullScreen : none pending."); + return; + } + Log.v(this, "cancelAutoFullScreen : cancelling pending"); + mAutoFullScreenPending = false; + } + private static boolean isAudioRouteEnabled(int audioRoute, int audioRouteMask) { return ((audioRoute & audioRouteMask) != 0); } @@ -1101,12 +1230,87 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi } /** + * Starts an asynchronous load of the user's profile photo. + */ + public void loadProfilePhotoAsync() { + final VideoCallUi ui = getUi(); + if (ui == null) { + return; + } + + final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { + /** + * Performs asynchronous load of the user profile information. + * + * @param params The parameters of the task. + * + * @return {@code null}. + */ + @Override + protected Void doInBackground(Void... params) { + if (mProfileInfo == null) { + // Try and read the photo URI from the local profile. + mProfileInfo = new ContactInfoCache.ContactCacheEntry(); + final Cursor cursor = mContext.getContentResolver().query( + ContactsContract.Profile.CONTENT_URI, new String[]{ + ContactsContract.CommonDataKinds.Phone._ID, + ContactsContract.CommonDataKinds.Phone.PHOTO_URI, + ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY, + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, + }, null, null, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + mProfileInfo.lookupKey = cursor.getString(cursor.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY)); + String photoUri = cursor.getString(cursor.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.PHOTO_URI)); + mProfileInfo.displayPhotoUri = photoUri == null ? null + : Uri.parse(photoUri); + mProfileInfo.name = cursor.getString(cursor.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + } + } finally { + cursor.close(); + } + } + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + // If user profile information was found, issue an async request to load the user's + // profile photo. + if (mProfileInfo != null) { + if (mContactPhotoManager == null) { + mContactPhotoManager = ContactPhotoManager.getInstance(mContext); + } + ContactPhotoManager.DefaultImageRequest imageRequest = (mProfileInfo != null) + ? null : + new ContactPhotoManager.DefaultImageRequest(mProfileInfo.name, + mProfileInfo.lookupKey, false /* isCircularPhoto */); + + ImageView photoView = ui.getPreviewPhotoView(); + if (photoView == null) { + return; + } + mContactPhotoManager.loadDirectoryPhoto(photoView, + mProfileInfo.displayPhotoUri, + false /* darkTheme */, false /* isCircular */, imageRequest); + } + } + }; + + task.execute(); + } + + /** * Defines the VideoCallUI interactions. */ public interface VideoCallUi extends Ui { void showVideoViews(boolean showPreview, boolean showIncoming); void hideVideoUi(); - void showVideoQualityChanged(int videoQuality); boolean isDisplayVideoSurfaceCreated(); boolean isPreviewVideoSurfaceCreated(); Surface getDisplayVideoSurface(); @@ -1115,10 +1319,9 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi 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(); Point getPreviewSize(); void cleanupSurfaces(); + ImageView getPreviewPhotoView(); } } diff --git a/InCallUI/src/com/android/incallui/widget/multiwaveview/GlowPadView.java b/InCallUI/src/com/android/incallui/widget/multiwaveview/GlowPadView.java index 812e883c0..4bd27db9c 100644 --- a/InCallUI/src/com/android/incallui/widget/multiwaveview/GlowPadView.java +++ b/InCallUI/src/com/android/incallui/widget/multiwaveview/GlowPadView.java @@ -637,11 +637,15 @@ public class GlowPadView extends View { } /** - * Sets teh handle drawable to the drawable specified by the resource ID. + * Sets the handle drawable to the drawable specified by the resource ID. * @param resourceId */ public void setHandleDrawable(int resourceId) { - mHandleDrawable = new TargetDrawable(getResources(), resourceId, 2); + if (mHandleDrawable != null) { + mHandleDrawable.setDrawable(getResources(), resourceId); + } else { + mHandleDrawable = new TargetDrawable(getResources(), resourceId, 1); + } mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); } |