blob: ca4eb8838851faaf61827df1df33d49ef5d755aa [file] [log] [blame]
/*
* Copyright (C) 2013 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.content.Context;
import android.telecom.AudioState;
import android.telecom.InCallService.VideoCall;
import android.telecom.PhoneCapabilities;
import android.telecom.VideoProfile;
import com.android.incallui.AudioModeProvider.AudioModeListener;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
import com.android.incallui.InCallPresenter.InCallDetailsListener;
import android.telephony.PhoneNumberUtils;
import java.util.Objects;
/**
* Logic for call buttons.
*/
public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
implements InCallStateListener, AudioModeListener, IncomingCallListener,
InCallDetailsListener {
private Call mCall;
private boolean mAutomaticallyMuted = false;
private boolean mPreviousMuteState = false;
public CallButtonPresenter() {
}
@Override
public void onUiReady(CallButtonUi ui) {
super.onUiReady(ui);
AudioModeProvider.getInstance().addListener(this);
// register for call state changes last
InCallPresenter.getInstance().addListener(this);
InCallPresenter.getInstance().addIncomingCallListener(this);
InCallPresenter.getInstance().addDetailsListener(this);
}
@Override
public void onUiUnready(CallButtonUi ui) {
super.onUiUnready(ui);
InCallPresenter.getInstance().removeListener(this);
AudioModeProvider.getInstance().removeListener(this);
InCallPresenter.getInstance().removeIncomingCallListener(this);
InCallPresenter.getInstance().removeDetailsListener(this);
}
@Override
public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
CallButtonUi ui = getUi();
if (newState == InCallState.OUTGOING) {
mCall = callList.getOutgoingCall();
} else if (newState == InCallState.INCALL) {
mCall = callList.getActiveOrBackgroundCall();
// When connected to voice mail, automatically shows the dialpad.
// (On previous releases we showed it when in-call shows up, before waiting for
// 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 */);
}
}
} else if (newState == InCallState.INCOMING) {
if (ui != null) {
ui.displayDialpad(false /* show */, true /* animate */);
}
mCall = null;
} else {
mCall = null;
}
updateUi(newState, mCall);
}
/**
* Updates the user interface in response to a change in the details of a call.
* Currently handles changes to the call buttons in response to a change in the details for a
* call. This is important to ensure changes to the active call are reflected in the available
* buttons.
*
* @param call The active call.
* @param details The call details.
*/
@Override
public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
// If the details change is not for the currently active call no update is required.
if (!Objects.equals(call, mCall)) {
return;
}
updateCallButtons(call, getUi().getContext());
}
@Override
public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
onStateChange(oldState, newState, CallList.getInstance());
}
@Override
public void onAudioMode(int mode) {
if (getUi() != null) {
getUi().setAudio(mode);
}
}
@Override
public void onSupportedAudioMode(int mask) {
if (getUi() != null) {
getUi().setSupportedAudio(mask);
}
}
@Override
public void onMute(boolean muted) {
if (getUi() != null && !mAutomaticallyMuted) {
getUi().setMute(muted);
}
}
public int getAudioMode() {
return AudioModeProvider.getInstance().getAudioMode();
}
public int getSupportedAudio() {
return AudioModeProvider.getInstance().getSupportedModes();
}
public void setAudioMode(int mode) {
// TODO: Set a intermediate state in this presenter until we get
// an update for onAudioMode(). This will make UI response immediate
// if it turns out to be slow
Log.d(this, "Sending new Audio Mode: " + AudioState.audioRouteToString(mode));
TelecomAdapter.getInstance().setAudioRoute(mode);
}
/**
* Function assumes that bluetooth is not supported.
*/
public void toggleSpeakerphone() {
// this function should not be called if bluetooth is available
if (0 != (AudioState.ROUTE_BLUETOOTH & getSupportedAudio())) {
// It's clear the UI is wrong, so update the supported mode once again.
Log.e(this, "toggling speakerphone not allowed when bluetooth supported.");
getUi().setSupportedAudio(getSupportedAudio());
return;
}
int newMode = AudioState.ROUTE_SPEAKER;
// if speakerphone is already on, change to wired/earpiece
if (getAudioMode() == AudioState.ROUTE_SPEAKER) {
newMode = AudioState.ROUTE_WIRED_OR_EARPIECE;
}
setAudioMode(newMode);
}
public void muteClicked(boolean checked) {
Log.d(this, "turning on mute: " + checked);
TelecomAdapter.getInstance().mute(checked);
}
public void holdClicked(boolean checked) {
if (mCall == null) {
return;
}
if (checked) {
Log.i(this, "Putting the call on hold: " + mCall);
TelecomAdapter.getInstance().holdCall(mCall.getId());
} else {
Log.i(this, "Removing the call from hold: " + mCall);
TelecomAdapter.getInstance().unholdCall(mCall.getId());
}
}
public void swapClicked() {
if (mCall == null) {
return;
}
Log.i(this, "Swapping the call: " + mCall);
TelecomAdapter.getInstance().swap(mCall.getId());
}
public void mergeClicked() {
TelecomAdapter.getInstance().merge(mCall.getId());
}
public void addCallClicked() {
// Automatically mute the current call
mAutomaticallyMuted = true;
mPreviousMuteState = AudioModeProvider.getInstance().getMute();
// Simulate a click on the mute button
muteClicked(true);
TelecomAdapter.getInstance().addCall();
}
public void changeToVoiceClicked() {
VideoCall videoCall = mCall.getVideoCall();
if (videoCall == null) {
return;
}
VideoProfile videoProfile = new VideoProfile(
VideoProfile.VideoState.AUDIO_ONLY, VideoProfile.QUALITY_DEFAULT);
videoCall.sendSessionModifyRequest(videoProfile);
}
public void showDialpadClicked(boolean checked) {
Log.v(this, "Show dialpad " + String.valueOf(checked));
getUi().displayDialpad(checked /* show */, true /* animate */);
}
public void changeToVideoClicked() {
VideoCall videoCall = mCall.getVideoCall();
if (videoCall == null) {
return;
}
VideoProfile videoProfile =
new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL);
videoCall.sendSessionModifyRequest(videoProfile);
mCall.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED);
}
/**
* Switches the camera between the front-facing and back-facing camera.
* @param useFrontFacingCamera True if we should switch to using the front-facing camera, or
* false if we should switch to using the back-facing camera.
*/
public void switchCameraClicked(boolean useFrontFacingCamera) {
InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager();
cameraManager.setUseFrontFacingCamera(useFrontFacingCamera);
VideoCall videoCall = mCall.getVideoCall();
if (videoCall == null) {
return;
}
String cameraId = cameraManager.getActiveCameraId();
if (cameraId != null) {
videoCall.setCamera(cameraId);
videoCall.requestCameraCapabilities();
}
getUi().setSwitchCameraButton(!useFrontFacingCamera);
}
/**
* Stop or start client's video transmission.
* @param pause True if pausing the local user's video, or false if starting the local user's
* video.
*/
public void pauseVideoClicked(boolean pause) {
VideoCall videoCall = mCall.getVideoCall();
if (videoCall == null) {
return;
}
if (pause) {
videoCall.setCamera(null);
VideoProfile videoProfile = new VideoProfile(
mCall.getVideoState() | VideoProfile.VideoState.PAUSED);
videoCall.sendSessionModifyRequest(videoProfile);
} else {
InCallCameraManager cameraManager = InCallPresenter.getInstance().
getInCallCameraManager();
videoCall.setCamera(cameraManager.getActiveCameraId());
VideoProfile videoProfile = new VideoProfile(
mCall.getVideoState() & ~VideoProfile.VideoState.PAUSED);
videoCall.sendSessionModifyRequest(videoProfile);
}
getUi().setPauseVideoButton(pause);
}
private void updateUi(InCallState state, Call call) {
Log.d(this, "Updating call UI for call: ", call);
final CallButtonUi ui = getUi();
if (ui == null) {
return;
}
final boolean isEnabled =
state.isConnectingOrConnected() &&!state.isIncoming() && call != null;
ui.setEnabled(isEnabled);
if (!isEnabled) {
return;
}
updateCallButtons(call, ui.getContext());
ui.enableMute(call.can(PhoneCapabilities.MUTE));
}
/**
* Updates the buttons applicable for the UI.
*
* @param call The active call.
* @param context The context.
*/
private void updateCallButtons(Call call, Context context) {
if (call.isVideoCall(context)) {
updateVideoCallButtons();
} else {
updateVoiceCallButtons(call);
}
}
private void updateVideoCallButtons() {
Log.v(this, "Showing buttons for video call.");
final CallButtonUi ui = getUi();
// Hide all voice-call-related buttons.
ui.showAudioButton(false);
ui.showDialpadButton(false);
ui.showHoldButton(false);
ui.showSwapButton(false);
ui.showChangeToVideoButton(false);
ui.showAddCallButton(false);
ui.showMergeButton(false);
ui.showOverflowButton(false);
// Show all video-call-related buttons.
ui.showChangeToVoiceButton(true);
ui.showSwitchCameraButton(true);
ui.showPauseVideoButton(true);
}
private void updateVoiceCallButtons(Call call) {
Log.v(this, "Showing buttons for voice call.");
final CallButtonUi ui = getUi();
// Hide all video-call-related buttons.
ui.showChangeToVoiceButton(false);
ui.showSwitchCameraButton(false);
ui.showPauseVideoButton(false);
// Show all voice-call-related buttons.
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));
Log.v(this, "Show merge ", call.can(PhoneCapabilities.MERGE_CONFERENCE));
Log.v(this, "Show swap ", call.can(PhoneCapabilities.SWAP_CONFERENCE));
Log.v(this, "Show add call ", call.can(PhoneCapabilities.ADD_CALL));
Log.v(this, "Show mute ", call.can(PhoneCapabilities.MUTE));
final boolean canAdd = call.can(PhoneCapabilities.ADD_CALL);
final boolean enableHoldOption = call.can(PhoneCapabilities.HOLD);
final boolean supportHold = call.can(PhoneCapabilities.SUPPORT_HOLD);
boolean canVideoCall = call.can(PhoneCapabilities.SUPPORTS_VT_LOCAL)
&& call.can(PhoneCapabilities.SUPPORTS_VT_REMOTE);
ui.showChangeToVideoButton(canVideoCall);
final boolean showMergeOption = call.can(PhoneCapabilities.MERGE_CONFERENCE);
final boolean showAddCallOption = canAdd;
// 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 showHoldOption = !showSwapOption && (enableHoldOption || supportHold);
ui.setHold(call.getState() == Call.State.ONHOLD);
// 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 isCdmaConferenceOverflowScenario =
(showHoldOption || showSwapOption) && showMergeOption && showAddCallOption;
if (isVideoOverflowScenario) {
ui.showHoldButton(false);
ui.showSwapButton(false);
ui.showAddCallButton(false);
ui.showMergeButton(false);
ui.showOverflowButton(true);
ui.configureOverflowMenu(
showMergeOption,
showAddCallOption /* showAddMenuOption */,
showHoldOption && enableHoldOption /* showHoldMenuOption */,
showSwapOption);
} else {
if (isCdmaConferenceOverflowScenario) {
ui.showAddCallButton(false);
ui.showMergeButton(false);
ui.configureOverflowMenu(
showMergeOption,
showAddCallOption /* showAddMenuOption */,
false /* showHoldMenuOption */,
false /* showSwapMenuOption */);
} else {
ui.showMergeButton(showMergeOption);
ui.showAddCallButton(showAddCallOption);
}
ui.showHoldButton(showHoldOption);
ui.enableHold(enableHoldOption);
ui.showSwapButton(showSwapOption);
}
}
public void refreshMuteState() {
// Restore the previous mute state
if (mAutomaticallyMuted &&
AudioModeProvider.getInstance().getMute() != mPreviousMuteState) {
if (getUi() == null) {
return;
}
muteClicked(mPreviousMuteState);
}
mAutomaticallyMuted = false;
}
public interface CallButtonUi extends Ui {
void setEnabled(boolean on);
void setMute(boolean on);
void enableMute(boolean enabled);
void showAudioButton(boolean show);
void showChangeToVoiceButton(boolean show);
void showDialpadButton(boolean show);
void setHold(boolean on);
void showHoldButton(boolean show);
void enableHold(boolean enabled);
void showSwapButton(boolean show);
void showChangeToVideoButton(boolean show);
void showSwitchCameraButton(boolean show);
void setSwitchCameraButton(boolean isBackFacingCamera);
void showAddCallButton(boolean show);
void showMergeButton(boolean show);
void showPauseVideoButton(boolean show);
void setPauseVideoButton(boolean isPaused);
void showOverflowButton(boolean show);
void displayDialpad(boolean on, boolean animate);
boolean isDialpadVisible();
void setAudio(int mode);
void setSupportedAudio(int mask);
void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption,
boolean showHoldMenuOption, boolean showSwapMenuOption);
Context getContext();
}
}