Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 20 | import com.android.dialer.common.Assert; |
| 21 | import com.android.dialer.common.LogUtil; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 35 | private static VideoPauseController videoPauseController; |
| 36 | private InCallPresenter inCallPresenter; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 37 | |
| 38 | /** The current call, if applicable. */ |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 39 | private DialerCall primaryCall = null; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 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 | */ |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 46 | private int prevCallState = State.INVALID; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 47 | |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 48 | private boolean wasVideoCall = false; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 49 | |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 50 | /** |
| 51 | * Tracks whether the application is in the background. {@code True} if the application is in the |
| 52 | * background, {@code false} otherwise. |
| 53 | */ |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 54 | private boolean isInBackground = false; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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() { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 63 | if (videoPauseController == null) { |
| 64 | videoPauseController = new VideoPauseController(); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 65 | } |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 66 | return videoPauseController; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 67 | } |
| 68 | |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 75 | private static boolean isIncomingCall(DialerCall call) { |
| 76 | return call != null |
| 77 | && (call.getState() == DialerCall.State.CALL_WAITING |
| 78 | || call.getState() == DialerCall.State.INCOMING); |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Determines if a call is dialing. |
| 83 | * |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 84 | * @return {@code true} if the call is dialing, {@code false} otherwise. |
| 85 | */ |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 86 | private boolean wasDialing() { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 87 | return DialerCall.State.isDialing(prevCallState); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 96 | public void setUp(@NonNull InCallPresenter inCallPresenter) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 97 | LogUtil.enterBlock("VideoPauseController.setUp"); |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 98 | this.inCallPresenter = Assert.isNotNull(inCallPresenter); |
| 99 | this.inCallPresenter.addListener(this); |
| 100 | this.inCallPresenter.addIncomingCallListener(this); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 107 | public void tearDown() { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 108 | LogUtil.enterBlock("VideoPauseController.tearDown"); |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 109 | inCallPresenter.removeListener(this); |
| 110 | inCallPresenter.removeIncomingCallListener(this); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 111 | clear(); |
| 112 | } |
| 113 | |
| 114 | /** Clears the internal state for the {@link VideoPauseController}. */ |
| 115 | private void clear() { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 116 | inCallPresenter = null; |
| 117 | primaryCall = null; |
| 118 | prevCallState = State.INVALID; |
| 119 | wasVideoCall = false; |
| 120 | isInBackground = false; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 |
| 132 | public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 146 | boolean hasPrimaryCallChanged = !Objects.equals(call, primaryCall); |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 147 | boolean canVideoPause = videoCanPause(call); |
| 148 | |
| 149 | LogUtil.i( |
| 150 | "VideoPauseController.onStateChange", |
| 151 | "hasPrimaryCallChanged: %b, videoCanPause: %b, isInBackground: %b", |
| 152 | hasPrimaryCallChanged, |
| 153 | canVideoPause, |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 154 | isInBackground); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 155 | |
| 156 | if (hasPrimaryCallChanged) { |
| 157 | onPrimaryCallChanged(call); |
| 158 | return; |
| 159 | } |
| 160 | |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 161 | if (wasDialing() && canVideoPause && isInBackground) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 162 | // Bring UI to foreground if outgoing request becomes active while UI is in |
| 163 | // background. |
| 164 | bringToForeground(); |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 165 | } else if (!wasVideoCall && canVideoPause && isInBackground) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 183 | private void onPrimaryCallChanged(DialerCall call) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 184 | LogUtil.i( |
| 185 | "VideoPauseController.onPrimaryCallChanged", |
| 186 | "new call: %s, old call: %s, mIsInBackground: %b", |
| 187 | call, |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 188 | primaryCall, |
| 189 | isInBackground); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 190 | |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 191 | if (Objects.equals(call, primaryCall)) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 192 | throw new IllegalStateException(); |
| 193 | } |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 194 | final boolean canVideoPause = videoCanPause(call); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 195 | |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 196 | if (canVideoPause && !isInBackground) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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); |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 200 | } else if (isIncomingCall(call) && videoCanPause(primaryCall)) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 201 | // Send pause request if there is an active video call, and we just received a new |
| 202 | // incoming call. |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 203 | sendRequest(primaryCall, false); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 |
| 217 | public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 218 | LogUtil.i( |
| 219 | "VideoPauseController.onIncomingCall", |
| 220 | "oldState: %s, newState: %s, call: %s", |
| 221 | oldState, |
| 222 | newState, |
| 223 | call); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 224 | |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 225 | if (Objects.equals(call, primaryCall)) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 237 | private void updatePrimaryCallContext(DialerCall call) { |
| 238 | if (call == null) { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 239 | primaryCall = null; |
| 240 | prevCallState = State.INVALID; |
| 241 | wasVideoCall = false; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 242 | } else { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 243 | primaryCall = call; |
| 244 | prevCallState = call.getState(); |
| 245 | wasVideoCall = call.isVideoCall(); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 254 | public void onUiShowing(boolean showing) { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 255 | if (inCallPresenter == null) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 256 | return; |
| 257 | } |
| 258 | |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 259 | final boolean isInCall = inCallPresenter.getInCallState() == InCallState.INCALL; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 274 | private void onResume(boolean isInCall) { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 275 | isInBackground = false; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 276 | if (isInCall) { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 277 | sendRequest(primaryCall, true); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 288 | private void onPause(boolean isInCall) { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 289 | isInBackground = true; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 290 | if (isInCall) { |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 291 | sendRequest(primaryCall, false); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 292 | } |
| 293 | } |
| 294 | |
| 295 | private void bringToForeground() { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 296 | LogUtil.enterBlock("VideoPauseController.bringToForeground"); |
linyuh | 183cb71 | 2017-12-27 17:02:37 -0800 | [diff] [blame] | 297 | if (inCallPresenter != null) { |
| 298 | inCallPresenter.bringToForeground(false); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 299 | } else { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 300 | LogUtil.e( |
| 301 | "VideoPauseController.bringToForeground", |
| 302 | "InCallPresenter is null. Cannot bring UI to foreground"); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | */ |
| 312 | private void sendRequest(DialerCall call, boolean resume) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 313 | if (call == null) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 314 | return; |
| 315 | } |
| 316 | |
| 317 | if (resume) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 318 | call.getVideoTech().unpause(); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 319 | } else { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 320 | call.getVideoTech().pause(); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 321 | } |
| 322 | } |
| 323 | |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame] | 324 | private static boolean videoCanPause(DialerCall call) { |
| 325 | return call != null && call.isVideoCall() && call.getState() == DialerCall.State.ACTIVE; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 326 | } |
| 327 | } |