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*/ 62 static synchronized VideoPauseController getInstance() { 63 if (sVideoPauseController == null) { 64 sVideoPauseController = new VideoPauseController(); 65 } 66 return sVideoPauseController; 67 } 68 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 */ 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 */ 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 */ 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 */ 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}. */ 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 293 private void onPause(boolean isInCall) { 294 mIsInBackground = true; 295 if (isInCall) { 296 sendRequest(mPrimaryCall, false); 297 } 298 } 299 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 */ 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 329 private static boolean videoCanPause(DialerCall call) { 330 return call != null && call.isVideoCall() && call.getState() == DialerCall.State.ACTIVE; 331 } 332 } 333