Home | History | Annotate | Download | only in incallui
      1 /*
      2  * Copyright (C) 2015 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.support.annotation.NonNull;
     20 import com.android.dialer.common.Assert;
     21 import com.android.dialer.common.LogUtil;
     22 import com.android.incallui.InCallPresenter.InCallState;
     23 import com.android.incallui.InCallPresenter.InCallStateListener;
     24 import com.android.incallui.InCallPresenter.IncomingCallListener;
     25 import com.android.incallui.call.CallList;
     26 import com.android.incallui.call.DialerCall;
     27 import com.android.incallui.call.DialerCall.State;
     28 import java.util.Objects;
     29 
     30 /**
     31  * This class is responsible for generating video pause/resume requests when the InCall UI is sent
     32  * to the background and subsequently brought back to the foreground.
     33  */
     34 class VideoPauseController implements InCallStateListener, IncomingCallListener {
     35   private static VideoPauseController sVideoPauseController;
     36   private InCallPresenter mInCallPresenter;
     37 
     38   /** The current call, if applicable. */
     39   private DialerCall mPrimaryCall = null;
     40 
     41   /**
     42    * The cached state of primary call, updated after onStateChange has processed.
     43    *
     44    * <p>These values are stored to detect specific changes in state between onStateChange calls.
     45    */
     46   private int mPrevCallState = State.INVALID;
     47 
     48   private boolean mWasVideoCall = false;
     49 
     50   /**
     51    * Tracks whether the application is in the background. {@code True} if the application is in the
     52    * background, {@code false} otherwise.
     53    */
     54   private boolean mIsInBackground = false;
     55 
     56   /**
     57    * Singleton accessor for the {@link VideoPauseController}.
     58    *
     59    * @return Singleton instance of the {@link VideoPauseController}.
     60    */
     61   /*package*/
     62   static synchronized VideoPauseController getInstance() {
     63     if (sVideoPauseController == null) {
     64       sVideoPauseController = new VideoPauseController();
     65     }
     66     return sVideoPauseController;
     67   }
     68 
     69   /**
     70    * Determines if a call is in incoming/waiting state.
     71    *
     72    * @param call The call.
     73    * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
     74    */
     75   private static boolean isIncomingCall(DialerCall call) {
     76     return call != null
     77         && (call.getState() == DialerCall.State.CALL_WAITING
     78             || call.getState() == DialerCall.State.INCOMING);
     79   }
     80 
     81   /**
     82    * Determines if a call is dialing.
     83    *
     84    * @return {@code true} if the call is dialing, {@code false} otherwise.
     85    */
     86   private boolean wasDialing() {
     87     return DialerCall.State.isDialing(mPrevCallState);
     88   }
     89 
     90   /**
     91    * Configures the {@link VideoPauseController} to listen to call events. Configured via the {@link
     92    * com.android.incallui.InCallPresenter}.
     93    *
     94    * @param inCallPresenter The {@link com.android.incallui.InCallPresenter}.
     95    */
     96   public void setUp(@NonNull InCallPresenter inCallPresenter) {
     97     LogUtil.enterBlock("VideoPauseController.setUp");
     98     mInCallPresenter = Assert.isNotNull(inCallPresenter);
     99     mInCallPresenter.addListener(this);
    100     mInCallPresenter.addIncomingCallListener(this);
    101   }
    102 
    103   /**
    104    * Cleans up the {@link VideoPauseController} by removing all listeners and clearing its internal
    105    * state. Called from {@link com.android.incallui.InCallPresenter}.
    106    */
    107   public void tearDown() {
    108     LogUtil.enterBlock("VideoPauseController.tearDown");
    109     mInCallPresenter.removeListener(this);
    110     mInCallPresenter.removeIncomingCallListener(this);
    111     clear();
    112   }
    113 
    114   /** Clears the internal state for the {@link VideoPauseController}. */
    115   private void clear() {
    116     mInCallPresenter = null;
    117     mPrimaryCall = null;
    118     mPrevCallState = State.INVALID;
    119     mWasVideoCall = false;
    120     mIsInBackground = false;
    121   }
    122 
    123   /**
    124    * Handles changes in the {@link InCallState}. Triggers pause and resumption of video for the
    125    * current foreground call.
    126    *
    127    * @param oldState The previous {@link InCallState}.
    128    * @param newState The current {@link InCallState}.
    129    * @param callList List of current call.
    130    */
    131   @Override
    132   public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
    133     DialerCall call;
    134     if (newState == InCallState.INCOMING) {
    135       call = callList.getIncomingCall();
    136     } else if (newState == InCallState.WAITING_FOR_ACCOUNT) {
    137       call = callList.getWaitingForAccountCall();
    138     } else if (newState == InCallState.PENDING_OUTGOING) {
    139       call = callList.getPendingOutgoingCall();
    140     } else if (newState == InCallState.OUTGOING) {
    141       call = callList.getOutgoingCall();
    142     } else {
    143       call = callList.getActiveCall();
    144     }
    145 
    146     boolean hasPrimaryCallChanged = !Objects.equals(call, mPrimaryCall);
    147     boolean canVideoPause = videoCanPause(call);
    148 
    149     LogUtil.i(
    150         "VideoPauseController.onStateChange",
    151         "hasPrimaryCallChanged: %b, videoCanPause: %b, isInBackground: %b",
    152         hasPrimaryCallChanged,
    153         canVideoPause,
    154         mIsInBackground);
    155 
    156     if (hasPrimaryCallChanged) {
    157       onPrimaryCallChanged(call);
    158       return;
    159     }
    160 
    161     if (wasDialing() && canVideoPause && mIsInBackground) {
    162       // Bring UI to foreground if outgoing request becomes active while UI is in
    163       // background.
    164       bringToForeground();
    165     } else if (!mWasVideoCall && canVideoPause && mIsInBackground) {
    166       // Bring UI to foreground if VoLTE call becomes active while UI is in
    167       // background.
    168       bringToForeground();
    169     }
    170 
    171     updatePrimaryCallContext(call);
    172   }
    173 
    174   /**
    175    * Handles a change to the primary call.
    176    *
    177    * <p>Reject incoming or hangup dialing call: Where the previous call was an incoming call or a
    178    * call in dialing state, resume the new primary call. DialerCall swap: Where the new primary call
    179    * is incoming, pause video on the previous primary call.
    180    *
    181    * @param call The new primary call.
    182    */
    183   private void onPrimaryCallChanged(DialerCall call) {
    184     LogUtil.i(
    185         "VideoPauseController.onPrimaryCallChanged",
    186         "new call: %s, old call: %s, mIsInBackground: %b",
    187         call,
    188         mPrimaryCall,
    189         mIsInBackground);
    190 
    191     if (Objects.equals(call, mPrimaryCall)) {
    192       throw new IllegalStateException();
    193     }
    194     final boolean canVideoPause = videoCanPause(call);
    195 
    196     if (canVideoPause && !mIsInBackground) {
    197       // Send resume request for the active call, if user rejects incoming call, ends dialing
    198       // call, or the call was previously in a paused state and UI is in the foreground.
    199       sendRequest(call, true);
    200     } else if (isIncomingCall(call) && videoCanPause(mPrimaryCall)) {
    201       // Send pause request if there is an active video call, and we just received a new
    202       // incoming call.
    203       sendRequest(mPrimaryCall, false);
    204     }
    205 
    206     updatePrimaryCallContext(call);
    207   }
    208 
    209   /**
    210    * Handles new incoming calls by triggering a change in the primary call.
    211    *
    212    * @param oldState the old {@link InCallState}.
    213    * @param newState the new {@link InCallState}.
    214    * @param call the incoming call.
    215    */
    216   @Override
    217   public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
    218     LogUtil.i(
    219         "VideoPauseController.onIncomingCall",
    220         "oldState: %s, newState: %s, call: %s",
    221         oldState,
    222         newState,
    223         call);
    224 
    225     if (Objects.equals(call, mPrimaryCall)) {
    226       return;
    227     }
    228 
    229     onPrimaryCallChanged(call);
    230   }
    231 
    232   /**
    233    * Caches a reference to the primary call and stores its previous state.
    234    *
    235    * @param call The new primary call.
    236    */
    237   private void updatePrimaryCallContext(DialerCall call) {
    238     if (call == null) {
    239       mPrimaryCall = null;
    240       mPrevCallState = State.INVALID;
    241       mWasVideoCall = false;
    242     } else {
    243       mPrimaryCall = call;
    244       mPrevCallState = call.getState();
    245       mWasVideoCall = call.isVideoCall();
    246     }
    247   }
    248 
    249   /**
    250    * Called when UI goes in/out of the foreground.
    251    *
    252    * @param showing true if UI is in the foreground, false otherwise.
    253    */
    254   public void onUiShowing(boolean showing) {
    255     if (mInCallPresenter == null) {
    256       return;
    257     }
    258 
    259     final boolean isInCall = mInCallPresenter.getInCallState() == InCallState.INCALL;
    260     if (showing) {
    261       onResume(isInCall);
    262     } else {
    263       onPause(isInCall);
    264     }
    265   }
    266 
    267   /**
    268    * Called when UI is brought to the foreground. Sends a session modification request to resume the
    269    * outgoing video.
    270    *
    271    * @param isInCall {@code true} if we are in an active call. A resume request is only sent to the
    272    *     video provider if we are in a call.
    273    */
    274   private void onResume(boolean isInCall) {
    275     mIsInBackground = false;
    276     if (isInCall) {
    277       sendRequest(mPrimaryCall, true);
    278     }
    279   }
    280 
    281   /**
    282    * Called when UI is sent to the background. Sends a session modification request to pause the
    283    * outgoing video.
    284    *
    285    * @param isInCall {@code true} if we are in an active call. A pause request is only sent to the
    286    *     video provider if we are in a call.
    287    */
    288   private void onPause(boolean isInCall) {
    289     mIsInBackground = true;
    290     if (isInCall) {
    291       sendRequest(mPrimaryCall, false);
    292     }
    293   }
    294 
    295   private void bringToForeground() {
    296     LogUtil.enterBlock("VideoPauseController.bringToForeground");
    297     if (mInCallPresenter != null) {
    298       mInCallPresenter.bringToForeground(false);
    299     } else {
    300       LogUtil.e(
    301           "VideoPauseController.bringToForeground",
    302           "InCallPresenter is null. Cannot bring UI to foreground");
    303     }
    304   }
    305 
    306   /**
    307    * Sends Pause/Resume request.
    308    *
    309    * @param call DialerCall to be paused/resumed.
    310    * @param resume If true resume request will be sent, otherwise pause request.
    311    */
    312   private void sendRequest(DialerCall call, boolean resume) {
    313     if (call == null) {
    314       return;
    315     }
    316 
    317     if (resume) {
    318       call.getVideoTech().unpause();
    319     } else {
    320       call.getVideoTech().pause();
    321     }
    322   }
    323 
    324   private static boolean videoCanPause(DialerCall call) {
    325     return call != null && call.isVideoCall() && call.getState() == DialerCall.State.ACTIVE;
    326   }
    327 }
    328