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 android.content.Context; 20 import android.telecom.AudioState; 21 import android.telecom.InCallService.VideoCall; 22 import android.telecom.PhoneCapabilities; 23 import android.telecom.VideoProfile; 24 25 26 import com.android.incallui.AudioModeProvider.AudioModeListener; 27 import com.android.incallui.InCallPresenter.InCallState; 28 import com.android.incallui.InCallPresenter.InCallStateListener; 29 import com.android.incallui.InCallPresenter.IncomingCallListener; 30 import com.android.incallui.InCallPresenter.InCallDetailsListener; 31 32 import android.telephony.PhoneNumberUtils; 33 34 import java.util.Objects; 35 36 /** 37 * Logic for call buttons. 38 */ 39 public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi> 40 implements InCallStateListener, AudioModeListener, IncomingCallListener, 41 InCallDetailsListener { 42 43 private Call mCall; 44 private boolean mAutomaticallyMuted = false; 45 private boolean mPreviousMuteState = false; 46 47 public CallButtonPresenter() { 48 } 49 50 @Override 51 public void onUiReady(CallButtonUi ui) { 52 super.onUiReady(ui); 53 54 AudioModeProvider.getInstance().addListener(this); 55 56 // register for call state changes last 57 InCallPresenter.getInstance().addListener(this); 58 InCallPresenter.getInstance().addIncomingCallListener(this); 59 InCallPresenter.getInstance().addDetailsListener(this); 60 } 61 62 @Override 63 public void onUiUnready(CallButtonUi ui) { 64 super.onUiUnready(ui); 65 66 InCallPresenter.getInstance().removeListener(this); 67 AudioModeProvider.getInstance().removeListener(this); 68 InCallPresenter.getInstance().removeIncomingCallListener(this); 69 InCallPresenter.getInstance().removeDetailsListener(this); 70 } 71 72 @Override 73 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { 74 CallButtonUi ui = getUi(); 75 76 if (newState == InCallState.OUTGOING) { 77 mCall = callList.getOutgoingCall(); 78 } else if (newState == InCallState.INCALL) { 79 mCall = callList.getActiveOrBackgroundCall(); 80 81 // When connected to voice mail, automatically shows the dialpad. 82 // (On previous releases we showed it when in-call shows up, before waiting for 83 // OUTGOING. We may want to do that once we start showing "Voice mail" label on 84 // the dialpad too.) 85 if (ui != null) { 86 if (oldState == InCallState.OUTGOING && mCall != null 87 && PhoneNumberUtils.isVoiceMailNumber(mCall.getNumber())) { 88 ui.displayDialpad(true /* show */, true /* animate */); 89 } 90 } 91 } else if (newState == InCallState.INCOMING) { 92 if (ui != null) { 93 ui.displayDialpad(false /* show */, true /* animate */); 94 } 95 mCall = null; 96 } else { 97 mCall = null; 98 } 99 updateUi(newState, mCall); 100 } 101 102 /** 103 * Updates the user interface in response to a change in the details of a call. 104 * Currently handles changes to the call buttons in response to a change in the details for a 105 * call. This is important to ensure changes to the active call are reflected in the available 106 * buttons. 107 * 108 * @param call The active call. 109 * @param details The call details. 110 */ 111 @Override 112 public void onDetailsChanged(Call call, android.telecom.Call.Details details) { 113 // If the details change is not for the currently active call no update is required. 114 if (!Objects.equals(call, mCall)) { 115 return; 116 } 117 118 updateCallButtons(call, getUi().getContext()); 119 } 120 121 @Override 122 public void onIncomingCall(InCallState oldState, InCallState newState, Call call) { 123 onStateChange(oldState, newState, CallList.getInstance()); 124 } 125 126 @Override 127 public void onAudioMode(int mode) { 128 if (getUi() != null) { 129 getUi().setAudio(mode); 130 } 131 } 132 133 @Override 134 public void onSupportedAudioMode(int mask) { 135 if (getUi() != null) { 136 getUi().setSupportedAudio(mask); 137 } 138 } 139 140 @Override 141 public void onMute(boolean muted) { 142 if (getUi() != null && !mAutomaticallyMuted) { 143 getUi().setMute(muted); 144 } 145 } 146 147 public int getAudioMode() { 148 return AudioModeProvider.getInstance().getAudioMode(); 149 } 150 151 public int getSupportedAudio() { 152 return AudioModeProvider.getInstance().getSupportedModes(); 153 } 154 155 public void setAudioMode(int mode) { 156 157 // TODO: Set a intermediate state in this presenter until we get 158 // an update for onAudioMode(). This will make UI response immediate 159 // if it turns out to be slow 160 161 Log.d(this, "Sending new Audio Mode: " + AudioState.audioRouteToString(mode)); 162 TelecomAdapter.getInstance().setAudioRoute(mode); 163 } 164 165 /** 166 * Function assumes that bluetooth is not supported. 167 */ 168 public void toggleSpeakerphone() { 169 // this function should not be called if bluetooth is available 170 if (0 != (AudioState.ROUTE_BLUETOOTH & getSupportedAudio())) { 171 172 // It's clear the UI is wrong, so update the supported mode once again. 173 Log.e(this, "toggling speakerphone not allowed when bluetooth supported."); 174 getUi().setSupportedAudio(getSupportedAudio()); 175 return; 176 } 177 178 int newMode = AudioState.ROUTE_SPEAKER; 179 180 // if speakerphone is already on, change to wired/earpiece 181 if (getAudioMode() == AudioState.ROUTE_SPEAKER) { 182 newMode = AudioState.ROUTE_WIRED_OR_EARPIECE; 183 } 184 185 setAudioMode(newMode); 186 } 187 188 public void muteClicked(boolean checked) { 189 Log.d(this, "turning on mute: " + checked); 190 TelecomAdapter.getInstance().mute(checked); 191 } 192 193 public void holdClicked(boolean checked) { 194 if (mCall == null) { 195 return; 196 } 197 if (checked) { 198 Log.i(this, "Putting the call on hold: " + mCall); 199 TelecomAdapter.getInstance().holdCall(mCall.getId()); 200 } else { 201 Log.i(this, "Removing the call from hold: " + mCall); 202 TelecomAdapter.getInstance().unholdCall(mCall.getId()); 203 } 204 } 205 206 public void swapClicked() { 207 if (mCall == null) { 208 return; 209 } 210 211 Log.i(this, "Swapping the call: " + mCall); 212 TelecomAdapter.getInstance().swap(mCall.getId()); 213 } 214 215 public void mergeClicked() { 216 TelecomAdapter.getInstance().merge(mCall.getId()); 217 } 218 219 public void addCallClicked() { 220 // Automatically mute the current call 221 mAutomaticallyMuted = true; 222 mPreviousMuteState = AudioModeProvider.getInstance().getMute(); 223 // Simulate a click on the mute button 224 muteClicked(true); 225 226 TelecomAdapter.getInstance().addCall(); 227 } 228 229 public void changeToVoiceClicked() { 230 VideoCall videoCall = mCall.getVideoCall(); 231 if (videoCall == null) { 232 return; 233 } 234 235 VideoProfile videoProfile = new VideoProfile( 236 VideoProfile.VideoState.AUDIO_ONLY, VideoProfile.QUALITY_DEFAULT); 237 videoCall.sendSessionModifyRequest(videoProfile); 238 } 239 240 public void showDialpadClicked(boolean checked) { 241 Log.v(this, "Show dialpad " + String.valueOf(checked)); 242 getUi().displayDialpad(checked /* show */, true /* animate */); 243 } 244 245 public void changeToVideoClicked() { 246 VideoCall videoCall = mCall.getVideoCall(); 247 if (videoCall == null) { 248 return; 249 } 250 251 VideoProfile videoProfile = 252 new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL); 253 videoCall.sendSessionModifyRequest(videoProfile); 254 255 mCall.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED); 256 } 257 258 /** 259 * Switches the camera between the front-facing and back-facing camera. 260 * @param useFrontFacingCamera True if we should switch to using the front-facing camera, or 261 * false if we should switch to using the back-facing camera. 262 */ 263 public void switchCameraClicked(boolean useFrontFacingCamera) { 264 InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager(); 265 cameraManager.setUseFrontFacingCamera(useFrontFacingCamera); 266 267 VideoCall videoCall = mCall.getVideoCall(); 268 if (videoCall == null) { 269 return; 270 } 271 272 String cameraId = cameraManager.getActiveCameraId(); 273 if (cameraId != null) { 274 videoCall.setCamera(cameraId); 275 videoCall.requestCameraCapabilities(); 276 } 277 getUi().setSwitchCameraButton(!useFrontFacingCamera); 278 } 279 280 /** 281 * Stop or start client's video transmission. 282 * @param pause True if pausing the local user's video, or false if starting the local user's 283 * video. 284 */ 285 public void pauseVideoClicked(boolean pause) { 286 VideoCall videoCall = mCall.getVideoCall(); 287 if (videoCall == null) { 288 return; 289 } 290 291 if (pause) { 292 videoCall.setCamera(null); 293 VideoProfile videoProfile = new VideoProfile( 294 mCall.getVideoState() | VideoProfile.VideoState.PAUSED); 295 videoCall.sendSessionModifyRequest(videoProfile); 296 } else { 297 InCallCameraManager cameraManager = InCallPresenter.getInstance(). 298 getInCallCameraManager(); 299 videoCall.setCamera(cameraManager.getActiveCameraId()); 300 VideoProfile videoProfile = new VideoProfile( 301 mCall.getVideoState() & ~VideoProfile.VideoState.PAUSED); 302 videoCall.sendSessionModifyRequest(videoProfile); 303 } 304 getUi().setPauseVideoButton(pause); 305 } 306 307 private void updateUi(InCallState state, Call call) { 308 Log.d(this, "Updating call UI for call: ", call); 309 310 final CallButtonUi ui = getUi(); 311 if (ui == null) { 312 return; 313 } 314 315 final boolean isEnabled = 316 state.isConnectingOrConnected() &&!state.isIncoming() && call != null; 317 ui.setEnabled(isEnabled); 318 319 if (!isEnabled) { 320 return; 321 } 322 323 updateCallButtons(call, ui.getContext()); 324 325 ui.enableMute(call.can(PhoneCapabilities.MUTE)); 326 } 327 328 /** 329 * Updates the buttons applicable for the UI. 330 * 331 * @param call The active call. 332 * @param context The context. 333 */ 334 private void updateCallButtons(Call call, Context context) { 335 if (call.isVideoCall(context)) { 336 updateVideoCallButtons(); 337 } else { 338 updateVoiceCallButtons(call); 339 } 340 } 341 342 private void updateVideoCallButtons() { 343 Log.v(this, "Showing buttons for video call."); 344 final CallButtonUi ui = getUi(); 345 346 // Hide all voice-call-related buttons. 347 ui.showAudioButton(false); 348 ui.showDialpadButton(false); 349 ui.showHoldButton(false); 350 ui.showSwapButton(false); 351 ui.showChangeToVideoButton(false); 352 ui.showAddCallButton(false); 353 ui.showMergeButton(false); 354 ui.showOverflowButton(false); 355 356 // Show all video-call-related buttons. 357 ui.showChangeToVoiceButton(true); 358 ui.showSwitchCameraButton(true); 359 ui.showPauseVideoButton(true); 360 } 361 362 private void updateVoiceCallButtons(Call call) { 363 Log.v(this, "Showing buttons for voice call."); 364 final CallButtonUi ui = getUi(); 365 366 // Hide all video-call-related buttons. 367 ui.showChangeToVoiceButton(false); 368 ui.showSwitchCameraButton(false); 369 ui.showPauseVideoButton(false); 370 371 // Show all voice-call-related buttons. 372 ui.showAudioButton(true); 373 ui.showDialpadButton(true); 374 375 Log.v(this, "Show hold ", call.can(PhoneCapabilities.SUPPORT_HOLD)); 376 Log.v(this, "Enable hold", call.can(PhoneCapabilities.HOLD)); 377 Log.v(this, "Show merge ", call.can(PhoneCapabilities.MERGE_CONFERENCE)); 378 Log.v(this, "Show swap ", call.can(PhoneCapabilities.SWAP_CONFERENCE)); 379 Log.v(this, "Show add call ", call.can(PhoneCapabilities.ADD_CALL)); 380 Log.v(this, "Show mute ", call.can(PhoneCapabilities.MUTE)); 381 382 final boolean canAdd = call.can(PhoneCapabilities.ADD_CALL); 383 final boolean enableHoldOption = call.can(PhoneCapabilities.HOLD); 384 final boolean supportHold = call.can(PhoneCapabilities.SUPPORT_HOLD); 385 386 boolean canVideoCall = call.can(PhoneCapabilities.SUPPORTS_VT_LOCAL) 387 && call.can(PhoneCapabilities.SUPPORTS_VT_REMOTE); 388 ui.showChangeToVideoButton(canVideoCall); 389 390 final boolean showMergeOption = call.can(PhoneCapabilities.MERGE_CONFERENCE); 391 final boolean showAddCallOption = canAdd; 392 393 // Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available: 394 // (1) If the device normally can hold, show HOLD in a disabled state. 395 // (2) If the device doesn't have the concept of hold/swap, remove the button. 396 final boolean showSwapOption = call.can(PhoneCapabilities.SWAP_CONFERENCE); 397 final boolean showHoldOption = !showSwapOption && (enableHoldOption || supportHold); 398 399 ui.setHold(call.getState() == Call.State.ONHOLD); 400 // If we show video upgrade and add/merge and hold/swap, the overflow menu is needed. 401 final boolean isVideoOverflowScenario = canVideoCall 402 && (showAddCallOption || showMergeOption) && (showHoldOption || showSwapOption); 403 // If we show hold/swap, add, and merge simultaneously, the overflow menu is needed. 404 final boolean isCdmaConferenceOverflowScenario = 405 (showHoldOption || showSwapOption) && showMergeOption && showAddCallOption; 406 407 if (isVideoOverflowScenario) { 408 ui.showHoldButton(false); 409 ui.showSwapButton(false); 410 ui.showAddCallButton(false); 411 ui.showMergeButton(false); 412 413 ui.showOverflowButton(true); 414 ui.configureOverflowMenu( 415 showMergeOption, 416 showAddCallOption /* showAddMenuOption */, 417 showHoldOption && enableHoldOption /* showHoldMenuOption */, 418 showSwapOption); 419 } else { 420 if (isCdmaConferenceOverflowScenario) { 421 ui.showAddCallButton(false); 422 ui.showMergeButton(false); 423 424 ui.configureOverflowMenu( 425 showMergeOption, 426 showAddCallOption /* showAddMenuOption */, 427 false /* showHoldMenuOption */, 428 false /* showSwapMenuOption */); 429 } else { 430 ui.showMergeButton(showMergeOption); 431 ui.showAddCallButton(showAddCallOption); 432 } 433 434 ui.showHoldButton(showHoldOption); 435 ui.enableHold(enableHoldOption); 436 ui.showSwapButton(showSwapOption); 437 } 438 } 439 440 public void refreshMuteState() { 441 // Restore the previous mute state 442 if (mAutomaticallyMuted && 443 AudioModeProvider.getInstance().getMute() != mPreviousMuteState) { 444 if (getUi() == null) { 445 return; 446 } 447 muteClicked(mPreviousMuteState); 448 } 449 mAutomaticallyMuted = false; 450 } 451 452 public interface CallButtonUi extends Ui { 453 void setEnabled(boolean on); 454 void setMute(boolean on); 455 void enableMute(boolean enabled); 456 void showAudioButton(boolean show); 457 void showChangeToVoiceButton(boolean show); 458 void showDialpadButton(boolean show); 459 void setHold(boolean on); 460 void showHoldButton(boolean show); 461 void enableHold(boolean enabled); 462 void showSwapButton(boolean show); 463 void showChangeToVideoButton(boolean show); 464 void showSwitchCameraButton(boolean show); 465 void setSwitchCameraButton(boolean isBackFacingCamera); 466 void showAddCallButton(boolean show); 467 void showMergeButton(boolean show); 468 void showPauseVideoButton(boolean show); 469 void setPauseVideoButton(boolean isPaused); 470 void showOverflowButton(boolean show); 471 void displayDialpad(boolean on, boolean animate); 472 boolean isDialpadVisible(); 473 void setAudio(int mode); 474 void setSupportedAudio(int mask); 475 void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption, 476 boolean showHoldMenuOption, boolean showSwapMenuOption); 477 Context getContext(); 478 } 479 } 480