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 || mInCallPresenter.getInCallState() != InCallState.INCALL) { 252 return; 253 } 254 255 if (showing) { 256 onResume(); 257 } else { 258 onPause(); 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 */ onResume()266 private void onResume() { 267 log("onResume"); 268 269 mIsInBackground = false; 270 if (canVideoPause(mPrimaryCallContext)) { 271 sendRequest(mPrimaryCallContext.getCall(), true); 272 } else { 273 log("onResume. Ignoring..."); 274 } 275 } 276 277 /** 278 * Called when UI is sent to the background. Sends a session modification request to pause the 279 * outgoing video. 280 */ onPause()281 private void onPause() { 282 log("onPause"); 283 284 mIsInBackground = true; 285 if (canVideoPause(mPrimaryCallContext)) { 286 sendRequest(mPrimaryCallContext.getCall(), false); 287 } else { 288 log("onPause, Ignoring..."); 289 } 290 } 291 bringToForeground()292 private void bringToForeground() { 293 if (mInCallPresenter != null) { 294 log("Bringing UI to foreground"); 295 mInCallPresenter.bringToForeground(false); 296 } else { 297 loge("InCallPresenter is null. Cannot bring UI to foreground"); 298 } 299 } 300 301 /** 302 * Sends Pause/Resume request. 303 * 304 * @param call Call to be paused/resumed. 305 * @param resume If true resume request will be sent, otherwise pause request. 306 */ sendRequest(Call call, boolean resume)307 private void sendRequest(Call call, boolean resume) { 308 // Check if this call supports pause/un-pause. 309 if (!call.can(android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO)) { 310 return; 311 } 312 313 if (resume) { 314 log("sending resume request, call=" + call); 315 call.getVideoCall() 316 .sendSessionModifyRequest(VideoUtils.makeVideoUnPauseProfile(call)); 317 } else { 318 log("sending pause request, call=" + call); 319 call.getVideoCall().sendSessionModifyRequest(VideoUtils.makeVideoPauseProfile(call)); 320 } 321 } 322 323 /** 324 * Determines if a given call is the same one stored in a {@link CallContext}. 325 * 326 * @param call The call. 327 * @param callContext The call context. 328 * @return {@code true} if the {@link Call} is the same as the one referenced in the 329 * {@link CallContext}. 330 */ areSame(Call call, CallContext callContext)331 private static boolean areSame(Call call, CallContext callContext) { 332 if (call == null && callContext == null) { 333 return true; 334 } else if (call == null || callContext == null) { 335 return false; 336 } 337 return call.equals(callContext.getCall()); 338 } 339 340 /** 341 * Determines if a video call can be paused. Only a video call which is active can be paused. 342 * 343 * @param callContext The call context to check. 344 * @return {@code true} if the call is an active video call. 345 */ canVideoPause(CallContext callContext)346 private static boolean canVideoPause(CallContext callContext) { 347 return isVideoCall(callContext) && callContext.getState() == Call.State.ACTIVE; 348 } 349 350 /** 351 * Determines if a call referenced by a {@link CallContext} is a video call. 352 * 353 * @param callContext The call context. 354 * @return {@code true} if the call is a video call, {@code false} otherwise. 355 */ isVideoCall(CallContext callContext)356 private static boolean isVideoCall(CallContext callContext) { 357 return callContext != null && VideoUtils.isVideoCall(callContext.getVideoState()); 358 } 359 360 /** 361 * Determines if call is in incoming/waiting state. 362 * 363 * @param call The call context. 364 * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise. 365 */ isIncomingCall(CallContext call)366 private static boolean isIncomingCall(CallContext call) { 367 return call != null && isIncomingCall(call.getCall()); 368 } 369 370 /** 371 * Determines if a call is in incoming/waiting state. 372 * 373 * @param call The call. 374 * @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise. 375 */ isIncomingCall(Call call)376 private static boolean isIncomingCall(Call call) { 377 return call != null && (call.getState() == Call.State.CALL_WAITING 378 || call.getState() == Call.State.INCOMING); 379 } 380 381 /** 382 * Determines if a call is dialing. 383 * 384 * @param call The call context. 385 * @return {@code true} if the call is dialing, {@code false} otherwise. 386 */ isDialing(CallContext call)387 private static boolean isDialing(CallContext call) { 388 return call != null && Call.State.isDialing(call.getState()); 389 } 390 391 /** 392 * Determines if a call is holding. 393 * 394 * @param call The call context. 395 * @return {@code true} if the call is holding, {@code false} otherwise. 396 */ isHolding(CallContext call)397 private static boolean isHolding(CallContext call) { 398 return call != null && call.getState() == Call.State.ONHOLD; 399 } 400 401 /** 402 * Logs a debug message. 403 * 404 * @param msg The message. 405 */ log(String msg)406 private void log(String msg) { 407 Log.d(this, TAG + msg); 408 } 409 410 /** 411 * Logs an error message. 412 * 413 * @param msg The message. 414 */ loge(String msg)415 private void loge(String msg) { 416 Log.e(this, TAG + msg); 417 } 418 } 419