blob: 29cdd4ddce985f085461725fb0b7400c67995e36 [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 static com.android.incallui.CallButtonFragment.Buttons.*;
import android.content.Context;
import android.os.Bundle;
import android.telecom.CallAudioState;
import android.telecom.InCallService.VideoCall;
import android.telecom.VideoProfile;
import com.android.incallui.AudioModeProvider.AudioModeListener;
import com.android.incallui.InCallCameraManager.Listener;
import com.android.incallui.InCallPresenter.CanAddCallListener;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
import com.android.incallui.InCallPresenter.InCallDetailsListener;
import java.util.Objects;
/**
* Logic for call buttons.
*/
public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
implements InCallStateListener, AudioModeListener, IncomingCallListener,
InCallDetailsListener, CanAddCallListener, Listener {
private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted";
private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state";
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
final InCallPresenter inCallPresenter = InCallPresenter.getInstance();
inCallPresenter.addListener(this);
inCallPresenter.addIncomingCallListener(this);
inCallPresenter.addDetailsListener(this);
inCallPresenter.addCanAddCallListener(this);
inCallPresenter.getInCallCameraManager().addCameraSelectionListener(this);
// Update the buttons state immediately for the current call
onStateChange(InCallState.NO_CALLS, inCallPresenter.getInCallState(),
CallList.getInstance());
}
@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);
InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this);
InCallPresenter.getInstance().removeCanAddCallListener(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) {
if (CallerInfoUtils.isVoiceMailNumber(ui.getContext(), mCall)) {
ui.displayDialpad(true /* show */, true /* animate */);
}
}
}
} else if (newState == InCallState.INCOMING) {
if (ui != null) {
ui.displayDialpad(false /* show */, true /* animate */);
}
mCall = callList.getIncomingCall();
} 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) {
// Only update if the changes are for the currently active call
if (getUi() != null && call != null && call.equals(mCall)) {
updateButtonsState(call);
}
}
@Override
public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
onStateChange(oldState, newState, CallList.getInstance());
}
@Override
public void onCanAddCallChanged(boolean canAddCall) {
if (getUi() != null && mCall != null) {
updateButtonsState(mCall);
}
}
@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: " + CallAudioState.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 != (CallAudioState.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 = CallAudioState.ROUTE_SPEAKER;
// if speakerphone is already on, change to wired/earpiece
if (getAudioMode() == CallAudioState.ROUTE_SPEAKER) {
newMode = CallAudioState.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.STATE_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;
}
int currVideoState = mCall.getVideoState();
int currUnpausedVideoState = CallUtils.getUnPausedVideoState(currVideoState);
currUnpausedVideoState |= VideoProfile.STATE_BIDIRECTIONAL;
VideoProfile videoProfile = new VideoProfile(currUnpausedVideoState);
videoCall.sendSessionModifyRequest(videoProfile);
mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE);
}
/**
* 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) {
final int cameraDir = cameraManager.isUsingFrontFacingCamera()
? Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING
: Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING;
mCall.getVideoSettings().setCameraDir(cameraDir);
videoCall.setCamera(cameraId);
videoCall.requestCameraCapabilities();
}
}
/**
* 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.STATE_TX_ENABLED);
videoCall.sendSessionModifyRequest(videoProfile);
} else {
InCallCameraManager cameraManager = InCallPresenter.getInstance().
getInCallCameraManager();
videoCall.setCamera(cameraManager.getActiveCameraId());
VideoProfile videoProfile = new VideoProfile(
mCall.getVideoState() | VideoProfile.STATE_TX_ENABLED);
videoCall.sendSessionModifyRequest(videoProfile);
mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE);
}
getUi().setVideoPaused(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 (call == null) {
return;
}
updateButtonsState(call);
}
/**
* Updates the buttons applicable for the UI.
*
* @param call The active call.
*/
private void updateButtonsState(Call call) {
Log.v(this, "updateButtonsState");
final CallButtonUi ui = getUi();
final boolean isVideo = CallUtils.isVideoCall(call);
// Common functionality (audio, hold, etc).
// 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 showSwap = call.can(
android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
final boolean showHold = !showSwap
&& call.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD)
&& call.can(android.telecom.Call.Details.CAPABILITY_HOLD);
final boolean isCallOnHold = call.getState() == Call.State.ONHOLD;
final boolean showAddCall = TelecomAdapter.getInstance().canAddCall();
final boolean showMerge = call.can(
android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
final boolean showUpgradeToVideo = !isVideo &&
(call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX)
&& call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX));
final boolean showMute = call.can(android.telecom.Call.Details.CAPABILITY_MUTE);
ui.showButton(BUTTON_AUDIO, true);
ui.showButton(BUTTON_SWAP, showSwap);
ui.showButton(BUTTON_HOLD, showHold);
ui.setHold(isCallOnHold);
ui.showButton(BUTTON_MUTE, showMute);
ui.showButton(BUTTON_ADD_CALL, showAddCall);
ui.showButton(BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo);
ui.showButton(BUTTON_SWITCH_CAMERA, isVideo);
ui.showButton(BUTTON_PAUSE_VIDEO, isVideo);
ui.showButton(BUTTON_DIALPAD, !isVideo);
ui.showButton(BUTTON_MERGE, showMerge);
ui.updateButtonStates();
}
public void refreshMuteState() {
// Restore the previous mute state
if (mAutomaticallyMuted &&
AudioModeProvider.getInstance().getMute() != mPreviousMuteState) {
if (getUi() == null) {
return;
}
muteClicked(mPreviousMuteState);
}
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 showButton(int buttonId, boolean show);
void enableButton(int buttonId, boolean enable);
void setEnabled(boolean on);
void setMute(boolean on);
void setHold(boolean on);
void setCameraSwitched(boolean isBackFacingCamera);
void setVideoPaused(boolean isPaused);
void setAudio(int mode);
void setSupportedAudio(int mask);
void displayDialpad(boolean on, boolean animate);
boolean isDialpadVisible();
/**
* Once showButton() has been called on each of the individual buttons in the UI, call
* this to configure the overflow menu appropriately.
*/
void updateButtonStates();
Context getContext();
}
@Override
public void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera) {
if (getUi() == null) {
return;
}
getUi().setCameraSwitched(!isUsingFrontFacingCamera);
}
}