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.state.DialerCallState;
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 videoPauseController;
36   private InCallPresenter inCallPresenter;
37 
38   /** The current call, if applicable. */
39   private DialerCall primaryCall = 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 prevCallState = DialerCallState.INVALID;
47 
48   private boolean wasVideoCall = 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 isInBackground = false;
55 
56   /**
57    * Singleton accessor for the {@link VideoPauseController}.
58    *
59    * @return Singleton instance of the {@link VideoPauseController}.
60    */
61   /*package*/
getInstance()62   static synchronized VideoPauseController getInstance() {
63     if (videoPauseController == null) {
64       videoPauseController = new VideoPauseController();
65     }
66     return videoPauseController;
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    */
isIncomingCall(DialerCall call)75   private static boolean isIncomingCall(DialerCall call) {
76     return call != null
77         && (call.getState() == DialerCallState.CALL_WAITING
78             || call.getState() == DialerCallState.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    */
wasDialing()86   private boolean wasDialing() {
87     return DialerCallState.isDialing(prevCallState);
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    */
setUp(@onNull InCallPresenter inCallPresenter)96   public void setUp(@NonNull InCallPresenter inCallPresenter) {
97     LogUtil.enterBlock("VideoPauseController.setUp");
98     this.inCallPresenter = Assert.isNotNull(inCallPresenter);
99     this.inCallPresenter.addListener(this);
100     this.inCallPresenter.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    */
tearDown()107   public void tearDown() {
108     LogUtil.enterBlock("VideoPauseController.tearDown");
109     inCallPresenter.removeListener(this);
110     inCallPresenter.removeIncomingCallListener(this);
111     clear();
112   }
113 
114   /** Clears the internal state for the {@link VideoPauseController}. */
clear()115   private void clear() {
116     inCallPresenter = null;
117     primaryCall = null;
118     prevCallState = DialerCallState.INVALID;
119     wasVideoCall = false;
120     isInBackground = 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
onStateChange(InCallState oldState, InCallState newState, CallList callList)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, primaryCall);
147     boolean canVideoPause = videoCanPause(call);
148 
149     LogUtil.i(
150         "VideoPauseController.onStateChange",
151         "hasPrimaryCallChanged: %b, videoCanPause: %b, isInBackground: %b",
152         hasPrimaryCallChanged,
153         canVideoPause,
154         isInBackground);
155 
156     if (hasPrimaryCallChanged) {
157       onPrimaryCallChanged(call);
158       return;
159     }
160 
161     if (wasDialing() && canVideoPause && isInBackground) {
162       // Bring UI to foreground if outgoing request becomes active while UI is in
163       // background.
164       bringToForeground();
165     } else if (!wasVideoCall && canVideoPause && isInBackground) {
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    */
onPrimaryCallChanged(DialerCall call)183   private void onPrimaryCallChanged(DialerCall call) {
184     LogUtil.i(
185         "VideoPauseController.onPrimaryCallChanged",
186         "new call: %s, old call: %s, mIsInBackground: %b",
187         call,
188         primaryCall,
189         isInBackground);
190 
191     if (Objects.equals(call, primaryCall)) {
192       throw new IllegalStateException();
193     }
194     final boolean canVideoPause = videoCanPause(call);
195 
196     if (canVideoPause && !isInBackground) {
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(primaryCall)) {
201       // Send pause request if there is an active video call, and we just received a new
202       // incoming call.
203       sendRequest(primaryCall, 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
onIncomingCall(InCallState oldState, InCallState newState, DialerCall call)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, primaryCall)) {
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    */
updatePrimaryCallContext(DialerCall call)237   private void updatePrimaryCallContext(DialerCall call) {
238     if (call == null) {
239       primaryCall = null;
240       prevCallState = DialerCallState.INVALID;
241       wasVideoCall = false;
242     } else {
243       primaryCall = call;
244       prevCallState = call.getState();
245       wasVideoCall = 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    */
onUiShowing(boolean showing)254   public void onUiShowing(boolean showing) {
255     if (inCallPresenter == null) {
256       return;
257     }
258 
259     final boolean isInCall = inCallPresenter.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    */
onResume(boolean isInCall)274   private void onResume(boolean isInCall) {
275     isInBackground = false;
276     if (isInCall) {
277       sendRequest(primaryCall, 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    */
onPause(boolean isInCall)288   private void onPause(boolean isInCall) {
289     isInBackground = true;
290     if (isInCall) {
291       sendRequest(primaryCall, false);
292     }
293   }
294 
bringToForeground()295   private void bringToForeground() {
296     LogUtil.enterBlock("VideoPauseController.bringToForeground");
297     if (inCallPresenter != null) {
298       inCallPresenter.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    */
sendRequest(DialerCall call, boolean resume)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 
videoCanPause(DialerCall call)324   private static boolean videoCanPause(DialerCall call) {
325     return call != null && call.isVideoCall() && call.getState() == DialerCallState.ACTIVE;
326   }
327 }
328