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