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