1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.incallui; 18 19 import static com.android.incallui.CallButtonFragment.Buttons.*; 20 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.telecom.CallAudioState; 24 import android.telecom.InCallService.VideoCall; 25 import android.telecom.VideoProfile; 26 27 import com.android.incallui.AudioModeProvider.AudioModeListener; 28 import com.android.incallui.InCallCameraManager.Listener; 29 import com.android.incallui.InCallPresenter.CanAddCallListener; 30 import com.android.incallui.InCallPresenter.InCallState; 31 import com.android.incallui.InCallPresenter.InCallStateListener; 32 import com.android.incallui.InCallPresenter.IncomingCallListener; 33 import com.android.incallui.InCallPresenter.InCallDetailsListener; 34 35 import java.util.Objects; 36 37 /** 38 * Logic for call buttons. 39 */ 40 public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi> 41 implements InCallStateListener, AudioModeListener, IncomingCallListener, 42 InCallDetailsListener, CanAddCallListener, Listener { 43 44 private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted"; 45 private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state"; 46 47 private Call mCall; 48 private boolean mAutomaticallyMuted = false; 49 private boolean mPreviousMuteState = false; 50 CallButtonPresenter()51 public CallButtonPresenter() { 52 } 53 54 @Override onUiReady(CallButtonUi ui)55 public void onUiReady(CallButtonUi ui) { 56 super.onUiReady(ui); 57 58 AudioModeProvider.getInstance().addListener(this); 59 60 // register for call state changes last 61 final InCallPresenter inCallPresenter = InCallPresenter.getInstance(); 62 inCallPresenter.addListener(this); 63 inCallPresenter.addIncomingCallListener(this); 64 inCallPresenter.addDetailsListener(this); 65 inCallPresenter.addCanAddCallListener(this); 66 inCallPresenter.getInCallCameraManager().addCameraSelectionListener(this); 67 68 // Update the buttons state immediately for the current call 69 onStateChange(InCallState.NO_CALLS, inCallPresenter.getInCallState(), 70 CallList.getInstance()); 71 } 72 73 @Override onUiUnready(CallButtonUi ui)74 public void onUiUnready(CallButtonUi ui) { 75 super.onUiUnready(ui); 76 77 InCallPresenter.getInstance().removeListener(this); 78 AudioModeProvider.getInstance().removeListener(this); 79 InCallPresenter.getInstance().removeIncomingCallListener(this); 80 InCallPresenter.getInstance().removeDetailsListener(this); 81 InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this); 82 InCallPresenter.getInstance().removeCanAddCallListener(this); 83 } 84 85 @Override onStateChange(InCallState oldState, InCallState newState, CallList callList)86 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { 87 CallButtonUi ui = getUi(); 88 89 if (newState == InCallState.OUTGOING) { 90 mCall = callList.getOutgoingCall(); 91 } else if (newState == InCallState.INCALL) { 92 mCall = callList.getActiveOrBackgroundCall(); 93 94 // When connected to voice mail, automatically shows the dialpad. 95 // (On previous releases we showed it when in-call shows up, before waiting for 96 // OUTGOING. We may want to do that once we start showing "Voice mail" label on 97 // the dialpad too.) 98 if (ui != null) { 99 if (oldState == InCallState.OUTGOING && mCall != null) { 100 if (CallerInfoUtils.isVoiceMailNumber(ui.getContext(), mCall)) { 101 ui.displayDialpad(true /* show */, true /* animate */); 102 } 103 } 104 } 105 } else if (newState == InCallState.INCOMING) { 106 if (ui != null) { 107 ui.displayDialpad(false /* show */, true /* animate */); 108 } 109 mCall = callList.getIncomingCall(); 110 } else { 111 mCall = null; 112 } 113 updateUi(newState, mCall); 114 } 115 116 /** 117 * Updates the user interface in response to a change in the details of a call. 118 * Currently handles changes to the call buttons in response to a change in the details for a 119 * call. This is important to ensure changes to the active call are reflected in the available 120 * buttons. 121 * 122 * @param call The active call. 123 * @param details The call details. 124 */ 125 @Override onDetailsChanged(Call call, android.telecom.Call.Details details)126 public void onDetailsChanged(Call call, android.telecom.Call.Details details) { 127 // Only update if the changes are for the currently active call 128 if (getUi() != null && call != null && call.equals(mCall)) { 129 updateButtonsState(call); 130 } 131 } 132 133 @Override onIncomingCall(InCallState oldState, InCallState newState, Call call)134 public void onIncomingCall(InCallState oldState, InCallState newState, Call call) { 135 onStateChange(oldState, newState, CallList.getInstance()); 136 } 137 138 @Override onCanAddCallChanged(boolean canAddCall)139 public void onCanAddCallChanged(boolean canAddCall) { 140 if (getUi() != null && mCall != null) { 141 updateButtonsState(mCall); 142 } 143 } 144 145 @Override onAudioMode(int mode)146 public void onAudioMode(int mode) { 147 if (getUi() != null) { 148 getUi().setAudio(mode); 149 } 150 } 151 152 @Override onSupportedAudioMode(int mask)153 public void onSupportedAudioMode(int mask) { 154 if (getUi() != null) { 155 getUi().setSupportedAudio(mask); 156 } 157 } 158 159 @Override onMute(boolean muted)160 public void onMute(boolean muted) { 161 if (getUi() != null && !mAutomaticallyMuted) { 162 getUi().setMute(muted); 163 } 164 } 165 getAudioMode()166 public int getAudioMode() { 167 return AudioModeProvider.getInstance().getAudioMode(); 168 } 169 getSupportedAudio()170 public int getSupportedAudio() { 171 return AudioModeProvider.getInstance().getSupportedModes(); 172 } 173 setAudioMode(int mode)174 public void setAudioMode(int mode) { 175 176 // TODO: Set a intermediate state in this presenter until we get 177 // an update for onAudioMode(). This will make UI response immediate 178 // if it turns out to be slow 179 180 Log.d(this, "Sending new Audio Mode: " + CallAudioState.audioRouteToString(mode)); 181 TelecomAdapter.getInstance().setAudioRoute(mode); 182 } 183 184 /** 185 * Function assumes that bluetooth is not supported. 186 */ toggleSpeakerphone()187 public void toggleSpeakerphone() { 188 // this function should not be called if bluetooth is available 189 if (0 != (CallAudioState.ROUTE_BLUETOOTH & getSupportedAudio())) { 190 191 // It's clear the UI is wrong, so update the supported mode once again. 192 Log.e(this, "toggling speakerphone not allowed when bluetooth supported."); 193 getUi().setSupportedAudio(getSupportedAudio()); 194 return; 195 } 196 197 int newMode = CallAudioState.ROUTE_SPEAKER; 198 199 // if speakerphone is already on, change to wired/earpiece 200 if (getAudioMode() == CallAudioState.ROUTE_SPEAKER) { 201 newMode = CallAudioState.ROUTE_WIRED_OR_EARPIECE; 202 } 203 204 setAudioMode(newMode); 205 } 206 muteClicked(boolean checked)207 public void muteClicked(boolean checked) { 208 Log.d(this, "turning on mute: " + checked); 209 TelecomAdapter.getInstance().mute(checked); 210 } 211 holdClicked(boolean checked)212 public void holdClicked(boolean checked) { 213 if (mCall == null) { 214 return; 215 } 216 if (checked) { 217 Log.i(this, "Putting the call on hold: " + mCall); 218 TelecomAdapter.getInstance().holdCall(mCall.getId()); 219 } else { 220 Log.i(this, "Removing the call from hold: " + mCall); 221 TelecomAdapter.getInstance().unholdCall(mCall.getId()); 222 } 223 } 224 swapClicked()225 public void swapClicked() { 226 if (mCall == null) { 227 return; 228 } 229 230 Log.i(this, "Swapping the call: " + mCall); 231 TelecomAdapter.getInstance().swap(mCall.getId()); 232 } 233 mergeClicked()234 public void mergeClicked() { 235 TelecomAdapter.getInstance().merge(mCall.getId()); 236 } 237 addCallClicked()238 public void addCallClicked() { 239 // Automatically mute the current call 240 mAutomaticallyMuted = true; 241 mPreviousMuteState = AudioModeProvider.getInstance().getMute(); 242 // Simulate a click on the mute button 243 muteClicked(true); 244 TelecomAdapter.getInstance().addCall(); 245 } 246 changeToVoiceClicked()247 public void changeToVoiceClicked() { 248 VideoCall videoCall = mCall.getVideoCall(); 249 if (videoCall == null) { 250 return; 251 } 252 253 VideoProfile videoProfile = new VideoProfile( 254 VideoProfile.STATE_AUDIO_ONLY, VideoProfile.QUALITY_DEFAULT); 255 videoCall.sendSessionModifyRequest(videoProfile); 256 } 257 showDialpadClicked(boolean checked)258 public void showDialpadClicked(boolean checked) { 259 Log.v(this, "Show dialpad " + String.valueOf(checked)); 260 getUi().displayDialpad(checked /* show */, true /* animate */); 261 } 262 changeToVideoClicked()263 public void changeToVideoClicked() { 264 VideoCall videoCall = mCall.getVideoCall(); 265 if (videoCall == null) { 266 return; 267 } 268 int currVideoState = mCall.getVideoState(); 269 int currUnpausedVideoState = CallUtils.getUnPausedVideoState(currVideoState); 270 currUnpausedVideoState |= VideoProfile.STATE_BIDIRECTIONAL; 271 272 VideoProfile videoProfile = new VideoProfile(currUnpausedVideoState); 273 videoCall.sendSessionModifyRequest(videoProfile); 274 mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE); 275 } 276 277 /** 278 * Switches the camera between the front-facing and back-facing camera. 279 * @param useFrontFacingCamera True if we should switch to using the front-facing camera, or 280 * false if we should switch to using the back-facing camera. 281 */ switchCameraClicked(boolean useFrontFacingCamera)282 public void switchCameraClicked(boolean useFrontFacingCamera) { 283 InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager(); 284 cameraManager.setUseFrontFacingCamera(useFrontFacingCamera); 285 286 VideoCall videoCall = mCall.getVideoCall(); 287 if (videoCall == null) { 288 return; 289 } 290 291 String cameraId = cameraManager.getActiveCameraId(); 292 if (cameraId != null) { 293 final int cameraDir = cameraManager.isUsingFrontFacingCamera() 294 ? Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING 295 : Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING; 296 mCall.getVideoSettings().setCameraDir(cameraDir); 297 videoCall.setCamera(cameraId); 298 videoCall.requestCameraCapabilities(); 299 } 300 } 301 302 303 /** 304 * Stop or start client's video transmission. 305 * @param pause True if pausing the local user's video, or false if starting the local user's 306 * video. 307 */ pauseVideoClicked(boolean pause)308 public void pauseVideoClicked(boolean pause) { 309 VideoCall videoCall = mCall.getVideoCall(); 310 if (videoCall == null) { 311 return; 312 } 313 314 if (pause) { 315 videoCall.setCamera(null); 316 VideoProfile videoProfile = new VideoProfile( 317 mCall.getVideoState() & ~VideoProfile.STATE_TX_ENABLED); 318 videoCall.sendSessionModifyRequest(videoProfile); 319 } else { 320 InCallCameraManager cameraManager = InCallPresenter.getInstance(). 321 getInCallCameraManager(); 322 videoCall.setCamera(cameraManager.getActiveCameraId()); 323 VideoProfile videoProfile = new VideoProfile( 324 mCall.getVideoState() | VideoProfile.STATE_TX_ENABLED); 325 videoCall.sendSessionModifyRequest(videoProfile); 326 mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE); 327 } 328 getUi().setVideoPaused(pause); 329 } 330 updateUi(InCallState state, Call call)331 private void updateUi(InCallState state, Call call) { 332 Log.d(this, "Updating call UI for call: ", call); 333 334 final CallButtonUi ui = getUi(); 335 if (ui == null) { 336 return; 337 } 338 339 final boolean isEnabled = 340 state.isConnectingOrConnected() &&!state.isIncoming() && call != null; 341 ui.setEnabled(isEnabled); 342 343 if (call == null) { 344 return; 345 } 346 347 updateButtonsState(call); 348 } 349 350 /** 351 * Updates the buttons applicable for the UI. 352 * 353 * @param call The active call. 354 */ updateButtonsState(Call call)355 private void updateButtonsState(Call call) { 356 Log.v(this, "updateButtonsState"); 357 final CallButtonUi ui = getUi(); 358 359 final boolean isVideo = CallUtils.isVideoCall(call); 360 361 // Common functionality (audio, hold, etc). 362 // Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available: 363 // (1) If the device normally can hold, show HOLD in a disabled state. 364 // (2) If the device doesn't have the concept of hold/swap, remove the button. 365 final boolean showSwap = call.can( 366 android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE); 367 final boolean showHold = !showSwap 368 && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD) 369 && call.can(android.telecom.Call.Details.CAPABILITY_HOLD); 370 final boolean isCallOnHold = call.getState() == Call.State.ONHOLD; 371 372 final boolean showAddCall = TelecomAdapter.getInstance().canAddCall(); 373 final boolean showMerge = call.can( 374 android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE); 375 final boolean showUpgradeToVideo = !isVideo && 376 (call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX) 377 && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX)); 378 379 final boolean showMute = call.can(android.telecom.Call.Details.CAPABILITY_MUTE); 380 381 ui.showButton(BUTTON_AUDIO, true); 382 ui.showButton(BUTTON_SWAP, showSwap); 383 ui.showButton(BUTTON_HOLD, showHold); 384 ui.setHold(isCallOnHold); 385 ui.showButton(BUTTON_MUTE, showMute); 386 ui.showButton(BUTTON_ADD_CALL, showAddCall); 387 ui.showButton(BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo); 388 ui.showButton(BUTTON_SWITCH_CAMERA, isVideo); 389 ui.showButton(BUTTON_PAUSE_VIDEO, isVideo); 390 ui.showButton(BUTTON_DIALPAD, !isVideo); 391 ui.showButton(BUTTON_MERGE, showMerge); 392 393 ui.updateButtonStates(); 394 } 395 refreshMuteState()396 public void refreshMuteState() { 397 // Restore the previous mute state 398 if (mAutomaticallyMuted && 399 AudioModeProvider.getInstance().getMute() != mPreviousMuteState) { 400 if (getUi() == null) { 401 return; 402 } 403 muteClicked(mPreviousMuteState); 404 } 405 mAutomaticallyMuted = false; 406 } 407 408 @Override onSaveInstanceState(Bundle outState)409 public void onSaveInstanceState(Bundle outState) { 410 super.onSaveInstanceState(outState); 411 outState.putBoolean(KEY_AUTOMATICALLY_MUTED, mAutomaticallyMuted); 412 outState.putBoolean(KEY_PREVIOUS_MUTE_STATE, mPreviousMuteState); 413 } 414 415 @Override onRestoreInstanceState(Bundle savedInstanceState)416 public void onRestoreInstanceState(Bundle savedInstanceState) { 417 mAutomaticallyMuted = 418 savedInstanceState.getBoolean(KEY_AUTOMATICALLY_MUTED, mAutomaticallyMuted); 419 mPreviousMuteState = 420 savedInstanceState.getBoolean(KEY_PREVIOUS_MUTE_STATE, mPreviousMuteState); 421 super.onRestoreInstanceState(savedInstanceState); 422 } 423 424 public interface CallButtonUi extends Ui { showButton(int buttonId, boolean show)425 void showButton(int buttonId, boolean show); enableButton(int buttonId, boolean enable)426 void enableButton(int buttonId, boolean enable); setEnabled(boolean on)427 void setEnabled(boolean on); setMute(boolean on)428 void setMute(boolean on); setHold(boolean on)429 void setHold(boolean on); setCameraSwitched(boolean isBackFacingCamera)430 void setCameraSwitched(boolean isBackFacingCamera); setVideoPaused(boolean isPaused)431 void setVideoPaused(boolean isPaused); setAudio(int mode)432 void setAudio(int mode); setSupportedAudio(int mask)433 void setSupportedAudio(int mask); displayDialpad(boolean on, boolean animate)434 void displayDialpad(boolean on, boolean animate); isDialpadVisible()435 boolean isDialpadVisible(); 436 437 /** 438 * Once showButton() has been called on each of the individual buttons in the UI, call 439 * this to configure the overflow menu appropriately. 440 */ updateButtonStates()441 void updateButtonStates(); getContext()442 Context getContext(); 443 } 444 445 @Override onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera)446 public void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera) { 447 if (getUi() == null) { 448 return; 449 } 450 getUi().setCameraSwitched(!isUsingFrontFacingCamera); 451 } 452 } 453