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