• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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