• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.Bundle;
21 import android.support.v4.app.Fragment;
22 import android.support.v4.os.UserManagerCompat;
23 import android.telecom.CallAudioState;
24 import com.android.contacts.common.compat.CallCompat;
25 import com.android.dialer.common.Assert;
26 import com.android.dialer.common.LogUtil;
27 import com.android.dialer.logging.DialerImpression;
28 import com.android.dialer.logging.Logger;
29 import com.android.incallui.InCallCameraManager.Listener;
30 import com.android.incallui.InCallPresenter.CanAddCallListener;
31 import com.android.incallui.InCallPresenter.InCallDetailsListener;
32 import com.android.incallui.InCallPresenter.InCallState;
33 import com.android.incallui.InCallPresenter.InCallStateListener;
34 import com.android.incallui.InCallPresenter.IncomingCallListener;
35 import com.android.incallui.audiomode.AudioModeProvider;
36 import com.android.incallui.audiomode.AudioModeProvider.AudioModeListener;
37 import com.android.incallui.call.CallList;
38 import com.android.incallui.call.DialerCall;
39 import com.android.incallui.call.DialerCall.CameraDirection;
40 import com.android.incallui.call.TelecomAdapter;
41 import com.android.incallui.incall.protocol.InCallButtonIds;
42 import com.android.incallui.incall.protocol.InCallButtonUi;
43 import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
44 import com.android.incallui.videotech.utils.VideoUtils;
45 
46 /** Logic for call buttons. */
47 public class CallButtonPresenter
48     implements InCallStateListener,
49         AudioModeListener,
50         IncomingCallListener,
51         InCallDetailsListener,
52         CanAddCallListener,
53         Listener,
54         InCallButtonUiDelegate {
55 
56   private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted";
57   private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state";
58 
59   private final Context mContext;
60   private InCallButtonUi mInCallButtonUi;
61   private DialerCall mCall;
62   private boolean mAutomaticallyMuted = false;
63   private boolean mPreviousMuteState = false;
64   private boolean isInCallButtonUiReady;
65 
CallButtonPresenter(Context context)66   public CallButtonPresenter(Context context) {
67     mContext = context.getApplicationContext();
68   }
69 
70   @Override
onInCallButtonUiReady(InCallButtonUi ui)71   public void onInCallButtonUiReady(InCallButtonUi ui) {
72     Assert.checkState(!isInCallButtonUiReady);
73     mInCallButtonUi = ui;
74     AudioModeProvider.getInstance().addListener(this);
75 
76     // register for call state changes last
77     final InCallPresenter inCallPresenter = InCallPresenter.getInstance();
78     inCallPresenter.addListener(this);
79     inCallPresenter.addIncomingCallListener(this);
80     inCallPresenter.addDetailsListener(this);
81     inCallPresenter.addCanAddCallListener(this);
82     inCallPresenter.getInCallCameraManager().addCameraSelectionListener(this);
83 
84     // Update the buttons state immediately for the current call
85     onStateChange(InCallState.NO_CALLS, inCallPresenter.getInCallState(), CallList.getInstance());
86     isInCallButtonUiReady = true;
87   }
88 
89   @Override
onInCallButtonUiUnready()90   public void onInCallButtonUiUnready() {
91     Assert.checkState(isInCallButtonUiReady);
92     mInCallButtonUi = null;
93     InCallPresenter.getInstance().removeListener(this);
94     AudioModeProvider.getInstance().removeListener(this);
95     InCallPresenter.getInstance().removeIncomingCallListener(this);
96     InCallPresenter.getInstance().removeDetailsListener(this);
97     InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this);
98     InCallPresenter.getInstance().removeCanAddCallListener(this);
99     isInCallButtonUiReady = false;
100   }
101 
102   @Override
onStateChange(InCallState oldState, InCallState newState, CallList callList)103   public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
104     if (newState == InCallState.OUTGOING) {
105       mCall = callList.getOutgoingCall();
106     } else if (newState == InCallState.INCALL) {
107       mCall = callList.getActiveOrBackgroundCall();
108 
109       // When connected to voice mail, automatically shows the dialpad.
110       // (On previous releases we showed it when in-call shows up, before waiting for
111       // OUTGOING.  We may want to do that once we start showing "Voice mail" label on
112       // the dialpad too.)
113       if (oldState == InCallState.OUTGOING && mCall != null) {
114         if (CallerInfoUtils.isVoiceMailNumber(mContext, mCall) && getActivity() != null) {
115           getActivity().showDialpadFragment(true /* show */, true /* animate */);
116         }
117       }
118     } else if (newState == InCallState.INCOMING) {
119       if (getActivity() != null) {
120         getActivity().showDialpadFragment(false /* show */, true /* animate */);
121       }
122       mCall = callList.getIncomingCall();
123     } else {
124       mCall = null;
125     }
126     updateUi(newState, mCall);
127   }
128 
129   /**
130    * Updates the user interface in response to a change in the details of a call. Currently handles
131    * changes to the call buttons in response to a change in the details for a call. This is
132    * important to ensure changes to the active call are reflected in the available buttons.
133    *
134    * @param call The active call.
135    * @param details The call details.
136    */
137   @Override
onDetailsChanged(DialerCall call, android.telecom.Call.Details details)138   public void onDetailsChanged(DialerCall call, android.telecom.Call.Details details) {
139     // Only update if the changes are for the currently active call
140     if (mInCallButtonUi != null && call != null && call.equals(mCall)) {
141       updateButtonsState(call);
142     }
143   }
144 
145   @Override
onIncomingCall(InCallState oldState, InCallState newState, DialerCall call)146   public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
147     onStateChange(oldState, newState, CallList.getInstance());
148   }
149 
150   @Override
onCanAddCallChanged(boolean canAddCall)151   public void onCanAddCallChanged(boolean canAddCall) {
152     if (mInCallButtonUi != null && mCall != null) {
153       updateButtonsState(mCall);
154     }
155   }
156 
157   @Override
onAudioStateChanged(CallAudioState audioState)158   public void onAudioStateChanged(CallAudioState audioState) {
159     if (mInCallButtonUi != null) {
160       mInCallButtonUi.setAudioState(audioState);
161     }
162   }
163 
164   @Override
getCurrentAudioState()165   public CallAudioState getCurrentAudioState() {
166     return AudioModeProvider.getInstance().getAudioState();
167   }
168 
169   @Override
setAudioRoute(int route)170   public void setAudioRoute(int route) {
171     LogUtil.i(
172         "CallButtonPresenter.setAudioRoute",
173         "sending new audio route: " + CallAudioState.audioRouteToString(route));
174     TelecomAdapter.getInstance().setAudioRoute(route);
175   }
176 
177   /** Function assumes that bluetooth is not supported. */
178   @Override
toggleSpeakerphone()179   public void toggleSpeakerphone() {
180     // This function should not be called if bluetooth is available.
181     CallAudioState audioState = getCurrentAudioState();
182     if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) {
183       // It's clear the UI is wrong, so update the supported mode once again.
184       LogUtil.e(
185           "CallButtonPresenter", "toggling speakerphone not allowed when bluetooth supported.");
186       mInCallButtonUi.setAudioState(audioState);
187       return;
188     }
189 
190     int newRoute;
191     if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
192       newRoute = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
193       Logger.get(mContext)
194           .logCallImpression(
195               DialerImpression.Type.IN_CALL_SCREEN_TURN_ON_WIRED_OR_EARPIECE,
196               mCall.getUniqueCallId(),
197               mCall.getTimeAddedMs());
198     } else {
199       newRoute = CallAudioState.ROUTE_SPEAKER;
200       Logger.get(mContext)
201           .logCallImpression(
202               DialerImpression.Type.IN_CALL_SCREEN_TURN_ON_SPEAKERPHONE,
203               mCall.getUniqueCallId(),
204               mCall.getTimeAddedMs());
205     }
206 
207     setAudioRoute(newRoute);
208   }
209 
210   @Override
muteClicked(boolean checked, boolean clickedByUser)211   public void muteClicked(boolean checked, boolean clickedByUser) {
212     LogUtil.i(
213         "CallButtonPresenter", "turning on mute: %s, clicked by user: %s", checked, clickedByUser);
214     if (clickedByUser) {
215       Logger.get(mContext)
216           .logCallImpression(
217               checked
218                   ? DialerImpression.Type.IN_CALL_SCREEN_TURN_ON_MUTE
219                   : DialerImpression.Type.IN_CALL_SCREEN_TURN_OFF_MUTE,
220               mCall.getUniqueCallId(),
221               mCall.getTimeAddedMs());
222     }
223     TelecomAdapter.getInstance().mute(checked);
224   }
225 
226   @Override
holdClicked(boolean checked)227   public void holdClicked(boolean checked) {
228     if (mCall == null) {
229       return;
230     }
231     if (checked) {
232       LogUtil.i("CallButtonPresenter", "putting the call on hold: " + mCall);
233       mCall.hold();
234     } else {
235       LogUtil.i("CallButtonPresenter", "removing the call from hold: " + mCall);
236       mCall.unhold();
237     }
238   }
239 
240   @Override
swapClicked()241   public void swapClicked() {
242     if (mCall == null) {
243       return;
244     }
245 
246     LogUtil.i("CallButtonPresenter", "swapping the call: " + mCall);
247     TelecomAdapter.getInstance().swap(mCall.getId());
248   }
249 
250   @Override
mergeClicked()251   public void mergeClicked() {
252     TelecomAdapter.getInstance().merge(mCall.getId());
253   }
254 
255   @Override
addCallClicked()256   public void addCallClicked() {
257     // Automatically mute the current call
258     mAutomaticallyMuted = true;
259     mPreviousMuteState = AudioModeProvider.getInstance().getAudioState().isMuted();
260     // Simulate a click on the mute button
261     muteClicked(true /* checked */, false /* clickedByUser */);
262     TelecomAdapter.getInstance().addCall();
263   }
264 
265   @Override
showDialpadClicked(boolean checked)266   public void showDialpadClicked(boolean checked) {
267     LogUtil.v("CallButtonPresenter", "show dialpad " + String.valueOf(checked));
268     getActivity().showDialpadFragment(checked /* show */, true /* animate */);
269   }
270 
271   @Override
changeToVideoClicked()272   public void changeToVideoClicked() {
273     LogUtil.enterBlock("CallButtonPresenter.changeToVideoClicked");
274     Logger.get(mContext)
275         .logCallImpression(
276             DialerImpression.Type.VIDEO_CALL_UPGRADE_REQUESTED,
277             mCall.getUniqueCallId(),
278             mCall.getTimeAddedMs());
279     mCall.getVideoTech().upgradeToVideo();
280   }
281 
282   @Override
onEndCallClicked()283   public void onEndCallClicked() {
284     LogUtil.i("CallButtonPresenter.onEndCallClicked", "call: " + mCall);
285     if (mCall != null) {
286       mCall.disconnect();
287     }
288   }
289 
290   @Override
showAudioRouteSelector()291   public void showAudioRouteSelector() {
292     mInCallButtonUi.showAudioRouteSelector();
293   }
294 
295   /**
296    * Switches the camera between the front-facing and back-facing camera.
297    *
298    * @param useFrontFacingCamera True if we should switch to using the front-facing camera, or false
299    *     if we should switch to using the back-facing camera.
300    */
301   @Override
switchCameraClicked(boolean useFrontFacingCamera)302   public void switchCameraClicked(boolean useFrontFacingCamera) {
303     InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager();
304     cameraManager.setUseFrontFacingCamera(useFrontFacingCamera);
305 
306     String cameraId = cameraManager.getActiveCameraId();
307     if (cameraId != null) {
308       final int cameraDir =
309           cameraManager.isUsingFrontFacingCamera()
310               ? CameraDirection.CAMERA_DIRECTION_FRONT_FACING
311               : CameraDirection.CAMERA_DIRECTION_BACK_FACING;
312       mCall.setCameraDir(cameraDir);
313       mCall.getVideoTech().setCamera(cameraId);
314     }
315   }
316 
317   @Override
toggleCameraClicked()318   public void toggleCameraClicked() {
319     LogUtil.i("CallButtonPresenter.toggleCameraClicked", "");
320     Logger.get(mContext)
321         .logCallImpression(
322             DialerImpression.Type.IN_CALL_SCREEN_SWAP_CAMERA,
323             mCall.getUniqueCallId(),
324             mCall.getTimeAddedMs());
325     switchCameraClicked(
326         !InCallPresenter.getInstance().getInCallCameraManager().isUsingFrontFacingCamera());
327   }
328 
329   /**
330    * Stop or start client's video transmission.
331    *
332    * @param pause True if pausing the local user's video, or false if starting the local user's
333    *     video.
334    */
335   @Override
pauseVideoClicked(boolean pause)336   public void pauseVideoClicked(boolean pause) {
337     LogUtil.i("CallButtonPresenter.pauseVideoClicked", "%s", pause ? "pause" : "unpause");
338 
339     Logger.get(mContext)
340         .logCallImpression(
341             pause
342                 ? DialerImpression.Type.IN_CALL_SCREEN_TURN_OFF_VIDEO
343                 : DialerImpression.Type.IN_CALL_SCREEN_TURN_ON_VIDEO,
344             mCall.getUniqueCallId(),
345             mCall.getTimeAddedMs());
346 
347     if (pause) {
348       mCall.getVideoTech().stopTransmission();
349     } else {
350       mCall.getVideoTech().resumeTransmission();
351     }
352 
353     mInCallButtonUi.setVideoPaused(pause);
354     mInCallButtonUi.enableButton(InCallButtonIds.BUTTON_PAUSE_VIDEO, false);
355   }
356 
updateUi(InCallState state, DialerCall call)357   private void updateUi(InCallState state, DialerCall call) {
358     LogUtil.v("CallButtonPresenter", "updating call UI for call: ", call);
359 
360     if (mInCallButtonUi == null) {
361       return;
362     }
363 
364     if (call != null) {
365       mInCallButtonUi.updateInCallButtonUiColors();
366     }
367 
368     final boolean isEnabled =
369         state.isConnectingOrConnected() && !state.isIncoming() && call != null;
370     mInCallButtonUi.setEnabled(isEnabled);
371 
372     if (call == null) {
373       return;
374     }
375 
376     updateButtonsState(call);
377   }
378 
379   /**
380    * Updates the buttons applicable for the UI.
381    *
382    * @param call The active call.
383    */
updateButtonsState(DialerCall call)384   private void updateButtonsState(DialerCall call) {
385     LogUtil.v("CallButtonPresenter.updateButtonsState", "");
386     final boolean isVideo = call.isVideoCall();
387 
388     // Common functionality (audio, hold, etc).
389     // Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available:
390     //     (1) If the device normally can hold, show HOLD in a disabled state.
391     //     (2) If the device doesn't have the concept of hold/swap, remove the button.
392     final boolean showSwap = call.can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
393     final boolean showHold =
394         !showSwap
395             && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD)
396             && call.can(android.telecom.Call.Details.CAPABILITY_HOLD);
397     final boolean isCallOnHold = call.getState() == DialerCall.State.ONHOLD;
398 
399     final boolean showAddCall =
400         TelecomAdapter.getInstance().canAddCall() && UserManagerCompat.isUserUnlocked(mContext);
401     final boolean showMerge = call.can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
402     final boolean showUpgradeToVideo = !isVideo && (hasVideoCallCapabilities(call));
403     final boolean showDowngradeToAudio = isVideo && isDowngradeToAudioSupported(call);
404     final boolean showMute = call.can(android.telecom.Call.Details.CAPABILITY_MUTE);
405 
406     final boolean hasCameraPermission =
407         isVideo && VideoUtils.hasCameraPermissionAndAllowedByUser(mContext);
408     // Disabling local video doesn't seem to work when dialing. See b/30256571.
409     final boolean showPauseVideo =
410         isVideo
411             && call.getState() != DialerCall.State.DIALING
412             && call.getState() != DialerCall.State.CONNECTING;
413 
414     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_AUDIO, true);
415     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_SWAP, showSwap);
416     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_HOLD, showHold);
417     mInCallButtonUi.setHold(isCallOnHold);
418     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_MUTE, showMute);
419     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_ADD_CALL, true);
420     mInCallButtonUi.enableButton(InCallButtonIds.BUTTON_ADD_CALL, showAddCall);
421     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo);
422     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_DOWNGRADE_TO_AUDIO, showDowngradeToAudio);
423     mInCallButtonUi.showButton(
424         InCallButtonIds.BUTTON_SWITCH_CAMERA, isVideo && hasCameraPermission);
425     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_PAUSE_VIDEO, showPauseVideo);
426     if (isVideo) {
427       mInCallButtonUi.setVideoPaused(!call.getVideoTech().isTransmitting() || !hasCameraPermission);
428     }
429     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_DIALPAD, true);
430     mInCallButtonUi.showButton(InCallButtonIds.BUTTON_MERGE, showMerge);
431 
432     mInCallButtonUi.updateButtonStates();
433   }
434 
hasVideoCallCapabilities(DialerCall call)435   private boolean hasVideoCallCapabilities(DialerCall call) {
436     return call.getVideoTech().isAvailable(mContext);
437   }
438 
439   /**
440    * Determines if downgrading from a video call to an audio-only call is supported. In order to
441    * support downgrade to audio, the SDK version must be >= N and the call should NOT have the
442    * {@link android.telecom.Call.Details#CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO}.
443    *
444    * @param call The call.
445    * @return {@code true} if downgrading to an audio-only call from a video call is supported.
446    */
isDowngradeToAudioSupported(DialerCall call)447   private boolean isDowngradeToAudioSupported(DialerCall call) {
448     // TODO(b/33676907): If there is an RCS video share session, return true here
449     return !call.can(CallCompat.Details.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO);
450   }
451 
452   @Override
refreshMuteState()453   public void refreshMuteState() {
454     // Restore the previous mute state
455     if (mAutomaticallyMuted
456         && AudioModeProvider.getInstance().getAudioState().isMuted() != mPreviousMuteState) {
457       if (mInCallButtonUi == null) {
458         return;
459       }
460       muteClicked(mPreviousMuteState, false /* clickedByUser */);
461     }
462     mAutomaticallyMuted = false;
463   }
464 
465   @Override
onSaveInstanceState(Bundle outState)466   public void onSaveInstanceState(Bundle outState) {
467     outState.putBoolean(KEY_AUTOMATICALLY_MUTED, mAutomaticallyMuted);
468     outState.putBoolean(KEY_PREVIOUS_MUTE_STATE, mPreviousMuteState);
469   }
470 
471   @Override
onRestoreInstanceState(Bundle savedInstanceState)472   public void onRestoreInstanceState(Bundle savedInstanceState) {
473     mAutomaticallyMuted =
474         savedInstanceState.getBoolean(KEY_AUTOMATICALLY_MUTED, mAutomaticallyMuted);
475     mPreviousMuteState = savedInstanceState.getBoolean(KEY_PREVIOUS_MUTE_STATE, mPreviousMuteState);
476   }
477 
478   @Override
onCameraPermissionGranted()479   public void onCameraPermissionGranted() {
480     if (mCall != null) {
481       updateButtonsState(mCall);
482     }
483   }
484 
485   @Override
onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera)486   public void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera) {
487     if (mInCallButtonUi == null) {
488       return;
489     }
490     mInCallButtonUi.setCameraSwitched(!isUsingFrontFacingCamera);
491   }
492 
493   @Override
getContext()494   public Context getContext() {
495     return mContext;
496   }
497 
getActivity()498   private InCallActivity getActivity() {
499     if (mInCallButtonUi != null) {
500       Fragment fragment = mInCallButtonUi.getInCallButtonUiFragment();
501       if (fragment != null) {
502         return (InCallActivity) fragment.getActivity();
503       }
504     }
505     return null;
506   }
507 }
508