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 { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 35 | private static VideoPauseController sVideoPauseController; |
| 36 | private InCallPresenter mInCallPresenter; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 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 | |
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 | */ |
| 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 | |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 69 | private boolean wasIncomingCall() { |
| 70 | return (mPrevCallState == DialerCall.State.CALL_WAITING |
| 71 | || mPrevCallState == DialerCall.State.INCOMING); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | * |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 89 | * @return {@code true} if the call is dialing, {@code false} otherwise. |
| 90 | */ |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 91 | private boolean wasDialing() { |
| 92 | return DialerCall.State.isDialing(mPrevCallState); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 102 | LogUtil.enterBlock("VideoPauseController.setUp"); |
| 103 | mInCallPresenter = Assert.isNotNull(inCallPresenter); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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() { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 113 | LogUtil.enterBlock("VideoPauseController.tearDown"); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 122 | mPrimaryCall = null; |
| 123 | mPrevCallState = State.INVALID; |
| 124 | mWasVideoCall = false; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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 | |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 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); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 160 | |
| 161 | if (hasPrimaryCallChanged) { |
| 162 | onPrimaryCallChanged(call); |
| 163 | return; |
| 164 | } |
| 165 | |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 166 | if (wasDialing() && canVideoPause && mIsInBackground) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 167 | // Bring UI to foreground if outgoing request becomes active while UI is in |
| 168 | // background. |
| 169 | bringToForeground(); |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 170 | } else if (!mWasVideoCall && canVideoPause && mIsInBackground) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 189 | LogUtil.i( |
| 190 | "VideoPauseController.onPrimaryCallChanged", |
| 191 | "new call: %s, old call: %s, mIsInBackground: %b", |
| 192 | call, |
| 193 | mPrimaryCall, |
| 194 | mIsInBackground); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 195 | |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 196 | if (Objects.equals(call, mPrimaryCall)) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 197 | throw new IllegalStateException(); |
| 198 | } |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 199 | final boolean canVideoPause = videoCanPause(call); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 200 | |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 201 | if ((wasIncomingCall() || wasDialing()) && canVideoPause && !mIsInBackground) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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); |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 205 | } else if (isIncomingCall(call) && videoCanPause(mPrimaryCall)) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 206 | // Send pause request if there is an active video call, and we just received a new |
| 207 | // incoming call. |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 208 | sendRequest(mPrimaryCall, false); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 223 | LogUtil.i( |
| 224 | "VideoPauseController.onIncomingCall", |
| 225 | "oldState: %s, newState: %s, call: %s", |
| 226 | oldState, |
| 227 | newState, |
| 228 | call); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 229 | |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 230 | if (Objects.equals(call, mPrimaryCall)) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 244 | mPrimaryCall = null; |
| 245 | mPrevCallState = State.INVALID; |
| 246 | mWasVideoCall = false; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 247 | } else { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 248 | mPrimaryCall = call; |
| 249 | mPrevCallState = call.getState(); |
| 250 | mWasVideoCall = call.isVideoCall(); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 280 | mIsInBackground = false; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 281 | if (isInCall) { |
| 282 | sendRequest(mPrimaryCall, true); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 294 | mIsInBackground = true; |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 295 | if (isInCall) { |
| 296 | sendRequest(mPrimaryCall, false); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 297 | } |
| 298 | } |
| 299 | |
| 300 | private void bringToForeground() { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 301 | LogUtil.enterBlock("VideoPauseController.bringToForeground"); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 302 | if (mInCallPresenter != null) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 303 | mInCallPresenter.bringToForeground(false); |
| 304 | } else { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 305 | LogUtil.e( |
| 306 | "VideoPauseController.bringToForeground", |
| 307 | "InCallPresenter is null. Cannot bring UI to foreground"); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 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) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 318 | if (call == null) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 319 | return; |
| 320 | } |
| 321 | |
| 322 | if (resume) { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 323 | call.getVideoTech().unpause(); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 324 | } else { |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 325 | call.getVideoTech().pause(); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 326 | } |
| 327 | } |
| 328 | |
Eric Erfanian | d5e47f6 | 2017-03-15 14:41:07 -0700 | [diff] [blame^] | 329 | private static boolean videoCanPause(DialerCall call) { |
| 330 | return call != null && call.isVideoCall() && call.getState() == DialerCall.State.ACTIVE; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 331 | } |
| 332 | } |