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