From b040158d7bb2f5c7d06bd2c1db5e96adafbcff3e Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Tue, 5 May 2015 12:22:38 -0700 Subject: DO NOT MERGE Video call upgrade/dowgrade request changes. - fixed potential NPE in VideoCallFragment when setting preview size. - moved photo load into the postExecute for the async task -- it is already threaded and I was seeing intermittent concurrency issues. - Changed CallButtonFragment to retrieve max # of buttons from config.xml. - Added override for wider screens (e.g. N6 and wider) to show an extra button. - Reorganized call buttons so that the "Camera on/off" button is adjacent to the flip camera button. - Changed answer Glowpad to pass correct video state so that accepting a video request uses the correct state (important for accepting requests to turn camera back on). - added new Session modification state REQUEST_REJECTED for when the remote user explicitly declines the request. This is used to trigger a "video request rejected" message when the remote party rejects the request. Bug: 20257400 Change-Id: Ibe25eb045ee868748f91bf411f285629d36ebcd2 --- .../src/com/android/incallui/AnswerFragment.java | 56 ++++----- .../src/com/android/incallui/AnswerPresenter.java | 41 +++---- InCallUI/src/com/android/incallui/Call.java | 3 +- .../com/android/incallui/CallButtonFragment.java | 12 +- .../com/android/incallui/CallButtonPresenter.java | 1 + .../src/com/android/incallui/CallCardFragment.java | 133 +++++++++++++++++---- .../com/android/incallui/CallCardPresenter.java | 64 ++++++++-- InCallUI/src/com/android/incallui/CallList.java | 24 ++++ .../src/com/android/incallui/GlowPadWrapper.java | 17 ++- .../android/incallui/InCallVideoCallCallback.java | 15 +++ .../com/android/incallui/StatusBarNotifier.java | 35 +++++- .../com/android/incallui/VideoCallFragment.java | 10 +- .../com/android/incallui/VideoCallPresenter.java | 45 ++----- 13 files changed, 314 insertions(+), 142 deletions(-) (limited to 'InCallUI/src/com') 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 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() @@ -166,27 +177,15 @@ public class AnswerPresenter extends Presenter 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) { @@ -220,15 +219,16 @@ public class AnswerPresenter extends Presenter } 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); } } @@ -293,6 +293,7 @@ public class AnswerPresenter extends Presenter 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 textResponses); public Context getContext(); diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java index 18eb7471e..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 { @@ -510,7 +511,7 @@ public class Call { Log.d(this, "setSessionModificationState " + state + " mSessionModificationState=" + mSessionModificationState); if (hasChanged) { - update(); + CallList.getInstance().onSessionModificationStateChange(this, state); } } diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java index 506ca7dcc..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 @@ -127,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 @@ -458,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 43ee3326b..d788a1097 100644 --- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java +++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java @@ -322,6 +322,7 @@ public class CallButtonPresenter extends Presenter 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 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 mCallCardPresenter; @@ -121,6 +122,7 @@ public class CallCardPresenter extends Presenter // 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 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 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 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 } 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 } } + @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 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 } 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..e937bb162 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 listeners = mCallUpdateListenerMap.get(call.getId()); + if (listeners != null) { + for (CallUpdateListener listener : listeners) { + listener.onSessionModificationStateChange(sessionModificationState); + } + } + } + public void notifyCallUpdateListeners(Call call) { final List listeners = mCallUpdateListenerMap.get(call.getId()); if (listeners != null) { @@ -556,10 +571,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/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/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..bc3687ced 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 @@ -574,4 +584,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 6d70b4271..24e9e8d76 100644 --- a/InCallUI/src/com/android/incallui/VideoCallFragment.java +++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java @@ -659,10 +659,12 @@ public class VideoCallFragment extends BaseFragment