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