• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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*/
getInstance()62   static synchronized VideoPauseController getInstance() {
63     if (sVideoPauseController == null) {
64       sVideoPauseController = new VideoPauseController();
65     }
66     return sVideoPauseController;
67   }
68 
wasIncomingCall()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    */
isIncomingCall(DialerCall call)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    */
wasDialing()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    */
setUp(@onNull InCallPresenter inCallPresenter)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    */
tearDown()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}. */
clear()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
onStateChange(InCallState oldState, InCallState newState, CallList callList)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    */
onPrimaryCallChanged(DialerCall call)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
onIncomingCall(InCallState oldState, InCallState newState, DialerCall call)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    */
updatePrimaryCallContext(DialerCall call)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    */
onUiShowing(boolean showing)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    */
onResume(boolean isInCall)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    */
onPause(boolean isInCall)293   private void onPause(boolean isInCall) {
294     mIsInBackground = true;
295     if (isInCall) {
296       sendRequest(mPrimaryCall, false);
297     }
298   }
299 
bringToForeground()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    */
sendRequest(DialerCall call, boolean resume)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 
videoCanPause(DialerCall call)329   private static boolean videoCanPause(DialerCall call) {
330     return call != null && call.isVideoCall() && call.getState() == DialerCall.State.ACTIVE;
331   }
332 }
333