diff options
Diffstat (limited to 'src')
29 files changed, 1290 insertions, 524 deletions
diff --git a/src/com/android/incallui/AnswerPresenter.java b/src/com/android/incallui/AnswerPresenter.java index 5af13f70..184b3096 100644 --- a/src/com/android/incallui/AnswerPresenter.java +++ b/src/com/android/incallui/AnswerPresenter.java @@ -16,10 +16,9 @@ package com.android.incallui; -import android.telecom.PhoneCapabilities; -import android.app.KeyguardManager; import android.content.Context; import android.os.SystemProperties; +import android.telecom.TelecomManager; import android.telecom.VideoProfile; import java.util.List; @@ -323,6 +322,7 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> public void onText() { if (getUi() != null) { + InCallPresenter.getInstance().getTelecomManager().silenceRinger(); getUi().showMessageDialog(); } } @@ -343,7 +343,9 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> final Context context = getUi().getContext(); mHasTextMessages = textMsgs != null; - boolean withSms = call.can(PhoneCapabilities.RESPOND_VIA_TEXT) && mHasTextMessages; + boolean withSms = + call.can(android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT) + && mHasTextMessages; if (call.isVideoCall(context)) { if (withSms) { getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITH_SMS); @@ -380,7 +382,8 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi> call.getId()); getUi().showAnswerUi(true); - boolean withSms = call.can(PhoneCapabilities.RESPOND_VIA_TEXT) && textMsgs != null; + boolean withSms = call.can(android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT) + && textMsgs != null; if (call.isVideoCall(getUi().getContext())) { if (withSms) { getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITH_SMS); diff --git a/src/com/android/incallui/AudioModeProvider.java b/src/com/android/incallui/AudioModeProvider.java index df5a2333..7a91731b 100644 --- a/src/com/android/incallui/AudioModeProvider.java +++ b/src/com/android/incallui/AudioModeProvider.java @@ -41,8 +41,8 @@ import java.util.List; private Phone.Listener mPhoneListener = new Phone.Listener() { @Override public void onAudioStateChanged(Phone phone, AudioState audioState) { - onAudioModeChange(audioState.route, audioState.isMuted); - onSupportedAudioModeChange(audioState.supportedRouteMask); + onAudioModeChange(audioState.getRoute(), audioState.isMuted()); + onSupportedAudioModeChange(audioState.getSupportedRouteMask()); } }; diff --git a/src/com/android/incallui/BaseFragment.java b/src/com/android/incallui/BaseFragment.java index 6c2ba216..1ef3b151 100644 --- a/src/com/android/incallui/BaseFragment.java +++ b/src/com/android/incallui/BaseFragment.java @@ -50,8 +50,22 @@ public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends } @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mPresenter.onRestoreInstanceState(savedInstanceState); + } + } + + @Override public void onDestroyView() { super.onDestroyView(); mPresenter.onUiDestroy(getUi()); } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mPresenter.onSaveInstanceState(outState); + } } diff --git a/src/com/android/incallui/Call.java b/src/com/android/incallui/Call.java index 3bac3715..debe960e 100644 --- a/src/com/android/incallui/Call.java +++ b/src/com/android/incallui/Call.java @@ -24,7 +24,6 @@ import android.hardware.camera2.CameraCharacteristics; import android.net.Uri; import android.telecom.CallProperties; import android.telecom.DisconnectCause; -import android.telecom.PhoneCapabilities; import android.telecom.GatewayInfo; import android.telecom.InCallService.VideoCall; import android.telecom.PhoneAccountHandle; @@ -395,7 +394,7 @@ public final class Call { public boolean can(int capabilities) { int supportedCapabilities = mTelecommCall.getDetails().getCallCapabilities(); - if ((capabilities & PhoneCapabilities.MERGE_CONFERENCE) != 0) { + if ((capabilities & android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) { // We allow you to merge if the capabilities allow it or if it is a call with // conferenceable calls. if (CallList.getInstance().isDsdaEnabled()) { @@ -415,18 +414,18 @@ public final class Call { } } if (!hasConfenceableCall && - ((PhoneCapabilities.MERGE_CONFERENCE & supportedCapabilities) == 0)) { + ((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) { // Cannot merge calls if there are no calls to merge with. return false; } } else if (mTelecommCall.getConferenceableCalls().isEmpty() || - ((PhoneCapabilities.MERGE_CONFERENCE & supportedCapabilities) == 0)) { + ((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) { // Cannot merge calls if there are no calls to merge with or // capability to merge is missing return false; } // Clearing this bit means this capability is available - capabilities &= ~PhoneCapabilities.MERGE_CONFERENCE; + capabilities &= ~android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE; } return (capabilities == (capabilities & mTelecommCall.getDetails().getCallCapabilities())); } @@ -603,7 +602,8 @@ public final class Call { + "VideoSettings:%s]", mId, State.toString(getState()), - PhoneCapabilities.toString(mTelecommCall.getDetails().getCallCapabilities()), + android.telecom.Call.Details + .capabilitiesToString(mTelecommCall.getDetails().getCallCapabilities()), mChildCallIds, getParentId(), mTelecommCall.getDetails().getVideoState(), mIsActiveSub, diff --git a/src/com/android/incallui/CallButtonFragment.java b/src/com/android/incallui/CallButtonFragment.java index a8d6ef5c..2c7fbe6f 100644 --- a/src/com/android/incallui/CallButtonFragment.java +++ b/src/com/android/incallui/CallButtonFragment.java @@ -19,20 +19,24 @@ package com.android.incallui; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.content.res.ColorStateList; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.RippleDrawable; +import android.graphics.drawable.StateListDrawable; import android.os.Bundle; import android.telecom.AudioState; import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.view.ContextThemeWrapper; +import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.PopupMenu; @@ -42,26 +46,29 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import java.util.ArrayList; +import com.android.contacts.common.util.MaterialColorMapUtils; +import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; + /** * Fragment for call control buttons */ public class CallButtonFragment extends BaseFragment<CallButtonPresenter, CallButtonPresenter.CallButtonUi> implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener, - View.OnClickListener, CompoundButton.OnCheckedChangeListener { + View.OnClickListener { private static final int INVALID_INDEX = -1; - private ImageButton mAudioButton; + private CompoundButton mAudioButton; private ImageButton mChangeToVoiceButton; - private ImageButton mMuteButton; - private ImageButton mShowDialpadButton; - private ImageButton mHoldButton; + private CompoundButton mMuteButton; + private CompoundButton mShowDialpadButton; + private CompoundButton mHoldButton; private ImageButton mSwapButton; private ImageButton mChangeToVideoButton; - private ImageButton mSwitchCameraButton; + private CompoundButton mSwitchCameraButton; private ImageButton mAddCallButton; private ImageButton mMergeButton; - private ImageButton mPauseVideoButton; + private CompoundButton mPauseVideoButton; private ImageButton mOverflowButton; private ImageButton mAddParticipantButton; private ImageButton mManageVideoCallConferenceButton; @@ -78,6 +85,7 @@ public class CallButtonFragment private static final int VISIBLE = 255; private boolean mIsEnabled; + private MaterialPalette mCurrentThemeColors; @Override CallButtonPresenter createPresenter() { @@ -100,27 +108,27 @@ public class CallButtonFragment Bundle savedInstanceState) { final View parent = inflater.inflate(R.layout.call_button_fragment, container, false); - mAudioButton = (ImageButton) parent.findViewById(R.id.audioButton); + mAudioButton = (CompoundButton) parent.findViewById(R.id.audioButton); mAudioButton.setOnClickListener(this); mChangeToVoiceButton = (ImageButton) parent.findViewById(R.id.changeToVoiceButton); mChangeToVoiceButton. setOnClickListener(this); - mMuteButton = (ImageButton) parent.findViewById(R.id.muteButton); + mMuteButton = (CompoundButton) parent.findViewById(R.id.muteButton); mMuteButton.setOnClickListener(this); - mShowDialpadButton = (ImageButton) parent.findViewById(R.id.dialpadButton); + mShowDialpadButton = (CompoundButton) parent.findViewById(R.id.dialpadButton); mShowDialpadButton.setOnClickListener(this); - mHoldButton = (ImageButton) parent.findViewById(R.id.holdButton); + mHoldButton = (CompoundButton) parent.findViewById(R.id.holdButton); mHoldButton.setOnClickListener(this); mSwapButton = (ImageButton) parent.findViewById(R.id.swapButton); mSwapButton.setOnClickListener(this); mChangeToVideoButton = (ImageButton) parent.findViewById(R.id.changeToVideoButton); mChangeToVideoButton.setOnClickListener(this); - mSwitchCameraButton = (ImageButton) parent.findViewById(R.id.switchCameraButton); + mSwitchCameraButton = (CompoundButton) parent.findViewById(R.id.switchCameraButton); mSwitchCameraButton.setOnClickListener(this); mAddCallButton = (ImageButton) parent.findViewById(R.id.addButton); mAddCallButton.setOnClickListener(this); mMergeButton = (ImageButton) parent.findViewById(R.id.mergeButton); mMergeButton.setOnClickListener(this); - mPauseVideoButton = (ImageButton) parent.findViewById(R.id.pauseVideoButton); + mPauseVideoButton = (CompoundButton) parent.findViewById(R.id.pauseVideoButton); mPauseVideoButton.setOnClickListener(this); mAddParticipantButton = (ImageButton) parent.findViewById(R.id.addParticipant); mAddParticipantButton.setOnClickListener(this); @@ -147,10 +155,8 @@ public class CallButtonFragment getPresenter().refreshMuteState(); } super.onResume(); - } - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + updateColors(); } @Override @@ -158,6 +164,7 @@ public class CallButtonFragment int id = view.getId(); Log.d(this, "onClick(View " + view + ", id " + id + ")..."); + boolean isClickHandled = true; switch(id) { case R.id.audioButton: onAudioButtonClicked(); @@ -169,8 +176,7 @@ public class CallButtonFragment getPresenter().displayModifyCallOptions(); break; case R.id.muteButton: { - final ImageButton button = (ImageButton) view; - getPresenter().muteClicked(!button.isSelected()); + getPresenter().muteClicked(!mMuteButton.isSelected()); break; } case R.id.mergeButton: @@ -178,8 +184,7 @@ public class CallButtonFragment mMergeButton.setEnabled(false); break; case R.id.holdButton: { - final ImageButton button = (ImageButton) view; - getPresenter().holdClicked(!button.isSelected()); + getPresenter().holdClicked(!mHoldButton.isSelected()); break; } case R.id.swapButton: @@ -209,9 +214,122 @@ public class CallButtonFragment onManageVideoCallConferenceClicked(); break; default: + isClickHandled = false; Log.wtf(this, "onClick: unexpected"); break; } + + if (isClickHandled) { + view.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + } + } + + public void updateColors() { + MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors(); + + if (mCurrentThemeColors != null && mCurrentThemeColors.equals(themeColors)) { + return; + } + + Resources res = getActivity().getResources(); + View[] compoundButtons = { + mAudioButton, + mMuteButton, + mShowDialpadButton, + mHoldButton, + mSwitchCameraButton, + mPauseVideoButton + }; + + for (View button : compoundButtons) { + final LayerDrawable layers = (LayerDrawable) button.getBackground(); + final RippleDrawable btnCompoundDrawable = compoundBackgroundDrawable(themeColors); + layers.setDrawableByLayerId(R.id.compoundBackgroundItem, btnCompoundDrawable); + } + + ImageButton[] normalButtons = { + mChangeToVoiceButton, + mSwapButton, + mChangeToVideoButton, + mAddCallButton, + mMergeButton, + mOverflowButton + }; + + for (ImageButton button : normalButtons) { + final LayerDrawable layers = (LayerDrawable) button.getBackground(); + final RippleDrawable btnDrawable = backgroundDrawable(themeColors); + layers.setDrawableByLayerId(R.id.backgroundItem, btnDrawable); + } + + mCurrentThemeColors = themeColors; + } + + /** + * Generate a RippleDrawable which will be the background for a compound button, i.e. + * a button with pressed and unpressed states. The unpressed state will be the same color + * as the rest of the call card, the pressed state will be the dark version of that color. + */ + private RippleDrawable compoundBackgroundDrawable(MaterialPalette palette) { + Resources res = getResources(); + ColorStateList rippleColor = + ColorStateList.valueOf(res.getColor(R.color.incall_accent_color)); + + StateListDrawable stateListDrawable = new StateListDrawable(); + addSelectedAndFocused(res, stateListDrawable); + addFocused(res, stateListDrawable); + addSelected(res, stateListDrawable, palette); + addUnselected(res, stateListDrawable, palette); + + return new RippleDrawable(rippleColor, stateListDrawable, null); + } + + /** + * Generate a RippleDrawable which will be the background of a button to ensure it + * is the same color as the rest of the call card. + */ + private RippleDrawable backgroundDrawable(MaterialPalette palette) { + Resources res = getResources(); + ColorStateList rippleColor = + ColorStateList.valueOf(res.getColor(R.color.incall_accent_color)); + + StateListDrawable stateListDrawable = new StateListDrawable(); + addFocused(res, stateListDrawable); + addUnselected(res, stateListDrawable, palette); + + return new RippleDrawable(rippleColor, stateListDrawable, null); + } + + // state_selected and state_focused + private void addSelectedAndFocused(Resources res, StateListDrawable drawable) { + int[] selectedAndFocused = {android.R.attr.state_selected, android.R.attr.state_focused}; + Drawable selectedAndFocusedDrawable = res.getDrawable(R.drawable.btn_selected_focused); + drawable.addState(selectedAndFocused, selectedAndFocusedDrawable); + } + + // state_focused + private void addFocused(Resources res, StateListDrawable drawable) { + int[] focused = {android.R.attr.state_focused}; + Drawable focusedDrawable = res.getDrawable(R.drawable.btn_unselected_focused); + drawable.addState(focused, focusedDrawable); + } + + // state_selected + private void addSelected(Resources res, StateListDrawable drawable, MaterialPalette palette) { + int[] selected = {android.R.attr.state_selected}; + LayerDrawable selectedDrawable = (LayerDrawable) res.getDrawable(R.drawable.btn_selected); + ((GradientDrawable) selectedDrawable.getDrawable(0)).setColor(palette.mSecondaryColor); + drawable.addState(selected, selectedDrawable); + } + + // default + private void addUnselected(Resources res, StateListDrawable drawable, MaterialPalette palette) { + LayerDrawable unselectedDrawable = + (LayerDrawable) res.getDrawable(R.drawable.btn_unselected); + ((GradientDrawable) unselectedDrawable.getDrawable(0)).setColor(palette.mPrimaryColor); + drawable.addState(new int[0], unselectedDrawable); } @Override @@ -242,8 +360,6 @@ public class CallButtonFragment public void setMute(boolean value) { if (mMuteButton.isSelected() != value) { mMuteButton.setSelected(value); - maybeSendAccessibilityEvent(mMuteButton, value ? R.string.accessibility_call_muted - : R.string.accessibility_call_unmuted); } } @@ -271,9 +387,6 @@ public class CallButtonFragment public void setHold(boolean value) { if (mHoldButton.isSelected() != value) { mHoldButton.setSelected(value); - maybeSendAccessibilityEvent(mHoldButton, - value ? R.string.accessibility_call_put_on_hold : - R.string.accessibility_call_removed_from_hold); } } @@ -298,6 +411,11 @@ public class CallButtonFragment } @Override + public void enableChangeToVideoButton(boolean enable) { + mChangeToVideoButton.setEnabled(enable); + } + + @Override public void showSwitchCameraButton(boolean show) { mSwitchCameraButton.setVisibility(show ? View.VISIBLE : View.GONE); } @@ -499,26 +617,7 @@ public class CallButtonFragment refreshAudioModePopup(); if (mPrevAudioMode != mode) { - if (mPrevAudioMode != 0) { - int stringId = 0; - switch (mode) { - case AudioState.ROUTE_EARPIECE: - stringId = R.string.accessibility_earpiece_selected; - break; - case AudioState.ROUTE_BLUETOOTH: - stringId = R.string.accessibility_bluetooth_headset_selected; - break; - case AudioState.ROUTE_WIRED_HEADSET: - stringId = R.string.accessibility_wired_headset_selected; - break; - case AudioState.ROUTE_SPEAKER: - stringId = R.string.accessibility_speakerphone_selected; - break; - } - if (stringId != 0) { - maybeSendAccessibilityEvent(mAudioButton, stringId); - } - } + updateAudioButtonContentDescription(mode); mPrevAudioMode = mode; } } @@ -572,6 +671,7 @@ public class CallButtonFragment public void onDismiss(PopupMenu menu) { Log.d(this, "- onDismiss: " + menu); mAudioModePopupVisible = false; + updateAudioButtons(getPresenter().getSupportedAudio()); } /** @@ -593,7 +693,7 @@ public class CallButtonFragment Log.d(this, "onManageVideoCallConferenceClicked"); final InCallActivity activity = (InCallActivity) getActivity(); if (activity != null) { - activity.showConferenceCallManager(); + activity.showConferenceCallManager(true); } } @@ -637,10 +737,8 @@ public class CallButtonFragment Log.d(this, "updateAudioButtons - popup menu mode"); audioButtonEnabled = true; + audioButtonChecked = true; showMoreIndicator = true; - // The audio button is NOT a toggle in this state. (And its - // setChecked() state is irrelevant since we completely hide the - // btn_compound_background layer anyway.) // Update desired layers: if (isAudio(AudioState.ROUTE_BLUETOOTH)) { @@ -654,6 +752,9 @@ public class CallButtonFragment // sort of "wired headset" icon here instead of the "handset // earpiece" icon. (Still need an asset for that, though.) } + + // The audio button is NOT a toggle in this state, so set selected to false. + mAudioButton.setSelected(false); } else if (speakerSupported) { Log.d(this, "updateAudioButtons - speaker toggle mode"); @@ -662,6 +763,7 @@ public class CallButtonFragment // The audio button *is* a toggle in this state, and indicated the // current state of the speakerphone. audioButtonChecked = isAudio(AudioState.ROUTE_SPEAKER); + mAudioButton.setSelected(audioButtonChecked); // update desired layers: showToggleIndicator = true; @@ -673,6 +775,7 @@ public class CallButtonFragment // irrelevant since it's always disabled and unchecked. audioButtonEnabled = false; audioButtonChecked = false; + mAudioButton.setSelected(false); // update desired layers: showToggleIndicator = true; @@ -690,7 +793,7 @@ public class CallButtonFragment // Only enable the audio button if the fragment is enabled. mAudioButton.setEnabled(audioButtonEnabled && mIsEnabled); - mAudioButton.setSelected(audioButtonChecked); + mAudioButton.setChecked(audioButtonChecked); final LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground(); Log.d(this, "'layers' drawable: " + layers); @@ -712,6 +815,38 @@ public class CallButtonFragment } + /** + * Update the content description of the audio button. + */ + private void updateAudioButtonContentDescription(int mode) { + int stringId = 0; + + // If bluetooth is not supported, the audio buttion will toggle, so use the label "speaker". + // Otherwise, use the label of the currently selected audio mode. + if (!isSupported(AudioState.ROUTE_BLUETOOTH)) { + stringId = R.string.audio_mode_speaker; + } else { + switch (mode) { + case AudioState.ROUTE_EARPIECE: + stringId = R.string.audio_mode_earpiece; + break; + case AudioState.ROUTE_BLUETOOTH: + stringId = R.string.audio_mode_bluetooth; + break; + case AudioState.ROUTE_WIRED_HEADSET: + stringId = R.string.audio_mode_wired_headset; + break; + case AudioState.ROUTE_SPEAKER: + stringId = R.string.audio_mode_speaker; + break; + } + } + + if (stringId != 0) { + mAudioButton.setContentDescription(getResources().getString(stringId)); + } + } + private void showAudioModePopup() { Log.d(this, "showAudioPopup()..."); @@ -791,21 +926,6 @@ public class CallButtonFragment return getActivity(); } - private void maybeSendAccessibilityEvent(View view, int stringId) { - final Context context = getActivity(); - AccessibilityManager manager = - (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - if (manager != null && manager.isEnabled()) { - AccessibilityEvent e = AccessibilityEvent.obtain(); - e.setSource(view); - e.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT); - e.setClassName(getClass().getName()); - e.setPackageName(context.getPackageName()); - e.getText().add(context.getResources().getString(stringId)); - manager.sendAccessibilityEvent(e); - } - } - private boolean isTtyModeEnabled() { return (android.provider.Settings.Secure.getInt( getContext().getContentResolver(), diff --git a/src/com/android/incallui/CallButtonPresenter.java b/src/com/android/incallui/CallButtonPresenter.java index 1beb7b26..bee02a6c 100644 --- a/src/com/android/incallui/CallButtonPresenter.java +++ b/src/com/android/incallui/CallButtonPresenter.java @@ -18,9 +18,9 @@ package com.android.incallui; import android.app.AlertDialog; import android.content.Context; +import android.os.Bundle; import android.telecom.AudioState; import android.telecom.InCallService.VideoCall; -import android.telecom.PhoneCapabilities; import android.telecom.VideoProfile; import com.android.incallui.AudioModeProvider.AudioModeListener; @@ -45,6 +45,9 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto InCallDetailsListener, CallList.ActiveSubChangeListener, CanAddCallListener, CameraSelectionListener { + private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted"; + private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state"; + private Call mCall; private boolean mAutomaticallyMuted = false; private boolean mPreviousMuteState = false; @@ -95,9 +98,10 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto // OUTGOING. We may want to do that once we start showing "Voice mail" label on // the dialpad too.) if (ui != null) { - if (oldState == InCallState.OUTGOING && mCall != null - && PhoneNumberUtils.isVoiceMailNumber(mCall.getNumber())) { - ui.displayDialpad(true /* show */, true /* animate */); + if (oldState == InCallState.OUTGOING && mCall != null) { + if (CallerInfoUtils.isVoiceMailNumber(ui.getContext(), mCall)) { + ui.displayDialpad(true /* show */, true /* animate */); + } } } } else if (newState == InCallState.INCOMING) { @@ -242,7 +246,6 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto mPreviousMuteState = AudioModeProvider.getInstance().getMute(); // Simulate a click on the mute button muteClicked(true); - TelecomAdapter.getInstance().addCall(); } @@ -350,7 +353,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto updateCallButtons(call, ui.getContext()); - ui.enableMute(call.can(PhoneCapabilities.MUTE)); + ui.enableMute(call.can(android.telecom.Call.Details.CAPABILITY_MUTE)); } private static int toInteger(boolean b) { @@ -366,28 +369,36 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto private void updateCallButtons(Call call, Context context) { if (CallUtils.isVideoCall(call)) { updateVoiceCallButtons(call); - updateVideoCallButtons(); + updateVideoCallButtons(call); } else { updateVoiceCallButtons(call); } } - private void updateVideoCallButtons() { + private void updateVideoCallButtons(Call call) { Log.v(this, "Showing buttons for video call."); final CallButtonUi ui = getUi(); // Show all video-call-related buttons. ui.showSwitchCameraButton(true); ui.showPauseVideoButton(false); + + final boolean supportHold = call.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD); + final boolean enableHoldOption = call.can(android.telecom.Call.Details.CAPABILITY_HOLD); + ui.showHoldButton(supportHold); + ui.enableHold(enableHoldOption); + ui.setHold(call.getState() == Call.State.ONHOLD); } private boolean canShowMergeOption() { CallList callList = CallList.getInstance(); Call activeCall = callList.getActiveCall(), backgroundCall = callList.getBackgroundCall(); boolean activeCallCanMerge = - (activeCall != null) && activeCall.can(PhoneCapabilities.MERGE_CONFERENCE); + (activeCall != null) && + activeCall.can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE); boolean backgroundCallCanMerge = - (backgroundCall != null) && backgroundCall.can(PhoneCapabilities.MERGE_CONFERENCE); + (backgroundCall != null) && + backgroundCall.can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE); String acId = (activeCall != null) ? activeCall.getId() : "null"; String bcId = (backgroundCall != null) ? backgroundCall.getId() : "null"; Log.v(this, "canShowMergeOption: " + acId + " " + activeCallCanMerge + @@ -408,37 +419,39 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto ui.showAudioButton(true); ui.showDialpadButton(true); - Log.v(this, "Show hold ", call.can(PhoneCapabilities.SUPPORT_HOLD)); - Log.v(this, "Enable hold", call.can(PhoneCapabilities.HOLD)); - // TODO: Every button here is calculated based on the provided call - // Is it ok that we don't pay attention to the call argument? - Log.v(this, "Show merge " + canShowMergeOption()); - - Log.v(this, "Show swap ", call.can(PhoneCapabilities.SWAP_CONFERENCE)); + Log.v(this, "Show hold ", call.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD)); + Log.v(this, "Enable hold", call.can(android.telecom.Call.Details.CAPABILITY_HOLD)); + Log.v(this, "Show merge ", call.can( + android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE)); + Log.v(this, "Show swap ", call.can( + android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE)); Log.v(this, "Show add call ", TelecomAdapter.getInstance().canAddCall()); - Log.v(this, "Show mute ", call.can(PhoneCapabilities.MUTE)); - Log.v(this, "Show video call local:", call.can(PhoneCapabilities.SUPPORTS_VT_LOCAL) - + " remote: " + call.can(PhoneCapabilities.SUPPORTS_VT_REMOTE)); + Log.v(this, "Show mute ", call.can(android.telecom.Call.Details.CAPABILITY_MUTE)); + Log.v(this, "Show video call local:", call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL) + + " remote: " + call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE)); final boolean canAdd = TelecomAdapter.getInstance().canAddCall(); - final boolean enableHoldOption = call.can(PhoneCapabilities.HOLD); - final boolean supportHold = call.can(PhoneCapabilities.SUPPORT_HOLD); + final boolean enableHoldOption = call.can(android.telecom.Call.Details.CAPABILITY_HOLD); + final boolean supportHold = call.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD); + final boolean isCallOnHold = call.getState() == Call.State.ONHOLD; - boolean canVideoCall = call.can(PhoneCapabilities.SUPPORTS_VT_LOCAL) - && call.can(PhoneCapabilities.SUPPORTS_VT_REMOTE) - && call.can(PhoneCapabilities.CALL_TYPE_MODIFIABLE); + boolean canVideoCall = call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL) + && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE); ui.showChangeToVideoButton(canVideoCall); + ui.enableChangeToVideoButton(!isCallOnHold); - final boolean showMergeOption = canShowMergeOption(); + final boolean showMergeOption = call.can( + android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE); final boolean showAddCallOption = canAdd; - final boolean showAddParticipantOption = call.can(PhoneCapabilities.ADD_PARTICIPANT); + final boolean showAddParticipantOption = call.can(android.telecom.Call.Details.ADD_PARTICIPANT); final boolean showManageVideoCallConferenceOption = call.can( - PhoneCapabilities.MANAGE_CONFERENCE) && CallUtils.isVideoCall(call); + android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && CallUtils.isVideoCall(call); // Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available: // (1) If the device normally can hold, show HOLD in a disabled state. // (2) If the device doesn't have the concept of hold/swap, remove the button. - final boolean showSwapOption = call.can(PhoneCapabilities.SWAP_CONFERENCE); + final boolean showSwapOption = call.can( + android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE); final boolean showHoldOption = !showSwapOption && (enableHoldOption || supportHold); ui.setHold(call.getState() == Call.State.ONHOLD); @@ -451,18 +464,24 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto buttonCount += toInteger(showAddParticipantOption); buttonCount += toInteger(showHoldOption); buttonCount += toInteger(showSwapOption); - buttonCount += toInteger(call.can(PhoneCapabilities.MUTE)); + buttonCount += toInteger(call.can(android.telecom.Call.Details.CAPABILITY_MUTE)); buttonCount += toInteger(showManageVideoCallConferenceOption); Log.v(this, "show AddParticipant: " + showAddParticipantOption + " show ManageVideoCallConference: " + showManageVideoCallConferenceOption); Log.v(this, "No of InCall buttons: " + buttonCount + " canVideoCall: " + canVideoCall); + ui.setHold(isCallOnHold); + // Show overflow menu if number of buttons is greater than 5. final boolean showOverflowMenu = buttonCount > BUTTON_THRESOLD_TO_DISPLAY_OVERFLOW_MENU; - final boolean isVideoOverflowScenario = canVideoCall && showOverflowMenu; - final boolean isOverflowScenario = !canVideoCall && showOverflowMenu; + // If we show video upgrade and add/merge and hold/swap, the overflow menu is needed. + final boolean isVideoOverflowScenario = canVideoCall + && (showAddCallOption || showMergeOption) && (showHoldOption || showSwapOption); + // If we show hold/swap, add, and merge simultaneously, the overflow menu is needed. + final boolean isOverflowScenario = + (showHoldOption || showSwapOption) && showMergeOption && showAddCallOption; if (isVideoOverflowScenario) { ui.showHoldButton(false); @@ -520,6 +539,22 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto mAutomaticallyMuted = false; } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(KEY_AUTOMATICALLY_MUTED, mAutomaticallyMuted); + outState.putBoolean(KEY_PREVIOUS_MUTE_STATE, mPreviousMuteState); + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + mAutomaticallyMuted = + savedInstanceState.getBoolean(KEY_AUTOMATICALLY_MUTED, mAutomaticallyMuted); + mPreviousMuteState = + savedInstanceState.getBoolean(KEY_PREVIOUS_MUTE_STATE, mPreviousMuteState); + super.onRestoreInstanceState(savedInstanceState); + } + public interface CallButtonUi extends Ui { void setEnabled(boolean on); void setMute(boolean on); @@ -532,6 +567,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto void enableHold(boolean enabled); void showSwapButton(boolean show); void showChangeToVideoButton(boolean show); + void enableChangeToVideoButton(boolean enable); void showSwitchCameraButton(boolean show); void setSwitchCameraButton(boolean isBackFacingCamera); void showAddCallButton(boolean show); diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index a1d826db..d67ae11d 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -30,6 +30,7 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.Point; +import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Bundle; @@ -42,6 +43,7 @@ import android.telecom.VideoProfile; import android.telephony.PhoneNumberUtils; import android.text.format.DateUtils; import android.text.TextUtils; +import android.text.format.DateUtils; import android.view.ContextThemeWrapper; import android.view.Display; import android.text.format.DateUtils; @@ -68,7 +70,9 @@ import android.widget.Toast; import android.telecom.AudioState; +import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; import com.android.contacts.common.widget.FloatingActionButtonController; +import com.android.incallui.service.PhoneNumberService; import com.android.internal.telephony.util.BlacklistUtils; import com.android.phone.common.animation.AnimUtils; @@ -102,6 +106,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr private TextView mElapsedTime; private ImageButton mMoreMenuButton; private MorePopupMenu mMoreMenu; + private Drawable mPrimaryPhotoDrawable; // Container view that houses the entire primary call card, including the call buttons private View mPrimaryCallCardContainer; @@ -125,7 +130,6 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr private TextView mSecondaryCallName; private View mSecondaryCallProviderInfo; private TextView mSecondaryCallProviderLabel; - private ImageView mSecondaryCallProviderIcon; private View mSecondaryCallConferenceCallIcon; private View mProgressSpinner; @@ -189,6 +193,8 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr private InCallActivity mInCallActivity; + private MaterialPalette mCurrentThemeColors; + @Override CallCardPresenter.CallCardUi getUi() { return this; @@ -326,7 +332,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr @Override public void onClick(View v) { InCallActivity activity = (InCallActivity) getActivity(); - activity.showConferenceCallManager(); + activity.showConferenceCallManager(true); } }); @@ -492,7 +498,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr if (TextUtils.isEmpty(name)) { mPrimaryName.setText(null); } else { - mPrimaryName.setText(name); + mPrimaryName.setText(nameIsNumber + ? PhoneNumberUtils.ttsSpanAsPhoneNumber(name) + : name); // Set direction of the name field int nameDirection = View.TEXT_DIRECTION_INHERIT; @@ -517,7 +525,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mPhoneNumber.setText(null); mPhoneNumber.setVisibility(View.GONE); } else { - mPhoneNumber.setText(number); + mPhoneNumber.setText(PhoneNumberUtils.ttsSpanAsPhoneNumber(number)); mPhoneNumber.setVisibility(View.VISIBLE); mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR); } @@ -545,8 +553,10 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr if (TextUtils.isEmpty(number) && TextUtils.isEmpty(label)) { mCallNumberAndLabel.setVisibility(View.GONE); + mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START); } else { mCallNumberAndLabel.setVisibility(View.VISIBLE); + mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END); } setPrimaryPhoneNumber(number); @@ -563,7 +573,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr @Override public void setSecondary(boolean show, String name, boolean nameIsNumber, String label, - String providerLabel, Drawable providerIcon, boolean isConference) { + String providerLabel, boolean isConference) { if (show != mSecondaryCallInfo.isShown()) { updateFabPositionForSecondaryCallInfo(); @@ -575,10 +585,11 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mSecondaryCallConferenceCallIcon.setVisibility(isConference ? View.VISIBLE : View.GONE); - mSecondaryCallName.setText(name); + mSecondaryCallName.setText(nameIsNumber + ? PhoneNumberUtils.ttsSpanAsPhoneNumber(name) + : name); if (hasProvider) { mSecondaryCallProviderLabel.setText(providerLabel); - mSecondaryCallProviderIcon.setImageDrawable(providerIcon); } int nameDirection = View.TEXT_DIRECTION_INHERIT; @@ -598,7 +609,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr int sessionModificationState, DisconnectCause disconnectCause, String connectionLabel, - Drawable connectionIcon, + Drawable callStateIcon, String gatewayNumber, boolean isWaitingForRemoteSide) { boolean isGatewayCall = !TextUtils.isEmpty(gatewayNumber); @@ -624,7 +635,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mCallStateLabel.setAlpha(1); mCallStateLabel.setVisibility(View.VISIBLE); - if (connectionIcon == null) { + if (callStateIcon == null) { mCallStateIcon.clearAnimation(); mCallStateIcon.setVisibility(View.GONE); } else { @@ -632,7 +643,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is // needed because the pulse animation operates on the view alpha. mCallStateIcon.setAlpha(1.0f); - mCallStateIcon.setImageDrawable(connectionIcon); + mCallStateIcon.setImageDrawable(callStateIcon); } if (VideoProfile.VideoState.isVideo(videoState) @@ -645,7 +656,6 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) { mCallStateLabel.clearAnimation(); - mCallStateIcon.clearAnimation(); } else { mCallStateLabel.startAnimation(mPulseAnimation); if (mCallStateIcon.getVisibility() == View.VISIBLE) { @@ -653,17 +663,60 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } } } else { - mCallStateLabel.clearAnimation(); + Animation callStateLabelAnimation = mCallStateLabel.getAnimation(); + if (callStateLabelAnimation != null) { + callStateLabelAnimation.cancel(); + } mCallStateLabel.setText(null); mCallStateLabel.setAlpha(0); mCallStateLabel.setVisibility(View.GONE); + } + + if (callStateIcon != null) { + mCallStateIcon.setVisibility(View.VISIBLE); + // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is + // needed because the pulse animation operates on the view alpha. + mCallStateIcon.setAlpha(1.0f); + mCallStateIcon.setImageDrawable(callStateIcon); + + if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED + || TextUtils.isEmpty(callStateLabel)) { + mCallStateIcon.clearAnimation(); + } else { + mCallStateIcon.startAnimation(mPulseAnimation); + } + + if (callStateIcon instanceof AnimationDrawable) { + ((AnimationDrawable) callStateIcon).start(); + } + } else { + Animation callStateIconAnimation = mCallStateIcon.getAnimation(); + if (callStateIconAnimation != null) { + callStateIconAnimation.cancel(); + } + // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is // needed because the pulse animation operates on the view alpha. mCallStateIcon.setAlpha(0.0f); mCallStateIcon.setVisibility(View.GONE); + } + if (VideoProfile.VideoState.isBidirectional(videoState) + || (state == Call.State.ACTIVE && sessionModificationState + == Call.SessionModificationState.WAITING_FOR_RESPONSE)) { + mCallStateVideoCallIcon.setVisibility(View.VISIBLE); + } else { mCallStateVideoCallIcon.setVisibility(View.GONE); } + + if (state == Call.State.INCOMING) { + if (callStateLabel != null) { + getView().announceForAccessibility(callStateLabel); + } + if (mPrimaryName.getText() != null) { + getView().announceForAccessibility(mPrimaryName.getText()); + } + } } @Override @@ -702,12 +755,15 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } @Override - public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) { + public void setPrimaryCallElapsedTime(boolean show, long duration) { if (show) { if (mElapsedTime.getVisibility() != View.VISIBLE) { AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION); } + String callTimeElapsed = DateUtils.formatElapsedTime(duration / 1000); + String durationDescription = InCallDateUtils.formatDetailedDuration(duration); mElapsedTime.setText(callTimeElapsed); + mElapsedTime.setContentDescription(durationDescription); } else { // hide() animation has no effect if it is already hidden. AnimUtils.fadeOut(mElapsedTime, AnimUtils.DEFAULT_DURATION); @@ -716,16 +772,24 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr private void setDrawableToImageView(ImageView view, Drawable photo) { if (photo == null) { - photo = view.getResources().getDrawable(R.drawable.img_no_image); - photo.setAutoMirrored(true); + photo = ContactInfoCache.getInstance( + view.getContext()).getDefaultContactPhotoDrawable(); } + if (mPrimaryPhotoDrawable == photo) { + return; + } + mPrimaryPhotoDrawable = photo; + final Drawable current = view.getDrawable(); if (current == null) { view.setImageDrawable(photo); AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION); } else { - InCallAnimationUtils.startCrossFade(view, current, photo); + // Cross fading is buggy and not noticable due to the multiple calls to this method + // that switch drawables in the middle of the cross-fade animations. Just set the + // photo directly instead. + view.setImageDrawable(photo); view.setVisibility(View.VISIBLE); } } @@ -838,13 +902,12 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName); mSecondaryCallConferenceCallIcon = getView().findViewById(R.id.secondaryCallConferenceCallIcon); - if (hasProvider) { - mSecondaryCallProviderInfo.setVisibility(View.VISIBLE); - mSecondaryCallProviderLabel = (TextView) getView() - .findViewById(R.id.secondaryCallProviderLabel); - mSecondaryCallProviderIcon = (ImageView) getView() - .findViewById(R.id.secondaryCallProviderIcon); - } + } + + if (mSecondaryCallProviderLabel == null && hasProvider) { + mSecondaryCallProviderInfo.setVisibility(View.VISIBLE); + mSecondaryCallProviderLabel = (TextView) getView() + .findViewById(R.id.secondaryCallProviderLabel); } } @@ -918,6 +981,22 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr return mManageConferenceCallButton.getVisibility() == View.VISIBLE; } + /** + * Get the overall InCallUI background colors and apply to call card. + */ + public void updateColors() { + MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors(); + + if (mCurrentThemeColors != null && mCurrentThemeColors.equals(themeColors)) { + return; + } + + mPrimaryCallCardContainer.setBackgroundColor(themeColors.mPrimaryColor); + mCallButtonsContainer.setBackgroundColor(themeColors.mPrimaryColor); + + mCurrentThemeColors = themeColors; + } + private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) { if (view == null) return; final List<CharSequence> eventText = event.getText(); @@ -929,9 +1008,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } } - public void animateForNewOutgoingCall(Point touchPoint) { + public void animateForNewOutgoingCall(final Point touchPoint, + final boolean showCircularReveal) { final ViewGroup parent = (ViewGroup) mPrimaryCallCardContainer.getParent(); - final Point startPoint = touchPoint; final ViewTreeObserver observer = getView().getViewTreeObserver(); @@ -962,19 +1041,16 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mCallTypeLabel.setAlpha(0); mCallNumberAndLabel.setAlpha(0); - final Animator revealAnimator = getRevealAnimator(startPoint); - final Animator shrinkAnimator = - getShrinkAnimator(parent.getHeight(), originalHeight); + final Animator animator = getOutgoingCallAnimator(touchPoint, + parent.getHeight(), originalHeight, showCircularReveal); - mAnimatorSet = new AnimatorSet(); - mAnimatorSet.playSequentially(revealAnimator, shrinkAnimator); - mAnimatorSet.addListener(new AnimatorListenerAdapter() { + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { setViewStatePostAnimation(listener); } }); - mAnimatorSet.start(); + animator.start(); } }); } @@ -1030,6 +1106,8 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr updateFabPosition(); } }); + + updateColors(); } /** @@ -1116,6 +1194,21 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr return valueAnimator; } + private Animator getOutgoingCallAnimator(Point touchPoint, int startHeight, int endHeight, + boolean showCircularReveal) { + + final Animator shrinkAnimator = getShrinkAnimator(startHeight, endHeight); + + if (!showCircularReveal) { + return shrinkAnimator; + } + + final Animator revealAnimator = getRevealAnimator(touchPoint); + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playSequentially(revealAnimator, shrinkAnimator); + return animatorSet; + } + private void assignTranslateAnimation(View view, int offset) { view.setTranslationY(mTranslationOffset * offset); view.animate().translationY(0).alpha(1).withLayer() diff --git a/src/com/android/incallui/CallCardPresenter.java b/src/com/android/incallui/CallCardPresenter.java index 39678611..07eca4ec 100644 --- a/src/com/android/incallui/CallCardPresenter.java +++ b/src/com/android/incallui/CallCardPresenter.java @@ -27,7 +27,6 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.telecom.DisconnectCause; -import android.telecom.PhoneCapabilities; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.StatusHints; @@ -37,7 +36,6 @@ import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; -import android.text.format.DateUtils; import com.android.incallui.ContactInfoCache.ContactCacheEntry; import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; @@ -76,7 +74,6 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> private ContactCacheEntry mSecondaryContactInfo; private CallTimer mCallTimer; private Context mContext; - private TelecomManager mTelecomManager; public static class ContactLookupCallback implements ContactInfoCacheCallback { private final WeakReference<CallCardPresenter> mCallCardPresenter; @@ -240,7 +237,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> } else { Log.d(this, "Canceling the calltime timer"); mCallTimer.cancel(); - ui.setPrimaryCallElapsedTime(false, null); + ui.setPrimaryCallElapsedTime(false, 0); } // Set the call state @@ -294,8 +291,10 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> public void onDetailsChanged(Call call, android.telecom.Call.Details details) { updatePrimaryCallState(); - if (call.can(PhoneCapabilities.MANAGE_CONFERENCE) != PhoneCapabilities.can( - details.getCallCapabilities(), PhoneCapabilities.MANAGE_CONFERENCE)) { + if (call.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) != + android.telecom.Call.Details.can( + details.getCallCapabilities(), + android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)) { maybeShowManageConferenceCallButton(); } } @@ -306,8 +305,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> // number directly from the telephony layer). PhoneAccountHandle accountHandle = mPrimary.getAccountHandle(); if (accountHandle != null) { - TelecomManager mgr = - (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); + TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager(); PhoneAccount account = mgr.getPhoneAccount(accountHandle); if (account != null) { return getNumberFromHandle(account.getSubscriptionAddress()); @@ -324,7 +322,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> mPrimary.getSessionModificationState(), mPrimary.getDisconnectCause(), getConnectionLabel(), - getConnectionIcon(), + getCallStateIcon(), getGatewayNumber(), mPrimary.isWaitingForRemoteSide()); setCallbackNumber(); @@ -349,7 +347,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> return false; } - return mPrimary.can(PhoneCapabilities.MANAGE_CONFERENCE) && !mPrimary.isVideoCall(mContext); + return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && + !mPrimary.isVideoCall(mContext); } private void setCallbackNumber() { @@ -369,9 +368,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> } } - TelephonyManager telephonyManager = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - String simNumber = telephonyManager.getLine1Number(); + TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager(); + String simNumber = mgr.getLine1Number(mPrimary.getAccountHandle()); if (PhoneNumberUtils.compare(callbackNumber, simNumber)) { Log.d(this, "Numbers are the same; not showing the callback number"); callbackNumber = null; @@ -385,13 +383,13 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> if (ui == null || mPrimary == null || mPrimary.getState() != Call.State.ACTIVE) { if (ui != null) { - ui.setPrimaryCallElapsedTime(false, null); + ui.setPrimaryCallElapsedTime(false, 0); } mCallTimer.cancel(); } else { final long callStart = mPrimary.getConnectTimeMillis(); final long duration = System.currentTimeMillis() - callStart; - ui.setPrimaryCallElapsedTime(true, DateUtils.formatElapsedTime(duration / 1000)); + ui.setPrimaryCallElapsedTime(true, duration); } } @@ -598,7 +596,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> if (mSecondary == null) { // Clear the secondary display info. - ui.setSecondary(false, null, false, null, null, null, false /* isConference */); + ui.setSecondary(false, null, false, null, null, false /* isConference */); return; } @@ -609,7 +607,6 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> false /* nameIsNumber */, null /* label */, getCallProviderLabel(mSecondary), - getCallProviderIcon(mSecondary), true /* isConference */); } else if (mSecondaryContactInfo != null) { Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo); @@ -621,11 +618,10 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> nameIsNumber, mSecondaryContactInfo.label, getCallProviderLabel(mSecondary), - getCallProviderIcon(mSecondary), false /* isConference */); } else { // Clear the secondary display info. - ui.setSecondary(false, null, false, null, null, null, false /* isConference */); + ui.setSecondary(false, null, false, null, null, false /* isConference */); } } @@ -638,7 +634,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> if (accountHandle == null) { return null; } - return getTelecomManager().getPhoneAccount(accountHandle); + return InCallPresenter.getInstance().getTelecomManager().getPhoneAccount(accountHandle); } /** @@ -652,30 +648,13 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> } /** - * Return the Drawable object of the icon to display to the left of the connection label. - */ - private Drawable getCallProviderIcon(Call call) { - PhoneAccount account = getAccountForCall(call); - - // on MSIM devices irrespective of number of enabled phone - // accounts pick icon from phone account and display on UI - if (account != null && (getTelecomManager().hasMultipleCallCapableAccounts() - || (CallList.PHONE_COUNT > 1))) { - return account.createIconDrawable(mContext); - } - return null; - } - - /** * Return the string label to represent the call provider */ private String getCallProviderLabel(Call call) { PhoneAccount account = getAccountForCall(call); - - // on MSIM devices irrespective of number of - // enabled phone accounts display label info on UI - if (account != null && (getTelecomManager().hasMultipleCallCapableAccounts() - || (CallList.PHONE_COUNT > 1))) { + TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager(); + if (account != null && !TextUtils.isEmpty(account.getLabel()) + && mgr.hasMultipleCallCapableAccounts()) { return account.getLabel().toString(); } return null; @@ -706,7 +685,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> return getCallProviderLabel(mPrimary); } - private Drawable getConnectionIcon() { + private Drawable getCallStateIcon() { + // Return connection icon if one exists. StatusHints statusHints = mPrimary.getTelecommCall().getDetails().getStatusHints(); if (statusHints != null && statusHints.getIconResId() != 0) { Drawable icon = statusHints.getIcon(mContext); @@ -714,7 +694,15 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> return icon; } } - return getCallProviderIcon(mPrimary); + + // Return high definition audio icon if the capability is indicated. + if (mPrimary.getTelecommCall().getDetails().can( + android.telecom.Call.Details.CAPABILITY_HIGH_DEF_AUDIO) + && mPrimary.getState() == Call.State.ACTIVE) { + return mContext.getResources().getDrawable(R.drawable.ic_hd_audio); + } + + return null; } private boolean hasOutgoingGatewayCall() { @@ -754,7 +742,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> public void secondaryInfoClicked() { if (mSecondary == null) { - Log.wtf(this, "Secondary info clicked but no secondary call."); + Log.w(this, "Secondary info clicked but no secondary call."); return; } @@ -791,16 +779,9 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> ui.setCallCardVisible(!isFullScreenVideo); } - private TelecomManager getTelecomManager() { - if (mTelecomManager == null) { - mTelecomManager = - (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); - } - return mTelecomManager; - } - private String getConferenceString(Call call) { - boolean isGenericConference = call.can(PhoneCapabilities.GENERIC_CONFERENCE); + boolean isGenericConference = call.can( + android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE); Log.v(this, "getConferenceString: " + isGenericConference); final int resId = isGenericConference @@ -809,7 +790,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> } private Drawable getConferencePhoto(Call call) { - boolean isGenericConference = call.can(PhoneCapabilities.GENERIC_CONFERENCE); + boolean isGenericConference = call.can( + android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE); Log.v(this, "getConferencePhoto: " + isGenericConference); final int resId = isGenericConference @@ -850,11 +832,11 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> String label, Drawable photo, boolean isSipCall, String nickName, String organization, String position, String city); void setSecondary(boolean show, String name, boolean nameIsNumber, String label, - String providerLabel, Drawable providerIcon, boolean isConference); + String providerLabel, boolean isConference); void setCallState(int state, int videoState, int sessionModificationState, DisconnectCause disconnectCause, String connectionLabel, Drawable connectionIcon, String gatewayNumber, boolean isWaitingForRemoteSide); - void setPrimaryCallElapsedTime(boolean show, String duration); + void setPrimaryCallElapsedTime(boolean show, long duration); void setPrimaryName(String name, boolean nameIsNumber); void setPrimaryImage(Drawable image); void setPrimaryPhoneNumber(String phoneNumber); diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java index cee69f52..50313035 100644 --- a/src/com/android/incallui/CallList.java +++ b/src/com/android/incallui/CallList.java @@ -99,8 +99,6 @@ public class CallList implements InCallPhoneListener { public void onCallRemoved(Phone phone, android.telecom.Call telecommCall) { if (mCallByTelecommCall.containsKey(telecommCall)) { Call call = mCallByTelecommCall.get(telecommCall); - call.setState(Call.State.DISCONNECTED); - call.setDisconnectCause(new DisconnectCause(DisconnectCause.UNKNOWN)); if (updateCallInMap(call)) { Log.w(this, "Removing call not previously disconnected " + call.getId()); } @@ -485,7 +483,6 @@ public class CallList implements InCallPhoneListener { if (call.getState() == Call.State.DISCONNECTED) { // update existing (but do not add!!) disconnected calls if (mCallById.containsKey(call.getId())) { - // For disconnected calls, we want to keep them alive for a few seconds so that the // UI has a chance to display anything it needs when a call is disconnected. diff --git a/src/com/android/incallui/CallerInfo.java b/src/com/android/incallui/CallerInfo.java index c7940691..50dc7770 100644 --- a/src/com/android/incallui/CallerInfo.java +++ b/src/com/android/incallui/CallerInfo.java @@ -328,16 +328,10 @@ public class CallerInfo { * @param context To lookup the localized 'Emergency Number' string. * @return this instance. */ - // TODO: Note we're setting the phone number here (refer to - // javadoc comments at the top of CallerInfo class) to a localized - // string 'Emergency Number'. This is pretty bad because we are - // making UI work here instead of just packaging the data. We - // should set the phone number to the dialed number and name to - // 'Emergency Number' and let the UI make the decision about what - // should be displayed. - /* package */ CallerInfo markAsEmergency(Context context, String number) { - phoneNumber = context.getString(R.string.emergency_call_dialog_number_for_display) + " " - + number; + /* package */ CallerInfo markAsEmergency(Context context) { + name = context.getString(R.string.emergency_call_dialog_number_for_display); + phoneNumber = null; + photoResource = R.drawable.img_phone; mIsEmergency = true; return this; @@ -351,8 +345,6 @@ public class CallerInfo { * set to null. * @return this instance. */ - // TODO: As in the emergency number handling, we end up writing a - // string in the phone number field. /* package */ CallerInfo markAsVoiceMail(Context context) { mIsVoiceMail = true; @@ -360,7 +352,8 @@ public class CallerInfo { // For voicemail calls, we display the voice mail tag // instead of the real phone number in the "number" // field. - phoneNumber = TelephonyManagerUtils.getVoiceMailAlphaTag(context); + name = TelephonyManagerUtils.getVoiceMailAlphaTag(context); + phoneNumber = null; } catch (SecurityException se) { // Should never happen: if this process does not have // permission to retrieve VM tag, it should not have diff --git a/src/com/android/incallui/CallerInfoAsyncQuery.java b/src/com/android/incallui/CallerInfoAsyncQuery.java index 8c5abe05..87174d71 100644 --- a/src/com/android/incallui/CallerInfoAsyncQuery.java +++ b/src/com/android/incallui/CallerInfoAsyncQuery.java @@ -250,7 +250,7 @@ public class CallerInfoAsyncQuery { if (cw.event == EVENT_EMERGENCY_NUMBER) { // Note we're setting the phone number here (refer to javadoc // comments at the top of CallerInfo class). - mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext, cw.number); + mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext); } else if (cw.event == EVENT_VOICEMAIL_NUMBER) { mCallerInfo = new CallerInfo().markAsVoiceMail(mQueryContext); } else { @@ -371,8 +371,7 @@ public class CallerInfoAsyncQuery { // check to see if these are recognized numbers, and use shortcuts if we can. if (PhoneNumberUtils.isLocalEmergencyNumber(context, info.phoneNumber)) { cw.event = EVENT_EMERGENCY_NUMBER; - } else if (info.isVoiceMailNumber() - || PhoneNumberUtils.isVoiceMailNumber(subId, info.phoneNumber)) { + } else if (info.isVoiceMailNumber()) { cw.event = EVENT_VOICEMAIL_NUMBER; } else { cw.event = EVENT_NEW_QUERY; diff --git a/src/com/android/incallui/CallerInfoUtils.java b/src/com/android/incallui/CallerInfoUtils.java index 9fd45e41..5693db09 100644 --- a/src/com/android/incallui/CallerInfoUtils.java +++ b/src/com/android/incallui/CallerInfoUtils.java @@ -6,6 +6,7 @@ import android.content.Loader.OnLoadCompleteListener; import android.net.Uri; import android.telecom.PhoneAccount; import android.telecom.TelecomManager; +import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.util.Log; @@ -74,14 +75,22 @@ public class CallerInfoUtils { // Because the InCallUI is immediately launched before the call is connected, occasionally // a voicemail call will be passed to InCallUI as a "voicemail:" URI without a number. // This call should still be handled as a voicemail call. - if (call.getHandle() != null && - PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())) { + if ((call.getHandle() != null && + PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())) || + isVoiceMailNumber(context, call)) { info.markAsVoiceMail(context); } return info; } + public static boolean isVoiceMailNumber(Context context, Call call) { + TelecomManager telecomManager = + (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + return telecomManager.isVoiceMailNumber( + call.getTelecommCall().getDetails().getAccountHandle(), call.getNumber()); + } + /** * Handles certain "corner cases" for CNAP. When we receive weird phone numbers * from the network to indicate different number presentations, convert them to diff --git a/src/com/android/incallui/CircularRevealActivity.java b/src/com/android/incallui/CircularRevealActivity.java new file mode 100644 index 00000000..7a9b7ccb --- /dev/null +++ b/src/com/android/incallui/CircularRevealActivity.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.incallui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Outline; +import android.graphics.Point; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; +import android.view.Display; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewOutlineProvider; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnPreDrawListener; + +import com.android.contacts.common.interactions.TouchPointManager; +import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; + +/** + * Lightweight activity used to display a circular reveal while InCallActivity is starting up. + * A BroadcastReceiver is used to listen to broadcasts from a LocalBroadcastManager to finish + * the activity at suitable times. + */ +public class CircularRevealActivity extends Activity { + private static final int REVEAL_DURATION = 333; + public static final String EXTRA_THEME_COLORS = "extra_theme_colors"; + public static final String ACTION_CLEAR_DISPLAY = "action_clear_display"; + + final BroadcastReceiver mClearDisplayReceiver = new BroadcastReceiver( ) { + @Override + public void onReceive(Context context, Intent intent) { + clearDisplay(); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + overridePendingTransition(0, 0); + setContentView(R.layout.outgoing_call_animation); + final Point touchPoint = getIntent().getParcelableExtra(TouchPointManager.TOUCH_POINT); + final MaterialPalette palette = getIntent().getParcelableExtra(EXTRA_THEME_COLORS); + setupDecorView(touchPoint, palette); + } + + @Override + protected void onStart() { + super.onStart(); + if (!InCallPresenter.getInstance().isServiceBound()) { + clearDisplay(); + } + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_CLEAR_DISPLAY); + LocalBroadcastManager.getInstance(this).registerReceiver(mClearDisplayReceiver, filter); + } + + @Override + protected void onStop() { + LocalBroadcastManager.getInstance(this).unregisterReceiver(mClearDisplayReceiver); + super.onStop(); + } + + private void setupDecorView(final Point touchPoint, MaterialPalette palette) { + final View view = getWindow().getDecorView(); + + // The circle starts from an initial size of 0 so clip it such that it is invisible. When + // the animation later starts, this clip will be clobbered by the circular reveal clip. + // See ViewAnimationUtils.createCircularReveal. + view.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + // Using (0, 0, 0, 0) will not work since the outline will simply be treated as + // an empty outline. + outline.setOval(-1, -1, 0, 0); + } + }); + view.setClipToOutline(true); + + if (palette != null) { + view.findViewById(R.id.outgoing_call_animation_circle).setBackgroundColor( + palette.mPrimaryColor); + getWindow().setStatusBarColor(palette.mSecondaryColor); + } + + view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + final ViewTreeObserver vto = view.getViewTreeObserver(); + if (vto.isAlive()) { + vto.removeOnPreDrawListener(this); + } + final Animator animator = getRevealAnimator(touchPoint); + // Since this animator is a RenderNodeAnimator (native animator), add an arbitary + // start delay to force the onAnimationStart callback to happen later on the UI + // thread. Otherwise it would happen right away inside animator.start() + animator.setStartDelay(5); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + InCallPresenter.getInstance().onCircularRevealStarted( + CircularRevealActivity.this); + } + + @Override + public void onAnimationEnd(Animator animation) { + view.setClipToOutline(false); + super.onAnimationEnd(animation); + } + }); + animator.start(); + return false; + } + }); + } + + private void clearDisplay() { + getWindow().getDecorView().setVisibility(View.INVISIBLE); + finish(); + } + + @Override + public void onBackPressed() { + return; + } + + public static void sendClearDisplayBroadcast(Context context) { + LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_CLEAR_DISPLAY)); + } + + private Animator getRevealAnimator(Point touchPoint) { + final View view = getWindow().getDecorView(); + final Display display = getWindowManager().getDefaultDisplay(); + final Point size = new Point(); + display.getSize(size); + + int startX = size.x / 2; + int startY = size.y / 2; + if (touchPoint != null) { + startX = touchPoint.x; + startY = touchPoint.y; + } + + final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view, + startX, startY, 0, Math.max(size.x, size.y)); + valueAnimator.setDuration(REVEAL_DURATION); + return valueAnimator; + } +} diff --git a/src/com/android/incallui/ConferenceManagerFragment.java b/src/com/android/incallui/ConferenceManagerFragment.java index efdea28b..72182867 100644 --- a/src/com/android/incallui/ConferenceManagerFragment.java +++ b/src/com/android/incallui/ConferenceManagerFragment.java @@ -94,6 +94,9 @@ public class ConferenceManagerFragment final CallList calls = CallList.getInstance(); getPresenter().init(getActivity(), calls); getView().setVisibility(View.VISIBLE); + // Request focus on the list of participants for accessibility purposes. This ensures + // that once the list of participants is shown, the first participant is announced. + mConferenceParticipantList.requestFocus(); } else { getView().setVisibility(View.GONE); diff --git a/src/com/android/incallui/ConferenceManagerPresenter.java b/src/com/android/incallui/ConferenceManagerPresenter.java index 7acd94e3..8c41424a 100644 --- a/src/com/android/incallui/ConferenceManagerPresenter.java +++ b/src/com/android/incallui/ConferenceManagerPresenter.java @@ -17,14 +17,12 @@ package com.android.incallui; import android.content.Context; -import android.net.Uri; -import android.telecom.PhoneCapabilities; -import android.text.TextUtils; import com.android.incallui.ContactInfoCache.ContactCacheEntry; import com.android.incallui.InCallPresenter.InCallDetailsListener; import com.android.incallui.InCallPresenter.InCallState; import com.android.incallui.InCallPresenter.InCallStateListener; +import com.android.incallui.InCallPresenter.IncomingCallListener; import com.google.common.base.Preconditions; @@ -36,7 +34,7 @@ import java.util.List; */ public class ConferenceManagerPresenter extends Presenter<ConferenceManagerPresenter.ConferenceManagerUi> - implements InCallStateListener, InCallDetailsListener { + implements InCallStateListener, InCallDetailsListener, IncomingCallListener { private Context mContext; @@ -46,6 +44,7 @@ public class ConferenceManagerPresenter // register for call state changes last InCallPresenter.getInstance().addListener(this); + InCallPresenter.getInstance().addIncomingCallListener(this); } @Override @@ -53,6 +52,7 @@ public class ConferenceManagerPresenter super.onUiUnready(ui); InCallPresenter.getInstance().removeListener(this); + InCallPresenter.getInstance().removeIncomingCallListener(this); } @Override @@ -66,29 +66,40 @@ public class ConferenceManagerPresenter String.valueOf(call.getChildCallIds().size())); update(callList); } else { - getUi().setVisible(false); + InCallPresenter.getInstance().showConferenceCallManager(false); } } else { - getUi().setVisible(false); + InCallPresenter.getInstance().showConferenceCallManager(false); } } } @Override public void onDetailsChanged(Call call, android.telecom.Call.Details details) { - boolean canDisconnect = PhoneCapabilities.can( - details.getCallCapabilities(), PhoneCapabilities.DISCONNECT_FROM_CONFERENCE); - boolean canSeparate = PhoneCapabilities.can( - details.getCallCapabilities(), PhoneCapabilities.SEPARATE_FROM_CONFERENCE); - - if (call.can(PhoneCapabilities.DISCONNECT_FROM_CONFERENCE) != canDisconnect - || call.can(PhoneCapabilities.SEPARATE_FROM_CONFERENCE) != canSeparate) { + boolean canDisconnect = details.can( + android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE); + boolean canSeparate = details.can( + android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE); + + if (call.can(android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE) + != canDisconnect + || call.can(android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE) + != canSeparate) { getUi().refreshCall(call); } - if (!PhoneCapabilities.can( - details.getCallCapabilities(), PhoneCapabilities.MANAGE_CONFERENCE)) { - getUi().setVisible(false); + if (!details.can( + android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)) { + InCallPresenter.getInstance().showConferenceCallManager(false); + } + } + + @Override + public void onIncomingCall(InCallState oldState, InCallState newState, Call call) { + // When incoming call exists, set conference ui invisible. + if (getUi().isFragmentVisible()) { + Log.d(this, "onIncomingCall()... Conference ui is showing, hide it."); + InCallPresenter.getInstance().showConferenceCallManager(false); } } @@ -117,9 +128,8 @@ public class ConferenceManagerPresenter Log.d(this, "Number of calls is " + String.valueOf(calls.size())); - // Users can split out a call from the conference call if there either the active call - // or the holding call is empty. If both are filled at the moment, users can not split out - // another call. + // Users can split out a call from the conference call if either the active call or the + // holding call is empty. If both are filled, users can not split out another call. final boolean hasActiveCall = (callList.getActiveCall() != null); final boolean hasHoldingCall = (callList.getBackgroundCall() != null); boolean canSeparate = !(hasActiveCall && hasHoldingCall); diff --git a/src/com/android/incallui/ConferenceParticipantListAdapter.java b/src/com/android/incallui/ConferenceParticipantListAdapter.java index 641261e0..f03b9f05 100644 --- a/src/com/android/incallui/ConferenceParticipantListAdapter.java +++ b/src/com/android/incallui/ConferenceParticipantListAdapter.java @@ -19,6 +19,7 @@ package com.android.incallui; import android.content.Context; import android.net.Uri; import android.telecom.PhoneCapabilities; +import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -336,11 +337,10 @@ public class ConferenceParticipantListAdapter extends BaseAdapter { new ContactLookupCallback(this)); } - int callCapabilities = call.getTelecommCall().getDetails().getCallCapabilities(); - boolean thisRowCanSeparate = mParentCanSeparate && PhoneCapabilities.can( - callCapabilities, PhoneCapabilities.SEPARATE_FROM_CONFERENCE); - boolean thisRowCanDisconnect = PhoneCapabilities.can( - callCapabilities, PhoneCapabilities.DISCONNECT_FROM_CONFERENCE); + boolean thisRowCanSeparate = mParentCanSeparate && call.getTelecommCall().getDetails().can( + android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE); + boolean thisRowCanDisconnect = call.getTelecommCall().getDetails().can( + android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE); setCallerInfoForRow(result, contactCache.name, contactCache.number, contactCache.label, contactCache.lookupKey, contactCache.displayPhotoUri, thisRowCanSeparate, @@ -420,7 +420,7 @@ public class ConferenceParticipantListAdapter extends BaseAdapter { numberTypeTextView.setVisibility(View.GONE); } else { numberTextView.setVisibility(View.VISIBLE); - numberTextView.setText(callerNumber); + numberTextView.setText(PhoneNumberUtils.ttsSpanAsPhoneNumber(callerNumber)); numberTypeTextView.setVisibility(View.VISIBLE); numberTypeTextView.setText(callerNumberType); } diff --git a/src/com/android/incallui/ContactInfoCache.java b/src/com/android/incallui/ContactInfoCache.java index 0570ee2d..b953099b 100644 --- a/src/com/android/incallui/ContactInfoCache.java +++ b/src/com/android/incallui/ContactInfoCache.java @@ -70,6 +70,9 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete private static ContactInfoCache sCache = null; + private Drawable mDefaultContactPhotoDrawable; + private Drawable mConferencePhotoDrawable; + public static synchronized ContactInfoCache getInstance(Context mContext) { if (sCache == null) { sCache = new ContactInfoCache(mContext.getApplicationContext()); @@ -170,12 +173,10 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete } ContactCacheEntry cacheEntry = mInfoMap.get(callId); - // Rebuild the entry from the new data if: - // 1) This is NOT the asynchronous local lookup (IOW, this is the first pass) - // 2) The local lookup was done and the contact exists - // 3) The existing cached entry is empty (no name). - if (!didLocalLookup || callerInfo.contactExists || - (cacheEntry != null && TextUtils.isEmpty(cacheEntry.name))) { + // Ensure we always have a cacheEntry. Replace the existing entry if + // it has no name or if we found a local contact. + if (cacheEntry == null || TextUtils.isEmpty(cacheEntry.name) || + callerInfo.contactExists) { cacheEntry = buildEntry(mContext, callId, callerInfo, presentationMode, isIncoming); mInfoMap.put(callId, cacheEntry); } @@ -183,8 +184,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete sendInfoNotifications(callId, cacheEntry); if (didLocalLookup) { - - // Before issuing a request for more data from other services, We only check that the + // Before issuing a request for more data from other services, we only check that the // contact wasn't found in the local DB. We don't check the if the cache entry already // has a name because we allow overriding cnap data with data from other services. if (!callerInfo.contactExists && cacheEntry.name == null) { @@ -334,12 +334,10 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete if (info.cachedPhoto != null) { photo = info.cachedPhoto; } else { - photo = context.getResources().getDrawable(R.drawable.img_no_image); - photo.setAutoMirrored(true); + photo = getDefaultContactPhotoDrawable(); } } else if (info.contactDisplayPhotoUri == null) { - photo = context.getResources().getDrawable(R.drawable.img_no_image); - photo.setAutoMirrored(true); + photo = getDefaultContactPhotoDrawable(); } else { cce.displayPhotoUri = info.contactDisplayPhotoUri; } @@ -513,6 +511,22 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete return name; } + public Drawable getDefaultContactPhotoDrawable() { + if (mDefaultContactPhotoDrawable == null) { + mDefaultContactPhotoDrawable = + mContext.getResources().getDrawable(R.drawable.img_no_image_automirrored); + } + return mDefaultContactPhotoDrawable; + } + + public Drawable getConferenceDrawable() { + if (mConferencePhotoDrawable == null) { + mConferencePhotoDrawable = + mContext.getResources().getDrawable(R.drawable.img_conference_automirrored); + } + return mConferencePhotoDrawable; + } + /** * Callback interface for the contact query. */ diff --git a/src/com/android/incallui/DialpadFragment.java b/src/com/android/incallui/DialpadFragment.java index 6cb5d1f8..da3f0cb7 100644 --- a/src/com/android/incallui/DialpadFragment.java +++ b/src/com/android/incallui/DialpadFragment.java @@ -20,6 +20,7 @@ import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.telephony.PhoneNumberUtils; import android.text.Editable; import android.text.method.DialerKeyListener; import android.util.AttributeSet; @@ -34,6 +35,7 @@ import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; import com.android.phone.common.dialpad.DialpadKeyButton; import com.android.phone.common.dialpad.DialpadView; @@ -48,6 +50,10 @@ public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadPrese private static final int ACCESSIBILITY_DTMF_STOP_DELAY_MILLIS = 50; + private final int[] mButtonIds = new int[] {R.id.zero, R.id.one, R.id.two, R.id.three, + R.id.four, R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, + R.id.pound}; + /** * LinearLayout with getter and setter methods for the translationY property using floats, * for animation purposes. @@ -132,6 +138,8 @@ public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadPrese private DialpadView mDialpadView; + private int mCurrentTextColor; + /** * Our own key listener, specialized for dealing with DTMF codes. * 1. Ignore the backspace since it is irrelevant. @@ -457,13 +465,35 @@ public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadPrese // the edit (copy / paste / select) functions. mDtmfDialerField.setLongClickable(false); mDtmfDialerField.setElegantTextHeight(false); - configureKeypadListeners(mDialpadView); + configureKeypadListeners(); } return parent; } @Override + public void onResume() { + super.onResume(); + updateColors(); + } + + public void updateColors() { + int textColor = InCallPresenter.getInstance().getThemeColors().mPrimaryColor; + + if (mCurrentTextColor == textColor) { + return; + } + + DialpadKeyButton dialpadKey; + for (int i = 0; i < mButtonIds.length; i++) { + dialpadKey = (DialpadKeyButton) mDialpadView.findViewById(mButtonIds[i]); + ((TextView) dialpadKey.findViewById(R.id.dialpad_key_number)).setTextColor(textColor); + } + + mCurrentTextColor = textColor; + } + + @Override public void onDestroyView() { mDialerKeyListener = null; super.onDestroyView(); @@ -484,7 +514,7 @@ public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadPrese * @param text Text to set Dialpad EditText to. */ public void setDtmfText(String text) { - mDtmfDialerField.setText(text); + mDtmfDialerField.setText(PhoneNumberUtils.ttsSpanAsPhoneNumber(text)); } @Override @@ -544,12 +574,10 @@ public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadPrese } } - private void configureKeypadListeners(View fragmentView) { - final int[] buttonIds = new int[] {R.id.zero, R.id.one, R.id.two, R.id.three, R.id.four, - R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.pound}; + private void configureKeypadListeners() { DialpadKeyButton dialpadKey; - for (int i = 0; i < buttonIds.length; i++) { - dialpadKey = (DialpadKeyButton) fragmentView.findViewById(buttonIds[i]); + for (int i = 0; i < mButtonIds.length; i++) { + dialpadKey = (DialpadKeyButton) mDialpadView.findViewById(mButtonIds[i]); dialpadKey.setOnTouchListener(this); dialpadKey.setOnKeyListener(this); dialpadKey.setOnHoverListener(this); diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index c37d8080..fc70f6c5 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -34,12 +34,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.TypedArray; +import android.content.res.Resources; import android.graphics.Point; import android.net.Uri; import android.os.Bundle; import android.telecom.DisconnectCause; -import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -57,6 +58,10 @@ import android.widget.TextView; import com.android.phone.common.animation.AnimUtils; import com.android.phone.common.animation.AnimationListenerAdapter; import com.android.contacts.common.interactions.TouchPointManager; +import com.android.contacts.common.util.MaterialColorMapUtils; +import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; +import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; +import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; import com.android.incallui.Call.State; import java.util.ArrayList; @@ -70,11 +75,12 @@ public class InCallActivity extends Activity { public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad"; public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text"; - public static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call"; private static final String ACTION_SUPP_SERVICE_FAILURE = "org.codeaurora.ACTION_SUPP_SERVICE_FAILURE"; private static final String EXTRA_RESULT = "result"; + public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call"; + public static final String SHOW_CIRCULAR_REVEAL_EXTRA = "InCallActivity.show_circular_reveal"; private CallButtonFragment mCallButtonFragment; private CallCardFragment mCallCardFragment; @@ -110,6 +116,7 @@ public class InCallActivity extends Activity { private Tab[] mDsdaTab = new Tab[TAB_COUNT_TWO]; private boolean[] mDsdaTabAdd = {false, false}; + private boolean mDismissKeyguard = false; AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() { @Override @@ -216,6 +223,7 @@ public class InCallActivity extends Activity { if (mDialpadFragment != null) { out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText()); } + super.onSaveInstanceState(out); } @Override @@ -238,6 +246,8 @@ public class InCallActivity extends Activity { super.onResume(); mIsForegroundActivity = true; + + InCallPresenter.getInstance().setThemeColors(); InCallPresenter.getInstance().onUiShowing(true); if (mShowDialpadRequested) { @@ -271,6 +281,9 @@ public class InCallActivity extends Activity { } InCallPresenter.getInstance().onUiShowing(false); + if (isFinishing()) { + InCallPresenter.getInstance().unsetActivity(this); + } } @Override @@ -285,10 +298,7 @@ public class InCallActivity extends Activity { @Override protected void onDestroy() { Log.d(this, "onDestroy()... this = " + this); - - InCallPresenter.getInstance().updateIsChangingConfigurations(); - InCallPresenter.getInstance().setActivity(null); - unregisterReceiver(mReceiver); + InCallPresenter.getInstance().unsetActivity(this); super.onDestroy(); } @@ -351,12 +361,12 @@ public class InCallActivity extends Activity { @Override public void onBackPressed() { - Log.d(this, "onBackPressed()..."); + Log.i(this, "onBackPressed"); // BACK is also used to exit out of any "special modes" of the // in-call UI: - if (!mCallCardFragment.isVisible()) { + if (!mConferenceManagerFragment.isVisible() && !mCallCardFragment.isVisible()) { return; } @@ -365,7 +375,7 @@ public class InCallActivity extends Activity { mCallButtonFragment.getPresenter().showDialpadClicked(false); return; } else if (mConferenceManagerFragment.isVisible()) { - mConferenceManagerFragment.setVisible(false); + showConferenceCallManager(false); return; } @@ -531,8 +541,8 @@ public class InCallActivity extends Activity { relaunchedFromDialer(showDialpad); } - if (intent.getBooleanExtra(NEW_OUTGOING_CALL, false)) { - intent.removeExtra(NEW_OUTGOING_CALL); + if (intent.getBooleanExtra(NEW_OUTGOING_CALL_EXTRA, false)) { + intent.removeExtra(NEW_OUTGOING_CALL_EXTRA); Call call = CallList.getInstance().getOutgoingCall(); if (call == null) { call = CallList.getInstance().getPendingOutgoingCall(); @@ -547,7 +557,6 @@ public class InCallActivity extends Activity { extras = new Bundle(); } - Point touchPoint = null; if (TouchPointManager.getInstance().hasValidPoint()) { // Use the most immediate touch point in the InCallUi if available @@ -558,23 +567,21 @@ public class InCallActivity extends Activity { touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT); } } - mCallCardFragment.animateForNewOutgoingCall(touchPoint); - - /* - * If both a phone account handle and a list of phone accounts to choose from are - * missing, then disconnect the call because there is no way to place an outgoing - * call. - * The exception is emergency calls, which may be waiting for the ConnectionService - * to set the PhoneAccount during the PENDING_OUTGOING state. - */ - if (call != null && !isEmergencyCall(call)) { - final List<PhoneAccountHandle> phoneAccountHandles = extras - .getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); - if (call.getAccountHandle() == null && - (phoneAccountHandles == null || phoneAccountHandles.isEmpty())) { - TelecomAdapter.getInstance().disconnectCall(call.getId()); - } + + // This is only true in the case where an outgoing call is initiated by tapping + // on the "Select account dialog", in which case we skip the initial animation. In + // most other cases the circular reveal is done by OutgoingCallAnimationActivity. + final boolean showCircularReveal = + intent.getBooleanExtra(SHOW_CIRCULAR_REVEAL_EXTRA, false); + mCallCardFragment.animateForNewOutgoingCall(touchPoint, showCircularReveal); + + // InCallActivity is responsible for disconnecting a new outgoing call if there + // is no way of making it (i.e. no valid call capable accounts) + if (InCallPresenter.isCallWithNoValidAccounts(call)) { + TelecomAdapter.getInstance().disconnectCall(call.getId()); } + + dismissKeyguard(true); } Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall(); @@ -591,8 +598,22 @@ public class InCallActivity extends Activity { phoneAccountHandles = new ArrayList<>(); } + SelectPhoneAccountListener listener = new SelectPhoneAccountListener() { + @Override + public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle, + boolean setDefault) { + InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle, + setDefault); + } + @Override + public void onDialogDismissed() { + InCallPresenter.getInstance().cancelAccountSelection(); + } + }; + SelectPhoneAccountDialogFragment.showAccountDialog(getFragmentManager(), - phoneAccountHandles); + R.string.select_phone_account_for_calls, true, phoneAccountHandles, + listener); } else { mCallCardFragment.setVisible(true); } @@ -601,14 +622,6 @@ public class InCallActivity extends Activity { } } - private boolean isEmergencyCall(Call call) { - final Uri handle = call.getHandle(); - if (handle == null) { - return false; - } - return PhoneNumberUtils.isEmergencyNumber(handle.getSchemeSpecificPart()); - } - private void relaunchedFromDialer(boolean showDialpad) { mShowDialpadRequested = showDialpad; mAnimateDialpadOnShow = true; @@ -662,6 +675,10 @@ public class InCallActivity extends Activity { } public void dismissKeyguard(boolean dismiss) { + if (mDismissKeyguard == dismiss) { + return; + } + mDismissKeyguard = dismiss; if (dismiss) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); } else { @@ -716,8 +733,18 @@ public class InCallActivity extends Activity { return mDialpadFragment != null && mDialpadFragment.isVisible(); } - public void showConferenceCallManager() { - mConferenceManagerFragment.setVisible(true); + /** + * Hides or shows the conference manager fragment. + * + * @param show {@code true} if the conference manager should be shown, {@code false} if it + * should be hidden. + */ + public void showConferenceCallManager(boolean show) { + mConferenceManagerFragment.setVisible(show); + + // Need to hide the call card fragment to ensure that accessibility service does not try to + // give focus to the call card when the conference manager is visible. + mCallCardFragment.getView().setVisibility(show ? View.GONE : View.VISIBLE); } public void showPostCharWaitDialog(String callId, String chars) { @@ -838,7 +865,7 @@ public class InCallActivity extends Activity { mDialog = new AlertDialog.Builder(this) .setMessage(msg) - .setPositiveButton(R.string.ok, new OnClickListener() { + .setPositiveButton(android.R.string.ok, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { onDialogDismissed(); diff --git a/src/com/android/incallui/InCallDateUtils.java b/src/com/android/incallui/InCallDateUtils.java new file mode 100644 index 00000000..88c5354c --- /dev/null +++ b/src/com/android/incallui/InCallDateUtils.java @@ -0,0 +1,56 @@ +package com.android.incallui; + +import android.content.res.Resources; + +/** + * Methods to parse time and date information in the InCallUi + */ +public class InCallDateUtils { + public InCallDateUtils() { + + } + + /** + * Return given duration in a human-friendly format. For example, "4 + * minutes 3 seconds" or "3 hours 1 second". Returns the hours, minutes and seconds in that + * order if they exist. + */ + public static String formatDetailedDuration(long millis) { + int hours = 0; + int minutes = 0; + int seconds = 0; + int elapsedSeconds = (int) (millis / 1000); + if (elapsedSeconds >= 3600) { + hours = elapsedSeconds / 3600; + elapsedSeconds -= hours * 3600; + } + if (elapsedSeconds >= 60) { + minutes = elapsedSeconds / 60; + elapsedSeconds -= minutes * 60; + } + seconds = elapsedSeconds; + + final Resources res = Resources.getSystem(); + StringBuilder duration = new StringBuilder(); + if (hours > 0) { + duration.append(res.getQuantityString( + com.android.internal.R.plurals.duration_hours, hours, hours)); + } + if (minutes > 0) { + if (hours > 0) { + duration.append(' '); + } + duration.append(res.getQuantityString( + com.android.internal.R.plurals.duration_minutes, minutes, minutes)); + } + if (seconds > 0) { + if (hours > 0 || minutes > 0) { + duration.append(' '); + } + duration.append(res.getQuantityString( + com.android.internal.R.plurals.duration_seconds, seconds, seconds)); + } + return duration.toString(); + } + +} diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index d26e48aa..743f667d 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -16,19 +16,22 @@ package com.android.incallui; -import android.Manifest; -import android.app.PendingIntent; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.ActivityNotFoundException; import android.content.pm.ActivityInfo; +import android.graphics.Point; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.telecom.DisconnectCause; import android.telecom.PhoneAccount; -import android.telecom.PhoneCapabilities; import android.telecom.Phone; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import android.telecom.VideoProfile; +import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.view.Surface; @@ -37,6 +40,9 @@ import android.view.Window; import android.view.WindowManager; import com.google.common.base.Preconditions; + +import com.android.contacts.common.interactions.TouchPointManager; +import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; import com.android.incalluibind.ObjectFactory; import java.util.Collections; @@ -60,6 +66,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { private static final String EXTRA_FIRST_TIME_SHOWN = "com.android.incallui.intent.extra.FIRST_TIME_SHOWN"; + private static final Bundle EMPTY_EXTRAS = new Bundle(); + private static InCallPresenter sInCallPresenter; /** @@ -74,6 +82,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { new ConcurrentHashMap<InCallDetailsListener, Boolean>(8, 0.9f, 1)); private final Set<CanAddCallListener> mCanAddCallListeners = Collections.newSetFromMap( new ConcurrentHashMap<CanAddCallListener, Boolean>(8, 0.9f, 1)); + private final Set<InCallUiListener> mInCallUiListeners = Collections.newSetFromMap( + new ConcurrentHashMap<InCallUiListener, Boolean>(8, 0.9f, 1)); private final Set<InCallOrientationListener> mOrientationListeners = Collections.newSetFromMap( new ConcurrentHashMap<InCallOrientationListener, Boolean>(8, 0.9f, 1)); private final Set<InCallEventListener> mInCallEventListeners = Collections.newSetFromMap( @@ -159,9 +169,34 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { */ private boolean mIsChangingConfigurations = false; + /** + * Whether or not to wait for the circular reveal animation to be started, to avoid stopping + * the circular reveal animation activity before the animation is initiated. + */ + private boolean mWaitForRevealAnimationStart = false; + + /** + * Whether or not the CircularRevealAnimationActivity has started. + */ + private boolean mCircularRevealActivityStarted = false; + + private boolean mShowDialpadOnStart = false; + + /** + * Whether or not InCallService is bound to Telecom. + */ + private boolean mServiceBound = false; + private Phone mPhone; private int mLastDisconnectCause = DisconnectCause.ERROR; + private Handler mHandler = new Handler(); + + /** Display colors for the UI. Consists of a primary color and secondary (darker) color */ + private MaterialPalette mThemeColors; + + private TelecomManager mTelecomManager; + public static synchronized InCallPresenter getInstance() { if (sInCallPresenter == null) { sInCallPresenter = new InCallPresenter(); @@ -246,6 +281,13 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } private void attemptFinishActivity() { + mWaitForRevealAnimationStart = false; + + Context context = mContext != null ? mContext : mInCallActivity; + if (context != null) { + CircularRevealActivity.sendClearDisplayBroadcast(context); + } + final boolean doFinish = (mInCallActivity != null && isActivityStarted()); Log.i(this, "Hide in call UI: " + doFinish); @@ -270,11 +312,44 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } /** - * Called when the UI begins or ends. Starts the callstate callbacks if the UI just began. - * Attempts to tear down everything if the UI just ended. See #tearDown for more insight on - * the tear-down process. + * Called when the UI begins, and starts the callstate callbacks if necessary. */ public void setActivity(InCallActivity inCallActivity) { + if (inCallActivity == null) { + throw new IllegalArgumentException("registerActivity cannot be called with null"); + } + if (mInCallActivity != null && mInCallActivity != inCallActivity) { + Log.wtf(this, "Setting a second activity before destroying the first."); + } + updateActivity(inCallActivity); + } + + /** + * Called when the UI ends. Attempts to tear down everything if necessary. See + * {@link #tearDown()} for more insight on the tear-down process. + */ + public void unsetActivity(InCallActivity inCallActivity) { + if (inCallActivity == null) { + throw new IllegalArgumentException("unregisterActivity cannot be called with null"); + } + if (mInCallActivity == null) { + Log.i(this, "No InCallActivity currently set, no need to unset."); + return; + } + if (mInCallActivity != inCallActivity) { + Log.w(this, "Second instance of InCallActivity is trying to unregister when another" + + " instance is active. Ignoring."); + return; + } + updateActivity(null); + } + + /** + * Updates the current instance of {@link InCallActivity} with the provided one. If a + * {@code null} activity is provided, it means that the activity was finished and we should + * attempt to cleanup. + */ + private void updateActivity(InCallActivity inCallActivity) { boolean updateListeners = false; boolean doAttemptCleanup = false; @@ -282,8 +357,6 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { if (mInCallActivity == null) { updateListeners = true; Log.i(this, "UI Initialized"); - } else if (mInCallActivity != inCallActivity) { - Log.wtf(this, "Setting a second activity before destroying the first."); } else { // since setActivity is called onStart(), it can be called multiple times. // This is fine and ignorable, but we do not want to update the world every time @@ -305,18 +378,18 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { // NOTE: This code relies on {@link #mInCallActivity} being set so we run it after // it has been set. if (mInCallState == InCallState.NO_CALLS) { - Log.i(this, "UI Intialized, but no calls left. shut down."); + Log.i(this, "UI Initialized, but no calls left. shut down."); attemptFinishActivity(); return; } } else { - Log.i(this, "UI Destroyed)"); + Log.i(this, "UI Destroyed"); updateListeners = true; mInCallActivity = null; // We attempt cleanup for the destroy case but only after we recalculate the state - // to see if we need to come back up or stay shut down. This is why we do the cleanup - // after the call to onCallListChange() instead of directly here. + // to see if we need to come back up or stay shut down. This is why we do the + // cleanup after the call to onCallListChange() instead of directly here. doAttemptCleanup = true; } @@ -705,7 +778,11 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { if (showing) { mIsActivityPreviouslyStarted = true; } else { - updateIsChangingConfigurations(); + CircularRevealActivity.sendClearDisplayBroadcast(mContext); + } + + for (InCallUiListener listener : mInCallUiListeners) { + listener.onUiShowing(showing); } } @@ -729,6 +806,14 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } + public void addInCallUiListener(InCallUiListener listener) { + mInCallUiListeners.add(listener); + } + + public boolean removeInCallUiListener(InCallUiListener listener) { + return mInCallUiListeners.remove(listener); + } + /** * Brings the app into the foreground if possible. */ @@ -783,8 +868,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { if (activeCall != null) { // TODO: This logic is repeated from CallButtonPresenter.java. We should // consolidate this logic. - final boolean canMerge = activeCall.can(PhoneCapabilities.MERGE_CONFERENCE); - final boolean canSwap = activeCall.can(PhoneCapabilities.SWAP_CONFERENCE); + final boolean canMerge = activeCall.can( + android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE); + final boolean canSwap = activeCall.can( + android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE); Log.v(this, "activeCall: " + activeCall + ", canMerge: " + canMerge + ", canSwap: " + canSwap); @@ -806,7 +893,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { if (heldCall != null) { // We have a hold call so presumeable it will always support HOLD...but // there is no harm in double checking. - final boolean canHold = heldCall.can(PhoneCapabilities.HOLD); + final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD); Log.v(this, "heldCall: " + heldCall + ", canHold: " + canHold); @@ -939,12 +1026,23 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { // This is different from the incoming call sequence because we do not need to shock the // user with a top-level notification. Just show the call UI normally. final boolean mainUiNotVisible = !isShowingInCallUi() || !getCallCardFragmentVisible(); - final boolean showCallUi = ((InCallState.PENDING_OUTGOING == newState || - InCallState.OUTGOING == newState) && mainUiNotVisible); - - // TODO: Can we be suddenly in a call without it having been in the outgoing or incoming - // state? I havent seen that but if it can happen, the code below should be enabled. - // showCallUi |= (InCallState.INCALL && !isActivityStarted()); + boolean showCallUi = InCallState.OUTGOING == newState && mainUiNotVisible; + + // Direct transition from PENDING_OUTGOING -> INCALL means that there was an error in the + // outgoing call process, so the UI should be brought up to show an error dialog. + showCallUi |= (InCallState.PENDING_OUTGOING == mInCallState + && InCallState.INCALL == newState && !isActivityStarted()); + + // Another exception - InCallActivity is in charge of disconnecting a call with no + // valid accounts set. Bring the UI up if this is true for the current pending outgoing + // call so that: + // 1) The call can be disconnected correctly + // 2) The UI comes up and correctly displays the error dialog. + // TODO: Remove these special case conditions by making InCallPresenter a true state + // machine. Telecom should also be the component responsible for disconnecting a call + // with no valid accounts. + showCallUi |= InCallState.PENDING_OUTGOING == newState && mainUiNotVisible + && isCallWithNoValidAccounts(CallList.getInstance().getPendingOutgoingCall()); // The only time that we have an instance of mInCallActivity and it isn't started is // when it is being destroyed. In that case, lets avoid bringing up another instance of @@ -983,6 +1081,43 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } /** + * Determines whether or not a call has no valid phone accounts that can be used to make the + * call with. Emergency calls do not require a phone account. + * + * @param call to check accounts for. + * @return {@code true} if the call has no call capable phone accounts set, {@code false} if + * the call contains a phone account that could be used to initiate it with, or is an emergency + * call. + */ + public static boolean isCallWithNoValidAccounts(Call call) { + if (call != null && !isEmergencyCall(call)) { + Bundle extras = call.getTelecommCall().getDetails().getExtras(); + + if (extras == null) { + extras = EMPTY_EXTRAS; + } + + final List<PhoneAccountHandle> phoneAccountHandles = extras + .getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); + + if ((call.getAccountHandle() == null && + (phoneAccountHandles == null || phoneAccountHandles.isEmpty()))) { + Log.i(InCallPresenter.getInstance(), "No valid accounts for call " + call); + return true; + } + } + return false; + } + + private static boolean isEmergencyCall(Call call) { + final Uri handle = call.getHandle(); + if (handle == null) { + return false; + } + return PhoneNumberUtils.isEmergencyNumber(handle.getSchemeSpecificPart()); + } + + /** * Sets the DisconnectCause for a call that was disconnected because it was missing a * PhoneAccount or PhoneAccounts to select from. * @param call @@ -1030,6 +1165,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { Log.i(this, "Start UI " + " anyOtherSubActive:" + anyOtherSubActive); if (isCallWaiting || anyOtherSubActive) { if (mProximitySensor.isScreenReallyOff() && isActivityStarted()) { + Log.i(this, "Restarting InCallActivity to turn screen on for call waiting"); mInCallActivity.finish(); // When the activity actually finishes, we will start it again if there are // any active calls, so we do not need to start it explicitly here. Note, we @@ -1103,21 +1239,104 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } - private void showInCall(boolean showDialpad, boolean newOutgoingCall) { - mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall)); + public void showInCall(final boolean showDialpad, final boolean newOutgoingCall) { + if (mCircularRevealActivityStarted) { + mWaitForRevealAnimationStart = true; + mShowDialpadOnStart = showDialpad; + Log.i(this, "Waiting for circular reveal completion to show InCallActivity"); + } else { + Log.i(this, "Showing InCallActivity immediately"); + mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall, + newOutgoingCall /* showCircularReveal */)); + } + } + + public void onCircularRevealStarted(final Activity activity) { + mCircularRevealActivityStarted = false; + if (mWaitForRevealAnimationStart) { + mWaitForRevealAnimationStart = false; + mHandler.post(new Runnable() { + @Override + public void run() { + Log.i(this, "Showing InCallActivity after circular reveal"); + final Intent intent = + getInCallIntent(mShowDialpadOnStart, true, false, false); + activity.startActivity(intent); + mShowDialpadOnStart = false; + } + }); + } else if (!mServiceBound) { + CircularRevealActivity.sendClearDisplayBroadcast(mContext); + return; + } } - public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall) { - final Intent intent = new Intent(Intent.ACTION_MAIN, null); + public void onServiceBind() { + mServiceBound = true; + } + + public void onServiceUnbind() { + mServiceBound = false; + } + + public boolean isServiceBound() { + return mServiceBound; + } + + public void maybeStartRevealAnimation(Intent intent) { + if (intent == null || mInCallActivity != null) { + return; + } + final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); + if (extras == null) { + // Incoming call, just show the in-call UI directly. + return; + } + + if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) { + // Account selection dialog will show up so don't show the animation. + return; + } + + final PhoneAccountHandle accountHandle = + intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); + final MaterialPalette colors = getColorsFromPhoneAccountHandle(accountHandle); + final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT); + + mCircularRevealActivityStarted = true; + mContext.startActivity(getAnimationIntent(touchPoint, colors)); + } + + private Intent getAnimationIntent(Point touchPoint, MaterialPalette palette) { + final Intent intent = new Intent(mContext, CircularRevealActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NO_USER_ACTION); + intent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint); + intent.putExtra(CircularRevealActivity.EXTRA_THEME_COLORS, palette); + return intent; + } + + public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall, + boolean showCircularReveal) { + return getInCallIntent(showDialpad, newOutgoingCall, showCircularReveal, true); + } + + public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall, + boolean showCircularReveal, boolean newTask) { + final Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_NO_USER_ACTION); + if (newTask) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + intent.setClass(mContext, InCallActivity.class); if (showDialpad) { intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true); } - - intent.putExtra(InCallActivity.NEW_OUTGOING_CALL, newOutgoingCall); + intent.putExtra(InCallActivity.NEW_OUTGOING_CALL_EXTRA, newOutgoingCall); + intent.putExtra(InCallActivity.SHOW_CIRCULAR_REVEAL_EXTRA, showCircularReveal); return intent; } @@ -1262,6 +1481,20 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } /** + * Hides or shows the conference manager fragment. + * + * @param show {@code true} if the conference manager should be shown, {@code false} if it + * should be hidden. + */ + public void showConferenceCallManager(boolean show) { + if (mInCallActivity == null) { + return; + } + + mInCallActivity.showConferenceCallManager(show); + } + + /** * @return True if the application is currently running in a right-to-left locale. */ public static boolean isRtl() { @@ -1270,6 +1503,61 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } /** + * Extract background color from call object. The theme colors will include a primary color + * and a secondary color. + */ + public void setThemeColors() { + // This method will set the background to default if the color is PhoneAccount.NO_COLOR. + mThemeColors = getColorsFromCall(CallList.getInstance().getFirstCall()); + + if (mInCallActivity == null) { + return; + } + + mInCallActivity.getWindow().setStatusBarColor(mThemeColors.mSecondaryColor); + } + + /** + * @return A palette for colors to display in the UI. + */ + public MaterialPalette getThemeColors() { + return mThemeColors; + } + + private MaterialPalette getColorsFromCall(Call call) { + return getColorsFromPhoneAccountHandle(call == null ? null : call.getAccountHandle()); + } + + private MaterialPalette getColorsFromPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) { + int highlightColor = PhoneAccount.NO_HIGHLIGHT_COLOR; + if (phoneAccountHandle != null) { + final TelecomManager tm = getTelecomManager(); + + if (tm != null) { + final PhoneAccount account = tm.getPhoneAccount(phoneAccountHandle); + // For single-sim devices, there will be no selected highlight color, so the phone + // account will default to NO_HIGHLIGHT_COLOR. + if (account != null) { + highlightColor = account.getHighlightColor(); + } + } + } + return new InCallUIMaterialColorMapUtils( + mContext.getResources()).calculatePrimaryAndSecondaryColor(highlightColor); + } + + /** + * @return An instance of TelecomManager. + */ + public TelecomManager getTelecomManager() { + if (mTelecomManager == null) { + mTelecomManager = (TelecomManager) + mContext.getSystemService(Context.TELECOM_SERVICE); + } + return mTelecomManager; + } + + /** * Private constructor. Must use getInstance() to get this singleton. */ private InCallPresenter() { @@ -1340,4 +1628,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { public interface InCallEventListener { public void onFullScreenVideoStateChanged(boolean isFullScreenVideo); } + + public interface InCallUiListener { + void onUiShowing(boolean showing); + } } diff --git a/src/com/android/incallui/InCallServiceImpl.java b/src/com/android/incallui/InCallServiceImpl.java index dfec2622..39aaf931 100644 --- a/src/com/android/incallui/InCallServiceImpl.java +++ b/src/com/android/incallui/InCallServiceImpl.java @@ -16,6 +16,8 @@ package com.android.incallui; +import android.content.Intent; +import android.os.IBinder; import android.telecom.InCallService; import android.telecom.Phone; @@ -34,10 +36,6 @@ public class InCallServiceImpl extends InCallService { AudioModeProvider.getInstance().setPhone(phone); TelecomAdapter.getInstance().setPhone(phone); InCallPresenter.getInstance().setPhone(phone); - InCallPresenter.getInstance().setUp( - getApplicationContext(), - CallList.getInstance(), - AudioModeProvider.getInstance()); CallRecorder.getInstance().setUp(getApplicationContext()); TelecomAdapter.getInstance().setContext(InCallServiceImpl.this); } @@ -53,4 +51,21 @@ public class InCallServiceImpl extends InCallService { CallList.getInstance().clearOnDisconnect(); InCallPresenter.getInstance().tearDown(); } + + @Override + public IBinder onBind(Intent intent) { + InCallPresenter.getInstance().setUp( + getApplicationContext(), + CallList.getInstance(), + AudioModeProvider.getInstance()); + InCallPresenter.getInstance().onServiceBind(); + InCallPresenter.getInstance().maybeStartRevealAnimation(intent); + return super.onBind(intent); + } + + @Override + public boolean onUnbind(Intent intent) { + InCallPresenter.getInstance().onServiceUnbind(); + return super.onUnbind(intent); + } } diff --git a/src/com/android/incallui/InCallUIMaterialColorMapUtils.java b/src/com/android/incallui/InCallUIMaterialColorMapUtils.java new file mode 100644 index 00000000..1f61070e --- /dev/null +++ b/src/com/android/incallui/InCallUIMaterialColorMapUtils.java @@ -0,0 +1,52 @@ +package com.android.incallui; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.telecom.PhoneAccount; + +import com.android.contacts.common.util.MaterialColorMapUtils; +import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; + +public class InCallUIMaterialColorMapUtils extends MaterialColorMapUtils { + private final TypedArray sPrimaryColors; + private final TypedArray sSecondaryColors; + private final Resources mResources; + + public InCallUIMaterialColorMapUtils(Resources resources) { + super(resources); + sPrimaryColors = resources.obtainTypedArray( + com.android.incallui.R.array.background_colors); + sSecondaryColors = resources.obtainTypedArray( + com.android.incallui.R.array.background_colors_dark); + mResources = resources; + } + + /** + * Currently the InCallUI color will only vary by SIM color which is a list of colors + * defined in the background_colors array, so first search the list for the matching color and + * fall back to the closest matching color if an exact match does not exist. + */ + @Override + public MaterialPalette calculatePrimaryAndSecondaryColor(int color) { + if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) { + return getDefaultPrimaryAndSecondaryColors(mResources); + } + + for (int i = 0; i < sPrimaryColors.length(); i++) { + if (sPrimaryColors.getColor(i, 0) == color) { + return new MaterialPalette( + sPrimaryColors.getColor(i, 0), + sSecondaryColors.getColor(i, 0)); + } + } + + // The color isn't in the list, so use the superclass to find an approximate color. + return super.calculatePrimaryAndSecondaryColor(color); + } + + public static MaterialPalette getDefaultPrimaryAndSecondaryColors(Resources resources) { + final int primaryColor = resources.getColor(R.color.dialer_theme_color); + final int secondaryColor = resources.getColor(R.color.dialer_theme_color_dark); + return new MaterialPalette(primaryColor, secondaryColor); + } +}
\ No newline at end of file diff --git a/src/com/android/incallui/Log.java b/src/com/android/incallui/Log.java index 5e13c790..07a0e61c 100644 --- a/src/com/android/incallui/Log.java +++ b/src/com/android/incallui/Log.java @@ -31,9 +31,11 @@ public class Log { // Generic tag for all In Call logging public static final String TAG = "InCall"; - public static final boolean DEBUG = android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); - public static final boolean VERBOSE = android.util.Log.isLoggable(TAG, - android.util.Log.VERBOSE); + public static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */ + public static final boolean DEBUG = FORCE_DEBUG || + android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); + public static final boolean VERBOSE = FORCE_DEBUG || + android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE); public static final String TAG_DELIMETER = " - "; public static void d(String tag, String msg) { diff --git a/src/com/android/incallui/Presenter.java b/src/com/android/incallui/Presenter.java index d2f2d36f..4e1fa978 100644 --- a/src/com/android/incallui/Presenter.java +++ b/src/com/android/incallui/Presenter.java @@ -16,6 +16,8 @@ package com.android.incallui; +import android.os.Bundle; + /** * Base class for Presenters. */ @@ -47,6 +49,10 @@ public abstract class Presenter<U extends Ui> { public void onUiUnready(U ui) { } + public void onSaveInstanceState(Bundle outState) {} + + public void onRestoreInstanceState(Bundle savedInstanceState) {} + public U getUi() { return mUi; } diff --git a/src/com/android/incallui/SelectPhoneAccountDialogFragment.java b/src/com/android/incallui/SelectPhoneAccountDialogFragment.java deleted file mode 100644 index e6c78c6d..00000000 --- a/src/com/android/incallui/SelectPhoneAccountDialogFragment.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.incallui; - -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.FragmentManager; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.telecom.TelecomManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.TextView; - -import com.android.contacts.common.R; - -import java.util.List; - -/** - * Dialog that allows the user to switch between default SIM cards - */ -public class SelectPhoneAccountDialogFragment extends DialogFragment { - private List<PhoneAccountHandle> mAccountHandles; - private boolean mIsSelected; - private TelecomManager mTelecomManager; - - /** - * Shows the account selection dialog. - * This is the preferred way to show this dialog. - * - * @param fragmentManager The fragment manager. - * @param accountHandles The {@code PhoneAccountHandle}s available to select from. - */ - public static void showAccountDialog(FragmentManager fragmentManager, - List<PhoneAccountHandle> accountHandles) { - SelectPhoneAccountDialogFragment fragment = - new SelectPhoneAccountDialogFragment(accountHandles); - fragment.show(fragmentManager, "selectAccount"); - } - - public SelectPhoneAccountDialogFragment(List<PhoneAccountHandle> accountHandles) { - super(); - mAccountHandles = accountHandles; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - mIsSelected = false; - mTelecomManager = - (TelecomManager) getActivity().getSystemService(Context.TELECOM_SERVICE); - - final DialogInterface.OnClickListener selectionListener = - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mIsSelected = true; - PhoneAccountHandle selectedAccountHandle = mAccountHandles.get(which); - InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle, - mIsSelected); - } - }; - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - ListAdapter selectAccountListAdapter = new SelectAccountListAdapter( - builder.getContext(), - R.layout.select_account_list_item, - mAccountHandles); - - return builder.setTitle(R.string.select_account_dialog_title) - .setAdapter(selectAccountListAdapter, selectionListener) - .create(); - } - - private class SelectAccountListAdapter extends ArrayAdapter<PhoneAccountHandle> { - private Context mContext; - private int mResId; - - public SelectAccountListAdapter( - Context context, int resource, List<PhoneAccountHandle> accountHandles) { - super(context, resource, accountHandles); - mContext = context; - mResId = resource; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - LayoutInflater inflater = (LayoutInflater) - mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - View rowView; - final ViewHolder holder; - - if (convertView == null) { - // Cache views for faster scrolling - rowView = inflater.inflate(mResId, null); - holder = new ViewHolder(); - holder.textView = (TextView) rowView.findViewById(R.id.text); - holder.imageView = (ImageView) rowView.findViewById(R.id.icon); - rowView.setTag(holder); - } - else { - rowView = convertView; - holder = (ViewHolder) rowView.getTag(); - } - - PhoneAccountHandle accountHandle = getItem(position); - PhoneAccount account = mTelecomManager.getPhoneAccount(accountHandle); - holder.textView.setText(account.getLabel()); - holder.imageView.setImageDrawable(account.createIconDrawable(mContext)); - return rowView; - } - - private class ViewHolder { - TextView textView; - ImageView imageView; - } - } - - @Override - public void onPause() { - if (!mIsSelected) { - InCallPresenter.getInstance().cancelAccountSelection(); - } - super.onPause(); - } -} diff --git a/src/com/android/incallui/StatusBarNotifier.java b/src/com/android/incallui/StatusBarNotifier.java index ca76d235..8e0afcb3 100644 --- a/src/com/android/incallui/StatusBarNotifier.java +++ b/src/com/android/incallui/StatusBarNotifier.java @@ -17,6 +17,7 @@ package com.android.incallui; import android.net.Uri; + import com.google.common.base.Preconditions; import android.app.Notification; @@ -34,8 +35,11 @@ import android.telecom.PhoneCapabilities; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; import android.text.TextUtils; +import com.android.contacts.common.util.BitmapUtil; import com.android.incallui.ContactInfoCache.ContactCacheEntry; import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; import com.android.incallui.InCallApp.NotificationBroadcastReceiver; @@ -150,8 +154,10 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { * @see #updateInCallNotification(InCallState,CallList) */ private void cancelInCall() { - Log.d(this, "cancelInCall()..."); - mNotificationManager.cancel(IN_CALL_NOTIFICATION); + if (mIsShowingNotification) { + Log.d(this, "cancelInCall()..."); + mNotificationManager.cancel(IN_CALL_NOTIFICATION); + } mIsShowingNotification = false; } @@ -270,15 +276,12 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { } final int state = call.getState(); - final boolean isConference = call.isConferenceCall(); - final boolean isVideoUpgradeRequest = call.getSessionModificationState() - == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; // Check if data has changed; if nothing is different, don't issue another notification. final int iconResId = getIconToDisplay(call); - final Bitmap largeIcon = getLargeIconToDisplay(contactInfo, isConference); + final Bitmap largeIcon = getLargeIconToDisplay(contactInfo, call); final int contentResId = getContentString(call); - final String contentTitle = getContentTitle(contactInfo, isConference); + final String contentTitle = getContentTitle(contactInfo, call); if (!checkForChangeAndSaveData(iconResId, contentResId, largeIcon, contentTitle, state)) { return; @@ -297,6 +300,8 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { if ((state == Call.State.INCOMING || state == Call.State.CALL_WAITING) && !InCallPresenter.getInstance().isShowingInCallUi()) { configureFullScreenIntent(builder, inCallPendingIntent, call); + // Set the notification category for incoming calls + builder.setCategory(Notification.CATEGORY_CALL); } // Set the content @@ -306,14 +311,8 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { builder.setLargeIcon(largeIcon); builder.setColor(mContext.getResources().getColor(R.color.dialer_theme_color)); - if (TelephonyManager.getDefault().isMultiSimEnabled()) { - SubscriptionManager mgr = SubscriptionManager.from(mContext); - SubscriptionInfo subInfoRecord = mgr.getActiveSubscriptionInfo(call.getSubId()); - if (subInfoRecord != null) { - builder.setSubText(subInfoRecord.getDisplayName()); - } - } - + final boolean isVideoUpgradeRequest = call.getSessionModificationState() + == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; if (isVideoUpgradeRequest) { builder.setUsesChronometer(false); addDismissUpgradeRequestAction(builder); @@ -400,15 +399,15 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { /** * Returns the main string to use in the notification. */ - private String getContentTitle(ContactCacheEntry contactInfo, boolean isConference) { - if (isConference) { + private String getContentTitle(ContactCacheEntry contactInfo, Call call) { + if (call.isConferenceCall() + && !call.can(android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE)) { return mContext.getResources().getString(R.string.card_title_conf_call); } if (TextUtils.isEmpty(contactInfo.name)) { - if (!TextUtils.isEmpty(contactInfo.location)){ - return contactInfo.number + " " + contactInfo.location; - } - return contactInfo.number; + return TextUtils.isEmpty(contactInfo.number) ? null + : BidiFormatter.getInstance().unicodeWrap( + contactInfo.number.toString(), TextDirectionHeuristics.LTR); } return contactInfo.name; } @@ -426,9 +425,10 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { /** * Gets a large icon from the contact info object to display in the notification. */ - private Bitmap getLargeIconToDisplay(ContactCacheEntry contactInfo, boolean isConference) { + private Bitmap getLargeIconToDisplay(ContactCacheEntry contactInfo, Call call) { Bitmap largeIcon = null; - if (isConference) { + if (call.isConferenceCall() + && !call.can(android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE)) { largeIcon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.img_conference); } @@ -441,9 +441,8 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { android.R.dimen.notification_large_icon_height); final int width = (int) mContext.getResources().getDimension( android.R.dimen.notification_large_icon_width); - largeIcon = Bitmap.createScaledBitmap(largeIcon, width, height, false); + largeIcon = BitmapUtil.getRoundedBitmap(largeIcon, width, height); } - return largeIcon; } @@ -526,7 +525,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { PendingIntent answerVoicePendingIntent = createNotificationPendingIntent( mContext, InCallApp.ACTION_ANSWER_VOICE_INCOMING_CALL); builder.addAction(R.drawable.ic_call_white_24dp, - mContext.getText(R.string.description_target_answer), + mContext.getText(R.string.notification_action_answer), answerVoicePendingIntent); } @@ -657,7 +656,8 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { private PendingIntent createLaunchPendingIntent() { final Intent intent = InCallPresenter.getInstance().getInCallIntent( - false /* showDialpad */, false /* newOutgoingCall */); + false /* showDialpad */, false /* newOutgoingCall */, + false /* showCircularReveal */); // PendingIntent that can be used to launch the InCallActivity. The // system fires off this intent if the user pulls down the windowshade diff --git a/src/com/android/incallui/TelecomAdapter.java b/src/com/android/incallui/TelecomAdapter.java index 6acb6404..b0fcc128 100644 --- a/src/com/android/incallui/TelecomAdapter.java +++ b/src/com/android/incallui/TelecomAdapter.java @@ -24,8 +24,6 @@ import android.telecom.InCallAdapter; import android.telecom.Phone; import android.telecom.PhoneAccountHandle; -import android.telecom.PhoneCapabilities; - import com.google.common.base.Preconditions; import java.util.List; @@ -181,8 +179,8 @@ final class TelecomAdapter implements InCallPhoneListener { if (!conferenceable.isEmpty()) { call.conference(conferenceable.get(0)); } else { - int capabilities = call.getDetails().getCallCapabilities(); - if (0 != (capabilities & PhoneCapabilities.MERGE_CONFERENCE)) { + if (call.getDetails().can( + android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE)) { call.mergeConference(); } } @@ -194,8 +192,8 @@ final class TelecomAdapter implements InCallPhoneListener { void swap(String callId) { if (mPhone != null) { android.telecom.Call call = getTelecommCallById(callId); - int capabilities = call.getDetails().getCallCapabilities(); - if (0 != (capabilities & PhoneCapabilities.SWAP_CONFERENCE)) { + if (call.getDetails().can( + android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE)) { call.swapConference(); } } else { @@ -248,8 +246,7 @@ final class TelecomAdapter implements InCallPhoneListener { } } - void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle, - boolean setDefault) { + void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle, boolean setDefault) { if (mPhone != null) { getTelecommCallById(callId).phoneAccountSelected(accountHandle, setDefault); } else { diff --git a/src/com/android/incallui/widget/multiwaveview/GlowPadView.java b/src/com/android/incallui/widget/multiwaveview/GlowPadView.java index 745b1870..7c115b99 100644 --- a/src/com/android/incallui/widget/multiwaveview/GlowPadView.java +++ b/src/com/android/incallui/widget/multiwaveview/GlowPadView.java @@ -402,7 +402,6 @@ public class GlowPadView extends View { case STATE_TRACKING: mHandleDrawable.setAlpha(0.0f); - showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 1.0f, null); break; case STATE_SNAP: |