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