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