| /* |
| * 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(); |
| } |
| } |