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