Home | History | Annotate | Download | only in incallui
      1 /* Copyright (c) 2014, The Linux Foundation. All rights reserved.
      2  *
      3  * Redistribution and use in source and binary forms, with or without
      4  * modification, are permitted provided that the following conditions are
      5  * met:
      6  *     * Redistributions of source code must retain the above copyright
      7  *       notice, this list of conditions and the following disclaimer.
      8  *     * Redistributions in binary form must reproduce the above
      9  *       copyright notice, this list of conditions and the following
     10  *       disclaimer in the documentation and/or other materials provided
     11  *       with the distribution.
     12  *     * Neither the name of The Linux Foundation nor the names of its
     13  *       contributors may be used to endorse or promote products derived
     14  *       from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
     23  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     24  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     25  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
     26  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 package com.android.incallui;
     30 
     31 import android.telecom.VideoProfile;
     32 import com.android.incallui.Call.State;
     33 import com.android.incallui.InCallPresenter.InCallState;
     34 import com.android.incallui.InCallPresenter.InCallStateListener;
     35 import com.android.incallui.InCallPresenter.IncomingCallListener;
     36 import com.android.incallui.InCallVideoCallCallbackNotifier.SessionModificationListener;
     37 import com.google.common.base.Preconditions;
     38 
     39 /**
     40  * This class is responsible for generating video pause/resume requests when the InCall UI is sent
     41  * to the background and subsequently brought back to the foreground.
     42  */
     43 class VideoPauseController implements InCallStateListener, IncomingCallListener,
     44         SessionModificationListener {
     45     private static final String TAG = "VideoPauseController:";
     46 
     47     /**
     48      * Keeps track of the current active/foreground call.
     49      */
     50     private class CallContext {
     51         public CallContext(Call call) {
     52             Preconditions.checkNotNull(call);
     53             update(call);
     54         }
     55 
     56         public void update(Call call) {
     57             mCall = Preconditions.checkNotNull(call);
     58             mState = call.getState();
     59             mVideoState = call.getVideoState();
     60         }
     61 
     62         public int getState() {
     63             return mState;
     64         }
     65 
     66         public int getVideoState() {
     67             return mVideoState;
     68         }
     69 
     70         public String toString() {
     71             return String.format("CallContext {CallId=%s, State=%s, VideoState=%d}",
     72                     mCall.getId(), mState, mVideoState);
     73         }
     74 
     75         public Call getCall() {
     76             return mCall;
     77         }
     78 
     79         private int mState = State.INVALID;
     80         private int mVideoState;
     81         private Call mCall;
     82     }
     83 
     84     private InCallPresenter mInCallPresenter;
     85     private static VideoPauseController sVideoPauseController;
     86 
     87     /**
     88      * The current call context, if applicable.
     89      */
     90     private CallContext mPrimaryCallContext = null;
     91 
     92     /**
     93      * Tracks whether the application is in the background. {@code True} if the application is in
     94      * the background, {@code false} otherwise.
     95      */
     96     private boolean mIsInBackground = false;
     97 
     98     /**
     99      * Singleton accessor for the {@link VideoPauseController}.
    100      * @return Singleton instance of the {@link VideoPauseController}.
    101      */
    102     /*package*/
    103     static synchronized VideoPauseController getInstance() {
    104         if (sVideoPauseController == null) {
    105             sVideoPauseController = new VideoPauseController();
    106         }
    107         return sVideoPauseController;
    108     }
    109 
    110     /**
    111      * Configures the {@link VideoPauseController} to listen to call events.  Configured via the
    112      * {@link com.android.incallui.InCallPresenter}.
    113      *
    114      * @param inCallPresenter The {@link com.android.incallui.InCallPresenter}.
    115      */
    116     public void setUp(InCallPresenter inCallPresenter) {
    117         log("setUp");
    118         mInCallPresenter = Preconditions.checkNotNull(inCallPresenter);
    119         mInCallPresenter.addListener(this);
    120         mInCallPresenter.addIncomingCallListener(this);
    121         InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(this);
    122     }
    123 
    124     /**
    125      * Cleans up the {@link VideoPauseController} by removing all listeners and clearing its
    126      * internal state.  Called from {@link com.android.incallui.InCallPresenter}.
    127      */
    128     public void tearDown() {
    129         log("tearDown...");
    130         InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this);
    131         mInCallPresenter.removeListener(this);
    132         mInCallPresenter.removeIncomingCallListener(this);
    133         clear();
    134     }
    135 
    136     /**
    137      * Clears the internal state for the {@link VideoPauseController}.
    138      */
    139     private void clear() {
    140         mInCallPresenter = null;
    141         mPrimaryCallContext = null;
    142         mIsInBackground = false;
    143     }
    144 
    145     /**
    146      * Handles changes in the {@link InCallState}.  Triggers pause and resumption of video for the
    147      * current foreground call.
    148      *
    149      * @param oldState The previous {@link InCallState}.
    150      * @param newState The current {@link InCallState}.
    151      * @param callList List of current call.
    152      */
    153     @Override
    154     public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
    155         log("onStateChange, OldState=" + oldState + " NewState=" + newState);
    156 
    157         Call call = null;
    158         if (newState == InCallState.INCOMING) {
    159             call = callList.getIncomingCall();
    160         } else if (newState == InCallState.WAITING_FOR_ACCOUNT) {
    161             call = callList.getWaitingForAccountCall();
    162         } else if (newState == InCallState.PENDING_OUTGOING) {
    163             call = callList.getPendingOutgoingCall();
    164         } else if (newState == InCallState.OUTGOING) {
    165             call = callList.getOutgoingCall();
    166         } else {
    167             call = callList.getActiveCall();
    168         }
    169 
    170         boolean hasPrimaryCallChanged = !areSame(call, mPrimaryCallContext);
    171         boolean canVideoPause = CallUtils.canVideoPause(call);
    172         log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged);
    173         log("onStateChange, canVideoPause=" + canVideoPause);
    174         log("onStateChange, IsInBackground=" + mIsInBackground);
    175 
    176         if (hasPrimaryCallChanged) {
    177             onPrimaryCallChanged(call);
    178             return;
    179         }
    180 
    181         if (isDialing(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
    182             // Bring UI to foreground if outgoing request becomes active while UI is in
    183             // background.
    184             bringToForeground();
    185         } else if (!isVideoCall(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
    186             // Bring UI to foreground if VoLTE call becomes active while UI is in
    187             // background.
    188             bringToForeground();
    189         }
    190 
    191         updatePrimaryCallContext(call);
    192     }
    193 
    194     /**
    195      * Handles a change to the primary call.
    196      * <p>
    197      * Reject incoming or hangup dialing call: Where the previous call was an incoming call or a
    198      * call in dialing state, resume the new primary call.
    199      * Call swap: Where the new primary call is incoming, pause video on the previous primary call.
    200      *
    201      * @param call The new primary call.
    202      */
    203     private void onPrimaryCallChanged(Call call) {
    204         log("onPrimaryCallChanged: New call = " + call);
    205         log("onPrimaryCallChanged: Old call = " + mPrimaryCallContext);
    206         log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground);
    207 
    208         Preconditions.checkState(!areSame(call, mPrimaryCallContext));
    209         final boolean canVideoPause = CallUtils.canVideoPause(call);
    210 
    211         if ((isIncomingCall(mPrimaryCallContext) || isDialing(mPrimaryCallContext))
    212                 && canVideoPause && !mIsInBackground) {
    213             // Send resume request for the active call, if user rejects incoming call or ends
    214             // dialing call and UI is in the foreground.
    215             sendRequest(call, true);
    216         } else if (isIncomingCall(call) && canVideoPause(mPrimaryCallContext)) {
    217             // Send pause request if there is an active video call, and we just received a new
    218             // incoming call.
    219             sendRequest(mPrimaryCallContext.getCall(), false);
    220         }
    221 
    222         updatePrimaryCallContext(call);
    223     }
    224 
    225     /**
    226      * Handles new incoming calls by triggering a change in the primary call.
    227      *
    228      * @param oldState the old {@link InCallState}.
    229      * @param newState the new {@link InCallState}.
    230      * @param call the incoming call.
    231      */
    232     @Override
    233     public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
    234         log("onIncomingCall, OldState=" + oldState + " NewState=" + newState + " Call=" + call);
    235 
    236         if (areSame(call, mPrimaryCallContext)) {
    237             return;
    238         }
    239 
    240         onPrimaryCallChanged(call);
    241     }
    242 
    243     /**
    244      * Caches a reference to the primary call and stores its previous state.
    245      *
    246      * @param call The new primary call.
    247      */
    248     private void updatePrimaryCallContext(Call call) {
    249         if (call == null) {
    250             mPrimaryCallContext = null;
    251         } else if (mPrimaryCallContext != null) {
    252             mPrimaryCallContext.update(call);
    253         } else {
    254             mPrimaryCallContext = new CallContext(call);
    255         }
    256     }
    257 
    258     /**
    259      * Called when UI goes in/out of the foreground.
    260      * @param showing true if UI is in the foreground, false otherwise.
    261      */
    262     public void onUiShowing(boolean showing) {
    263         // Only send pause/unpause requests if we are in the INCALL state.
    264         if (mInCallPresenter == null || mInCallPresenter.getInCallState() != InCallState.INCALL) {
    265             return;
    266         }
    267 
    268         if (showing) {
    269             onResume();
    270         } else {
    271             onPause();
    272         }
    273     }
    274 
    275     /**
    276      * Handles requests to upgrade to video.
    277      *
    278      * @param call The call the request was received for.
    279      * @param videoState The video state that the request wants to upgrade to.
    280      */
    281     @Override
    282     public void onUpgradeToVideoRequest(Call call, int videoState) {
    283         // Not used.
    284     }
    285 
    286     /**
    287      * Handles successful upgrades to video.
    288      * @param call The call the request was successful for.
    289      */
    290     @Override
    291     public void onUpgradeToVideoSuccess(Call call) {
    292         // Not used.
    293     }
    294 
    295     /**
    296      * Handles a failure to upgrade a call to video.
    297      *
    298      * @param status The failure status.
    299      * @param call The call the request was successful for.
    300      */
    301     @Override
    302     public void onUpgradeToVideoFail(int status, Call call) {
    303         // TODO (ims-vt) Automatically bring in call ui to foreground.
    304     }
    305 
    306     /**
    307      * Handles a downgrade of a call to audio-only.
    308      *
    309      * @param call The call which was downgraded to audio-only.
    310      */
    311     @Override
    312     public void onDowngradeToAudio(Call call) {
    313     }
    314 
    315     /**
    316      * Called when UI is brought to the foreground.  Sends a session modification request to resume
    317      * the outgoing video.
    318      */
    319     private void onResume() {
    320         log("onResume");
    321 
    322         mIsInBackground = false;
    323         if (canVideoPause(mPrimaryCallContext)) {
    324             sendRequest(mPrimaryCallContext.getCall(), true);
    325         } else {
    326             log("onResume. Ignoring...");
    327         }
    328     }
    329 
    330     /**
    331      * Called when UI is sent to the background.  Sends a session modification request to pause the
    332      * outgoing video.
    333      */
    334     private void onPause() {
    335         log("onPause");
    336 
    337         mIsInBackground = true;
    338         if (canVideoPause(mPrimaryCallContext)) {
    339             sendRequest(mPrimaryCallContext.getCall(), false);
    340         } else {
    341             log("onPause, Ignoring...");
    342         }
    343     }
    344 
    345     private void bringToForeground() {
    346         if (mInCallPresenter != null) {
    347             log("Bringing UI to foreground");
    348             mInCallPresenter.bringToForeground(false);
    349         } else {
    350             loge("InCallPresenter is null. Cannot bring UI to foreground");
    351         }
    352     }
    353 
    354     /**
    355      * Sends Pause/Resume request.
    356      *
    357      * @param call Call to be paused/resumed.
    358      * @param resume If true resume request will be sent, otherwise pause request.
    359      */
    360     private void sendRequest(Call call, boolean resume) {
    361         // Check if this call supports pause/un-pause.
    362         if (!call.can(android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO)) {
    363             return;
    364         }
    365 
    366         if (resume) {
    367             log("sending resume request, call=" + call);
    368             call.getVideoCall()
    369                     .sendSessionModifyRequest(CallUtils.makeVideoUnPauseProfile(call));
    370         } else {
    371             log("sending pause request, call=" + call);
    372             call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoPauseProfile(call));
    373         }
    374     }
    375 
    376     /**
    377      * Determines if a given call is the same one stored in a {@link CallContext}.
    378      *
    379      * @param call The call.
    380      * @param callContext The call context.
    381      * @return {@code true} if the {@link Call} is the same as the one referenced in the
    382      *      {@link CallContext}.
    383      */
    384     private static boolean areSame(Call call, CallContext callContext) {
    385         if (call == null && callContext == null) {
    386             return true;
    387         } else if (call == null || callContext == null) {
    388             return false;
    389         }
    390         return call.equals(callContext.getCall());
    391     }
    392 
    393     /**
    394      * Determines if a video call can be paused.  Only a video call which is active can be paused.
    395      *
    396      * @param callContext The call context to check.
    397      * @return {@code true} if the call is an active video call.
    398      */
    399     private static boolean canVideoPause(CallContext callContext) {
    400         return isVideoCall(callContext) && callContext.getState() == Call.State.ACTIVE;
    401     }
    402 
    403     /**
    404      * Determines if a call referenced by a {@link CallContext} is a video call.
    405      *
    406      * @param callContext The call context.
    407      * @return {@code true} if the call is a video call, {@code false} otherwise.
    408      */
    409     private static boolean isVideoCall(CallContext callContext) {
    410         return callContext != null && CallUtils.isVideoCall(callContext.getVideoState());
    411     }
    412 
    413     /**
    414      * Determines if call is in incoming/waiting state.
    415      *
    416      * @param call The call context.
    417      * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
    418      */
    419     private static boolean isIncomingCall(CallContext call) {
    420         return call != null && isIncomingCall(call.getCall());
    421     }
    422 
    423     /**
    424      * Determines if a call is in incoming/waiting state.
    425      *
    426      * @param call The call.
    427      * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
    428      */
    429     private static boolean isIncomingCall(Call call) {
    430         return call != null && (call.getState() == Call.State.CALL_WAITING
    431                 || call.getState() == Call.State.INCOMING);
    432     }
    433 
    434     /**
    435      * Determines if a call is dialing.
    436      *
    437      * @param call The call context.
    438      * @return {@code true} if the call is dialing, {@code false} otherwise.
    439      */
    440     private static boolean isDialing(CallContext call) {
    441         return call != null && Call.State.isDialing(call.getState());
    442     }
    443 
    444     /**
    445      * Determines if a call is holding.
    446      *
    447      * @param call The call context.
    448      * @return {@code true} if the call is holding, {@code false} otherwise.
    449      */
    450     private static boolean isHolding(CallContext call) {
    451         return call != null && call.getState() == Call.State.ONHOLD;
    452     }
    453 
    454     /**
    455      * Logs a debug message.
    456      *
    457      * @param msg The message.
    458      */
    459     private void log(String msg) {
    460         Log.d(this, TAG + msg);
    461     }
    462 
    463     /**
    464      * Logs an error message.
    465      *
    466      * @param msg The message.
    467      */
    468     private void loge(String msg) {
    469         Log.e(this, TAG + msg);
    470     }
    471 }
    472