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