• 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 com.android.incallui.Call.State;
20 import com.android.incallui.InCallPresenter.InCallState;
21 import com.android.incallui.InCallPresenter.InCallStateListener;
22 import com.android.incallui.InCallPresenter.IncomingCallListener;
23 import com.android.incallui.InCallVideoCallCallbackNotifier.SessionModificationListener;
24 import com.google.common.base.Preconditions;
25 
26 import android.telecom.VideoProfile;
27 
28 /**
29  * This class is responsible for generating video pause/resume requests when the InCall UI is sent
30  * to the background and subsequently brought back to the foreground.
31  */
32 class VideoPauseController implements InCallStateListener, IncomingCallListener {
33     private static final String TAG = "VideoPauseController";
34 
35     /**
36      * Keeps track of the current active/foreground call.
37      */
38     private class CallContext {
CallContext(Call call)39         public CallContext(Call call) {
40             Preconditions.checkNotNull(call);
41             update(call);
42         }
43 
update(Call call)44         public void update(Call call) {
45             mCall = Preconditions.checkNotNull(call);
46             mState = call.getState();
47             mVideoState = call.getVideoState();
48         }
49 
getState()50         public int getState() {
51             return mState;
52         }
53 
getVideoState()54         public int getVideoState() {
55             return mVideoState;
56         }
57 
toString()58         public String toString() {
59             return String.format("CallContext {CallId=%s, State=%s, VideoState=%d}",
60                     mCall.getId(), mState, mVideoState);
61         }
62 
getCall()63         public Call getCall() {
64             return mCall;
65         }
66 
67         private int mState = State.INVALID;
68         private int mVideoState;
69         private Call mCall;
70     }
71 
72     private InCallPresenter mInCallPresenter;
73     private static VideoPauseController sVideoPauseController;
74 
75     /**
76      * The current call context, if applicable.
77      */
78     private CallContext mPrimaryCallContext = null;
79 
80     /**
81      * Tracks whether the application is in the background. {@code True} if the application is in
82      * the background, {@code false} otherwise.
83      */
84     private boolean mIsInBackground = false;
85 
86     /**
87      * Singleton accessor for the {@link VideoPauseController}.
88      * @return Singleton instance of the {@link VideoPauseController}.
89      */
90     /*package*/
getInstance()91     static synchronized VideoPauseController getInstance() {
92         if (sVideoPauseController == null) {
93             sVideoPauseController = new VideoPauseController();
94         }
95         return sVideoPauseController;
96     }
97 
98     /**
99      * Configures the {@link VideoPauseController} to listen to call events.  Configured via the
100      * {@link com.android.incallui.InCallPresenter}.
101      *
102      * @param inCallPresenter The {@link com.android.incallui.InCallPresenter}.
103      */
setUp(InCallPresenter inCallPresenter)104     public void setUp(InCallPresenter inCallPresenter) {
105         log("setUp");
106         mInCallPresenter = Preconditions.checkNotNull(inCallPresenter);
107         mInCallPresenter.addListener(this);
108         mInCallPresenter.addIncomingCallListener(this);
109     }
110 
111     /**
112      * Cleans up the {@link VideoPauseController} by removing all listeners and clearing its
113      * internal state.  Called from {@link com.android.incallui.InCallPresenter}.
114      */
tearDown()115     public void tearDown() {
116         log("tearDown...");
117         mInCallPresenter.removeListener(this);
118         mInCallPresenter.removeIncomingCallListener(this);
119         clear();
120     }
121 
122     /**
123      * Clears the internal state for the {@link VideoPauseController}.
124      */
clear()125     private void clear() {
126         mInCallPresenter = null;
127         mPrimaryCallContext = null;
128         mIsInBackground = false;
129     }
130 
131     /**
132      * Handles changes in the {@link InCallState}.  Triggers pause and resumption of video for the
133      * current foreground call.
134      *
135      * @param oldState The previous {@link InCallState}.
136      * @param newState The current {@link InCallState}.
137      * @param callList List of current call.
138      */
139     @Override
onStateChange(InCallState oldState, InCallState newState, CallList callList)140     public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
141         log("onStateChange, OldState=" + oldState + " NewState=" + newState);
142 
143         Call call = null;
144         if (newState == InCallState.INCOMING) {
145             call = callList.getIncomingCall();
146         } else if (newState == InCallState.WAITING_FOR_ACCOUNT) {
147             call = callList.getWaitingForAccountCall();
148         } else if (newState == InCallState.PENDING_OUTGOING) {
149             call = callList.getPendingOutgoingCall();
150         } else if (newState == InCallState.OUTGOING) {
151             call = callList.getOutgoingCall();
152         } else {
153             call = callList.getActiveCall();
154         }
155 
156         boolean hasPrimaryCallChanged = !areSame(call, mPrimaryCallContext);
157         boolean canVideoPause = VideoUtils.canVideoPause(call);
158         log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged);
159         log("onStateChange, canVideoPause=" + canVideoPause);
160         log("onStateChange, IsInBackground=" + mIsInBackground);
161 
162         if (hasPrimaryCallChanged) {
163             onPrimaryCallChanged(call);
164             return;
165         }
166 
167         if (isDialing(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
168             // Bring UI to foreground if outgoing request becomes active while UI is in
169             // background.
170             bringToForeground();
171         } else if (!isVideoCall(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
172             // Bring UI to foreground if VoLTE call becomes active while UI is in
173             // background.
174             bringToForeground();
175         }
176 
177         updatePrimaryCallContext(call);
178     }
179 
180     /**
181      * Handles a change to the primary call.
182      * <p>
183      * Reject incoming or hangup dialing call: Where the previous call was an incoming call or a
184      * call in dialing state, resume the new primary call.
185      * Call swap: Where the new primary call is incoming, pause video on the previous primary call.
186      *
187      * @param call The new primary call.
188      */
onPrimaryCallChanged(Call call)189     private void onPrimaryCallChanged(Call call) {
190         log("onPrimaryCallChanged: New call = " + call);
191         log("onPrimaryCallChanged: Old call = " + mPrimaryCallContext);
192         log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground);
193 
194         Preconditions.checkState(!areSame(call, mPrimaryCallContext));
195         final boolean canVideoPause = VideoUtils.canVideoPause(call);
196 
197         if ((isIncomingCall(mPrimaryCallContext) || isDialing(mPrimaryCallContext) ||
198                 (call != null && VideoProfile.isPaused(call.getVideoState())))
199                 && canVideoPause && !mIsInBackground) {
200             // Send resume request for the active call, if user rejects incoming call, ends dialing
201             // call, or the call was previously in a paused state and UI is in the foreground.
202             sendRequest(call, true);
203         } else if (isIncomingCall(call) && canVideoPause(mPrimaryCallContext)) {
204             // Send pause request if there is an active video call, and we just received a new
205             // incoming call.
206             sendRequest(mPrimaryCallContext.getCall(), false);
207         }
208 
209         updatePrimaryCallContext(call);
210     }
211 
212     /**
213      * Handles new incoming calls by triggering a change in the primary call.
214      *
215      * @param oldState the old {@link InCallState}.
216      * @param newState the new {@link InCallState}.
217      * @param call the incoming call.
218      */
219     @Override
onIncomingCall(InCallState oldState, InCallState newState, Call call)220     public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
221         log("onIncomingCall, OldState=" + oldState + " NewState=" + newState + " Call=" + call);
222 
223         if (areSame(call, mPrimaryCallContext)) {
224             return;
225         }
226 
227         onPrimaryCallChanged(call);
228     }
229 
230     /**
231      * Caches a reference to the primary call and stores its previous state.
232      *
233      * @param call The new primary call.
234      */
updatePrimaryCallContext(Call call)235     private void updatePrimaryCallContext(Call call) {
236         if (call == null) {
237             mPrimaryCallContext = null;
238         } else if (mPrimaryCallContext != null) {
239             mPrimaryCallContext.update(call);
240         } else {
241             mPrimaryCallContext = new CallContext(call);
242         }
243     }
244 
245     /**
246      * Called when UI goes in/out of the foreground.
247      * @param showing true if UI is in the foreground, false otherwise.
248      */
onUiShowing(boolean showing)249     public void onUiShowing(boolean showing) {
250         // Only send pause/unpause requests if we are in the INCALL state.
251         if (mInCallPresenter == null) {
252             return;
253         }
254         final boolean isInCall = mInCallPresenter.getInCallState() == InCallState.INCALL;
255         if (showing) {
256             onResume(isInCall);
257         } else {
258             onPause(isInCall);
259         }
260     }
261 
262     /**
263      * Called when UI is brought to the foreground.  Sends a session modification request to resume
264      * the outgoing video.
265      * @param isInCall true if phone state is INCALL, false otherwise
266      */
onResume(boolean isInCall)267     private void onResume(boolean isInCall) {
268         log("onResume");
269 
270         mIsInBackground = false;
271         if (canVideoPause(mPrimaryCallContext) && isInCall) {
272             sendRequest(mPrimaryCallContext.getCall(), true);
273         } else {
274             log("onResume. Ignoring...");
275         }
276     }
277 
278     /**
279      * Called when UI is sent to the background.  Sends a session modification request to pause the
280      * outgoing video.
281      * @param isInCall true if phone state is INCALL, false otherwise
282      */
onPause(boolean isInCall)283     private void onPause(boolean isInCall) {
284         log("onPause");
285 
286         mIsInBackground = true;
287         if (canVideoPause(mPrimaryCallContext) && isInCall) {
288             sendRequest(mPrimaryCallContext.getCall(), false);
289         } else {
290             log("onPause, Ignoring...");
291         }
292     }
293 
bringToForeground()294     private void bringToForeground() {
295         if (mInCallPresenter != null) {
296             log("Bringing UI to foreground");
297             mInCallPresenter.bringToForeground(false);
298         } else {
299             loge("InCallPresenter is null. Cannot bring UI to foreground");
300         }
301     }
302 
303     /**
304      * Sends Pause/Resume request.
305      *
306      * @param call Call to be paused/resumed.
307      * @param resume If true resume request will be sent, otherwise pause request.
308      */
sendRequest(Call call, boolean resume)309     private void sendRequest(Call call, boolean resume) {
310         // Check if this call supports pause/un-pause.
311         if (!call.can(android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO)) {
312             return;
313         }
314 
315         if (resume) {
316             log("sending resume request, call=" + call);
317             call.getVideoCall()
318                     .sendSessionModifyRequest(VideoUtils.makeVideoUnPauseProfile(call));
319         } else {
320             log("sending pause request, call=" + call);
321             call.getVideoCall().sendSessionModifyRequest(VideoUtils.makeVideoPauseProfile(call));
322         }
323     }
324 
325     /**
326      * Determines if a given call is the same one stored in a {@link CallContext}.
327      *
328      * @param call The call.
329      * @param callContext The call context.
330      * @return {@code true} if the {@link Call} is the same as the one referenced in the
331      *      {@link CallContext}.
332      */
areSame(Call call, CallContext callContext)333     private static boolean areSame(Call call, CallContext callContext) {
334         if (call == null && callContext == null) {
335             return true;
336         } else if (call == null || callContext == null) {
337             return false;
338         }
339         return call.equals(callContext.getCall());
340     }
341 
342     /**
343      * Determines if a video call can be paused.  Only a video call which is active can be paused.
344      *
345      * @param callContext The call context to check.
346      * @return {@code true} if the call is an active video call.
347      */
canVideoPause(CallContext callContext)348     private static boolean canVideoPause(CallContext callContext) {
349         return isVideoCall(callContext) && callContext.getState() == Call.State.ACTIVE;
350     }
351 
352     /**
353      * Determines if a call referenced by a {@link CallContext} is a video call.
354      *
355      * @param callContext The call context.
356      * @return {@code true} if the call is a video call, {@code false} otherwise.
357      */
isVideoCall(CallContext callContext)358     private static boolean isVideoCall(CallContext callContext) {
359         return callContext != null && VideoUtils.isVideoCall(callContext.getVideoState());
360     }
361 
362     /**
363      * Determines if call is in incoming/waiting state.
364      *
365      * @param call The call context.
366      * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
367      */
isIncomingCall(CallContext call)368     private static boolean isIncomingCall(CallContext call) {
369         return call != null && isIncomingCall(call.getCall());
370     }
371 
372     /**
373      * Determines if a call is in incoming/waiting state.
374      *
375      * @param call The call.
376      * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
377      */
isIncomingCall(Call call)378     private static boolean isIncomingCall(Call call) {
379         return call != null && (call.getState() == Call.State.CALL_WAITING
380                 || call.getState() == Call.State.INCOMING);
381     }
382 
383     /**
384      * Determines if a call is dialing.
385      *
386      * @param call The call context.
387      * @return {@code true} if the call is dialing, {@code false} otherwise.
388      */
isDialing(CallContext call)389     private static boolean isDialing(CallContext call) {
390         return call != null && Call.State.isDialing(call.getState());
391     }
392 
393     /**
394      * Determines if a call is holding.
395      *
396      * @param call The call context.
397      * @return {@code true} if the call is holding, {@code false} otherwise.
398      */
isHolding(CallContext call)399     private static boolean isHolding(CallContext call) {
400         return call != null && call.getState() == Call.State.ONHOLD;
401     }
402 
403     /**
404      * Logs a debug message.
405      *
406      * @param msg The message.
407      */
log(String msg)408     private void log(String msg) {
409         Log.d(this, TAG + msg);
410     }
411 
412     /**
413      * Logs an error message.
414      *
415      * @param msg The message.
416      */
loge(String msg)417     private void loge(String msg) {
418         Log.e(this, TAG + msg);
419     }
420 }
421