Home | History | Annotate | Download | only in incallui
      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 
     66   public CallButtonPresenter(Context context) {
     67     mContext = context.getApplicationContext();
     68   }
     69 
     70   @Override
     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
     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
    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
    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
    146   public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
    147     onStateChange(oldState, newState, CallList.getInstance());
    148   }
    149 
    150   @Override
    151   public void onCanAddCallChanged(boolean canAddCall) {
    152     if (mInCallButtonUi != null && mCall != null) {
    153       updateButtonsState(mCall);
    154     }
    155   }
    156 
    157   @Override
    158   public void onAudioStateChanged(CallAudioState audioState) {
    159     if (mInCallButtonUi != null) {
    160       mInCallButtonUi.setAudioState(audioState);
    161     }
    162   }
    163 
    164   @Override
    165   public CallAudioState getCurrentAudioState() {
    166     return AudioModeProvider.getInstance().getAudioState();
    167   }
    168 
    169   @Override
    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
    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
    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
    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
    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
    251   public void mergeClicked() {
    252     TelecomAdapter.getInstance().merge(mCall.getId());
    253   }
    254 
    255   @Override
    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
    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
    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
    283   public void onEndCallClicked() {
    284     LogUtil.i("CallButtonPresenter.onEndCallClicked", "call: " + mCall);
    285     if (mCall != null) {
    286       mCall.disconnect();
    287     }
    288   }
    289 
    290   @Override
    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
    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
    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
    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 
    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    */
    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 
    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    */
    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
    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
    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
    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
    479   public void onCameraPermissionGranted() {
    480     if (mCall != null) {
    481       updateButtonsState(mCall);
    482     }
    483   }
    484 
    485   @Override
    486   public void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera) {
    487     if (mInCallButtonUi == null) {
    488       return;
    489     }
    490     mInCallButtonUi.setCameraSwitched(!isUsingFrontFacingCamera);
    491   }
    492 
    493   @Override
    494   public Context getContext() {
    495     return mContext;
    496   }
    497 
    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