1 /* 2 * Copyright 2017 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.server.telecom; 18 19 import android.annotation.Nullable; 20 import android.content.ComponentName; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.telecom.Log; 25 import android.telecom.Logging.Session; 26 import android.text.TextUtils; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.Objects; 33 import java.util.stream.Collectors; 34 35 public class ConnectionServiceFocusManager { 36 private static final String TAG = "ConnectionSvrFocusMgr"; 37 38 /** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */ 39 public interface ConnectionServiceFocusManagerFactory { 40 ConnectionServiceFocusManager create(CallsManagerRequester requester, Looper looper); 41 } 42 43 /** 44 * Interface used by ConnectionServiceFocusManager to communicate with 45 * {@link ConnectionServiceWrapper}. 46 */ 47 public interface ConnectionServiceFocus { 48 /** 49 * Notifies the {@link android.telecom.ConnectionService} that it has lose the connection 50 * service focus. It should release all call resource i.e camera, audio once it lost the 51 * focus. 52 */ 53 void connectionServiceFocusLost(); 54 55 /** 56 * Notifies the {@link android.telecom.ConnectionService} that it has gain the connection 57 * service focus. It can request the call resource i.e camera, audio as they expected to be 58 * free at the moment. 59 */ 60 void connectionServiceFocusGained(); 61 62 /** 63 * Sets the ConnectionServiceFocusListener. 64 * 65 * @see {@link ConnectionServiceFocusListener}. 66 */ 67 void setConnectionServiceFocusListener(ConnectionServiceFocusListener listener); 68 69 /** 70 * Get the {@link ComponentName} of the ConnectionService for logging purposes. 71 * @return the {@link ComponentName}. 72 */ 73 ComponentName getComponentName(); 74 } 75 76 /** 77 * Interface used to receive the changed of {@link android.telecom.ConnectionService} that 78 * ConnectionServiceFocusManager cares about. 79 */ 80 public interface ConnectionServiceFocusListener { 81 /** 82 * Calls when {@link android.telecom.ConnectionService} has released the call resource. This 83 * usually happen after the {@link android.telecom.ConnectionService} lost the focus. 84 * 85 * @param connectionServiceFocus the {@link android.telecom.ConnectionService} that released 86 * the call resources. 87 */ 88 void onConnectionServiceReleased(ConnectionServiceFocus connectionServiceFocus); 89 90 /** 91 * Calls when {@link android.telecom.ConnectionService} is disconnected. 92 * 93 * @param connectionServiceFocus the {@link android.telecom.ConnectionService} which is 94 * disconnected. 95 */ 96 void onConnectionServiceDeath(ConnectionServiceFocus connectionServiceFocus); 97 } 98 99 /** 100 * Interface define to expose few information of {@link Call} that ConnectionServiceFocusManager 101 * cares about. 102 */ 103 public interface CallFocus { 104 /** 105 * Returns the ConnectionService associated with the call. 106 */ 107 ConnectionServiceFocus getConnectionServiceWrapper(); 108 109 /** 110 * Returns the state of the call. 111 * 112 * @see {@link CallState} 113 */ 114 int getState(); 115 116 /** 117 * @return {@code True} if this call can receive focus, {@code false} otherwise. 118 */ 119 boolean isFocusable(); 120 } 121 122 /** Interface define a call back for focus request event. */ 123 public interface RequestFocusCallback { 124 /** 125 * Invokes after the focus request is done. 126 * 127 * @param call the call associated with the focus request. 128 */ 129 void onRequestFocusDone(CallFocus call); 130 } 131 132 /** 133 * Interface define to allow the ConnectionServiceFocusManager to communicate with 134 * {@link CallsManager}. 135 */ 136 public interface CallsManagerRequester { 137 /** 138 * Requests {@link CallsManager} to disconnect a {@link ConnectionServiceFocus}. This 139 * usually happen when the connection service doesn't respond to focus lost event. 140 */ 141 void releaseConnectionService(ConnectionServiceFocus connectionService); 142 143 /** 144 * Sets the {@link com.android.server.telecom.CallsManager.CallsManagerListener} to listen 145 * the call event that ConnectionServiceFocusManager cares about. 146 */ 147 void setCallsManagerListener(CallsManager.CallsManagerListener listener); 148 } 149 150 private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] { 151 CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING 152 }; 153 154 private static final int MSG_REQUEST_FOCUS = 1; 155 private static final int MSG_RELEASE_CONNECTION_FOCUS = 2; 156 private static final int MSG_RELEASE_FOCUS_TIMEOUT = 3; 157 private static final int MSG_CONNECTION_SERVICE_DEATH = 4; 158 private static final int MSG_ADD_CALL = 5; 159 private static final int MSG_REMOVE_CALL = 6; 160 private static final int MSG_CALL_STATE_CHANGED = 7; 161 162 @VisibleForTesting 163 public static final int RELEASE_FOCUS_TIMEOUT_MS = 5000; 164 165 private final List<CallFocus> mCalls; 166 167 private final CallsManagerListenerBase mCallsManagerListener = 168 new CallsManagerListenerBase() { 169 @Override 170 public void onCallAdded(Call call) { 171 if (callShouldBeIgnored(call)) { 172 return; 173 } 174 175 mEventHandler 176 .obtainMessage(MSG_ADD_CALL, 177 new MessageArgs( 178 Log.createSubsession(), 179 "CSFM.oCA", 180 call)) 181 .sendToTarget(); 182 } 183 184 @Override 185 public void onCallRemoved(Call call) { 186 if (callShouldBeIgnored(call)) { 187 return; 188 } 189 190 mEventHandler 191 .obtainMessage(MSG_REMOVE_CALL, 192 new MessageArgs( 193 Log.createSubsession(), 194 "CSFM.oCR", 195 call)) 196 .sendToTarget(); 197 } 198 199 @Override 200 public void onCallStateChanged(Call call, int oldState, int newState) { 201 if (callShouldBeIgnored(call)) { 202 return; 203 } 204 205 mEventHandler 206 .obtainMessage(MSG_CALL_STATE_CHANGED, oldState, newState, 207 new MessageArgs( 208 Log.createSubsession(), 209 "CSFM.oCSS", 210 call)) 211 .sendToTarget(); 212 } 213 214 @Override 215 public void onExternalCallChanged(Call call, boolean isExternalCall) { 216 if (isExternalCall) { 217 mEventHandler 218 .obtainMessage(MSG_REMOVE_CALL, 219 new MessageArgs( 220 Log.createSubsession(), 221 "CSFM.oECC", 222 call)) 223 .sendToTarget(); 224 } else { 225 mEventHandler 226 .obtainMessage(MSG_ADD_CALL, 227 new MessageArgs( 228 Log.createSubsession(), 229 "CSFM.oECC", 230 call)) 231 .sendToTarget(); 232 } 233 } 234 235 boolean callShouldBeIgnored(Call call) { 236 return call.isExternalCall(); 237 } 238 }; 239 240 private final ConnectionServiceFocusListener mConnectionServiceFocusListener = 241 new ConnectionServiceFocusListener() { 242 @Override 243 public void onConnectionServiceReleased( 244 ConnectionServiceFocus connectionServiceFocus) { 245 mEventHandler 246 .obtainMessage(MSG_RELEASE_CONNECTION_FOCUS, 247 new MessageArgs( 248 Log.createSubsession(), 249 "CSFM.oCSR", 250 connectionServiceFocus)) 251 .sendToTarget(); 252 } 253 254 @Override 255 public void onConnectionServiceDeath( 256 ConnectionServiceFocus connectionServiceFocus) { 257 mEventHandler 258 .obtainMessage(MSG_CONNECTION_SERVICE_DEATH, 259 new MessageArgs( 260 Log.createSubsession(), 261 "CSFM.oCSD", 262 connectionServiceFocus)) 263 .sendToTarget(); 264 } 265 }; 266 267 private ConnectionServiceFocus mCurrentFocus; 268 private CallFocus mCurrentFocusCall; 269 private CallsManagerRequester mCallsManagerRequester; 270 private FocusRequest mCurrentFocusRequest; 271 private FocusManagerHandler mEventHandler; 272 273 public ConnectionServiceFocusManager( 274 CallsManagerRequester callsManagerRequester, Looper looper) { 275 mCallsManagerRequester = callsManagerRequester; 276 mCallsManagerRequester.setCallsManagerListener(mCallsManagerListener); 277 mEventHandler = new FocusManagerHandler(looper); 278 mCalls = new ArrayList<>(); 279 } 280 281 /** 282 * Requests the call focus for the given call. The {@code callback} will be invoked once 283 * the request is done. 284 * @param focus the call need to be focus. 285 * @param callback the callback associated with this request. 286 */ 287 public void requestFocus(CallFocus focus, RequestFocusCallback callback) { 288 mEventHandler.obtainMessage(MSG_REQUEST_FOCUS, 289 new MessageArgs( 290 Log.createSubsession(), 291 "CSFM.rF", 292 new FocusRequest(focus, callback))) 293 .sendToTarget(); 294 } 295 296 /** 297 * Returns the current focus call. The {@link android.telecom.ConnectionService} of the focus 298 * call is the current connection service focus. Also the state of the focus call must be one 299 * of {@link #PRIORITY_FOCUS_CALL_STATE}. 300 */ 301 public CallFocus getCurrentFocusCall() { 302 return mCurrentFocusCall; 303 } 304 305 /** Returns the current connection service focus. */ 306 public ConnectionServiceFocus getCurrentFocusConnectionService() { 307 return mCurrentFocus; 308 } 309 310 @VisibleForTesting 311 public Handler getHandler() { 312 return mEventHandler; 313 } 314 315 @VisibleForTesting 316 public List<CallFocus> getAllCall() { return mCalls; } 317 318 private void updateConnectionServiceFocus(ConnectionServiceFocus connSvrFocus) { 319 if (!Objects.equals(mCurrentFocus, connSvrFocus)) { 320 if (connSvrFocus != null) { 321 connSvrFocus.setConnectionServiceFocusListener(mConnectionServiceFocusListener); 322 connSvrFocus.connectionServiceFocusGained(); 323 } 324 mCurrentFocus = connSvrFocus; 325 Log.d(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus); 326 } 327 } 328 329 private void updateCurrentFocusCall() { 330 mCurrentFocusCall = null; 331 332 if (mCurrentFocus == null) { 333 return; 334 } 335 336 List<CallFocus> calls = mCalls 337 .stream() 338 .filter(call -> mCurrentFocus.equals(call.getConnectionServiceWrapper()) 339 && call.isFocusable()) 340 .collect(Collectors.toList()); 341 342 for (int i = 0; i < PRIORITY_FOCUS_CALL_STATE.length; i++) { 343 for (CallFocus call : calls) { 344 if (call.getState() == PRIORITY_FOCUS_CALL_STATE[i]) { 345 mCurrentFocusCall = call; 346 Log.d(this, "updateCurrentFocusCall %s", mCurrentFocusCall); 347 return; 348 } 349 } 350 } 351 352 Log.d(this, "updateCurrentFocusCall = null"); 353 } 354 355 private void onRequestFocusDone(FocusRequest focusRequest) { 356 if (focusRequest.callback != null) { 357 focusRequest.callback.onRequestFocusDone(focusRequest.call); 358 } 359 } 360 361 private void handleRequestFocus(FocusRequest focusRequest) { 362 Log.d(this, "handleRequestFocus req = %s", focusRequest); 363 if (mCurrentFocus == null 364 || mCurrentFocus.equals(focusRequest.call.getConnectionServiceWrapper())) { 365 updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper()); 366 updateCurrentFocusCall(); 367 onRequestFocusDone(focusRequest); 368 } else { 369 mCurrentFocus.connectionServiceFocusLost(); 370 mCurrentFocusRequest = focusRequest; 371 Message msg = mEventHandler.obtainMessage( 372 MSG_RELEASE_FOCUS_TIMEOUT, 373 new MessageArgs( 374 Log.createSubsession(), 375 "CSFM.hRF", 376 focusRequest)); 377 mEventHandler.sendMessageDelayed(msg, RELEASE_FOCUS_TIMEOUT_MS); 378 } 379 } 380 381 private void handleReleasedFocus(ConnectionServiceFocus connectionServiceFocus) { 382 Log.d(this, "handleReleasedFocus connSvr = %s", connectionServiceFocus); 383 // The ConnectionService can call onConnectionServiceFocusReleased even if it's not the 384 // current focus connection service, nothing will be changed in this case. 385 if (Objects.equals(mCurrentFocus, connectionServiceFocus)) { 386 mEventHandler.removeMessages(MSG_RELEASE_FOCUS_TIMEOUT); 387 ConnectionServiceFocus newCSF = null; 388 if (mCurrentFocusRequest != null) { 389 newCSF = mCurrentFocusRequest.call.getConnectionServiceWrapper(); 390 } 391 updateConnectionServiceFocus(newCSF); 392 updateCurrentFocusCall(); 393 if (mCurrentFocusRequest != null) { 394 onRequestFocusDone(mCurrentFocusRequest); 395 mCurrentFocusRequest = null; 396 } 397 } 398 } 399 400 private void handleReleasedFocusTimeout(FocusRequest focusRequest) { 401 Log.d(this, "handleReleasedFocusTimeout req = %s", focusRequest); 402 mCallsManagerRequester.releaseConnectionService(mCurrentFocus); 403 updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper()); 404 updateCurrentFocusCall(); 405 onRequestFocusDone(focusRequest); 406 mCurrentFocusRequest = null; 407 } 408 409 private void handleConnectionServiceDeath(ConnectionServiceFocus connectionServiceFocus) { 410 Log.d(this, "handleConnectionServiceDeath %s", connectionServiceFocus); 411 if (Objects.equals(connectionServiceFocus, mCurrentFocus)) { 412 updateConnectionServiceFocus(null); 413 updateCurrentFocusCall(); 414 } 415 } 416 417 private void handleAddedCall(CallFocus call) { 418 Log.d(this, "handleAddedCall %s", call); 419 if (!mCalls.contains(call)) { 420 mCalls.add(call); 421 } 422 if (Objects.equals(mCurrentFocus, call.getConnectionServiceWrapper())) { 423 updateCurrentFocusCall(); 424 } 425 } 426 427 private void handleRemovedCall(CallFocus call) { 428 Log.d(this, "handleRemovedCall %s", call); 429 mCalls.remove(call); 430 if (call.equals(mCurrentFocusCall)) { 431 updateCurrentFocusCall(); 432 } 433 } 434 435 private void handleCallStateChanged(CallFocus call, int oldState, int newState) { 436 Log.d(this, 437 "handleCallStateChanged %s, oldState = %d, newState = %d", 438 call, 439 oldState, 440 newState); 441 if (mCalls.contains(call) 442 && Objects.equals(mCurrentFocus, call.getConnectionServiceWrapper())) { 443 updateCurrentFocusCall(); 444 } 445 } 446 447 private final class FocusManagerHandler extends Handler { 448 FocusManagerHandler(Looper looper) { 449 super(looper); 450 } 451 452 @Override 453 public void handleMessage(Message msg) { 454 Session session = ((MessageArgs) msg.obj).logSession; 455 String shortName = ((MessageArgs) msg.obj).shortName; 456 if (TextUtils.isEmpty(shortName)) { 457 shortName = "hM"; 458 } 459 Log.continueSession(session, shortName); 460 Object msgObj = ((MessageArgs) msg.obj).obj; 461 462 try { 463 switch (msg.what) { 464 case MSG_REQUEST_FOCUS: 465 handleRequestFocus((FocusRequest) msgObj); 466 break; 467 case MSG_RELEASE_CONNECTION_FOCUS: 468 handleReleasedFocus((ConnectionServiceFocus) msgObj); 469 break; 470 case MSG_RELEASE_FOCUS_TIMEOUT: 471 handleReleasedFocusTimeout((FocusRequest) msgObj); 472 break; 473 case MSG_CONNECTION_SERVICE_DEATH: 474 handleConnectionServiceDeath((ConnectionServiceFocus) msgObj); 475 break; 476 case MSG_ADD_CALL: 477 handleAddedCall((CallFocus) msgObj); 478 break; 479 case MSG_REMOVE_CALL: 480 handleRemovedCall((CallFocus) msgObj); 481 break; 482 case MSG_CALL_STATE_CHANGED: 483 handleCallStateChanged((CallFocus) msgObj, msg.arg1, msg.arg2); 484 break; 485 } 486 } finally { 487 Log.endSession(); 488 } 489 } 490 } 491 492 private static final class FocusRequest { 493 CallFocus call; 494 @Nullable RequestFocusCallback callback; 495 496 FocusRequest(CallFocus call, RequestFocusCallback callback) { 497 this.call = call; 498 this.callback = callback; 499 } 500 } 501 502 private static final class MessageArgs { 503 Session logSession; 504 String shortName; 505 Object obj; 506 507 MessageArgs(Session logSession, String shortName, Object obj) { 508 this.logSession = logSession; 509 this.shortName = shortName; 510 this.obj = obj; 511 } 512 } 513 } 514