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