• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.Manifest;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.ServiceConnection;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.content.pm.ServiceInfo;
27 import android.content.res.Resources;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.Trace;
34 import android.os.UserHandle;
35 import android.telecom.CallAudioState;
36 import android.telecom.ConnectionService;
37 import android.telecom.DefaultDialerManager;
38 import android.telecom.InCallService;
39 import android.telecom.ParcelableCall;
40 import android.telecom.TelecomManager;
41 import android.text.TextUtils;
42 import android.util.ArrayMap;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 // TODO: Needed for move to system service: import com.android.internal.R;
46 import com.android.internal.telecom.IInCallService;
47 import com.android.internal.util.IndentingPrintWriter;
48 import com.android.server.telecom.SystemStateProvider.SystemStateListener;
49 import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
50 
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.LinkedList;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Objects;
57 
58 /**
59  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
60  * can send updates to the in-call app. This class is created and owned by CallsManager and retains
61  * a binding to the {@link IInCallService} (implemented by the in-call app).
62  */
63 public final class InCallController extends CallsManagerListenerBase {
64 
65     public class InCallServiceConnection {
66         public class Listener {
onDisconnect(InCallServiceConnection conn)67             public void onDisconnect(InCallServiceConnection conn) {}
68         }
69 
70         protected Listener mListener;
71 
connect(Call call)72         public boolean connect(Call call) { return false; }
disconnect()73         public void disconnect() {}
setHasEmergency(boolean hasEmergency)74         public void setHasEmergency(boolean hasEmergency) {}
setListener(Listener l)75         public void setListener(Listener l) {
76             mListener = l;
77         }
dump(IndentingPrintWriter pw)78         public void dump(IndentingPrintWriter pw) {}
79     }
80 
81     private class InCallServiceInfo {
82         private ComponentName mComponentName;
83         private boolean mIsExternalCallsSupported;
84 
InCallServiceInfo(ComponentName componentName, boolean isExternalCallsSupported)85         public InCallServiceInfo(ComponentName componentName, boolean isExternalCallsSupported) {
86             mComponentName = componentName;
87             mIsExternalCallsSupported = isExternalCallsSupported;
88         }
89 
getComponentName()90         public ComponentName getComponentName() {
91             return mComponentName;
92         }
93 
isExternalCallsSupported()94         public boolean isExternalCallsSupported() {
95             return mIsExternalCallsSupported;
96         }
97 
98         @Override
equals(Object o)99         public boolean equals(Object o) {
100             if (this == o) {
101                 return true;
102             }
103             if (o == null || getClass() != o.getClass()) {
104                 return false;
105             }
106 
107             InCallServiceInfo that = (InCallServiceInfo) o;
108 
109             if (mIsExternalCallsSupported != that.mIsExternalCallsSupported) {
110                 return false;
111             }
112             return mComponentName.equals(that.mComponentName);
113 
114         }
115 
116         @Override
hashCode()117         public int hashCode() {
118             return Objects.hash(mComponentName, mIsExternalCallsSupported);
119         }
120 
121         @Override
toString()122         public String toString() {
123             return "[" + mComponentName + " supportsExternal? " + mIsExternalCallsSupported + "]";
124         }
125     }
126 
127     private class InCallServiceBindingConnection extends InCallServiceConnection {
128 
129         private final ServiceConnection mServiceConnection = new ServiceConnection() {
130             @Override
131             public void onServiceConnected(ComponentName name, IBinder service) {
132                 Log.startSession("ICSBC.oSC");
133                 synchronized (mLock) {
134                     try {
135                         Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);
136                         mIsBound = true;
137                         if (mIsConnected) {
138                             // Only proceed if we are supposed to be connected.
139                             onConnected(service);
140                         }
141                     } finally {
142                         Log.endSession();
143                     }
144                 }
145             }
146 
147             @Override
148             public void onServiceDisconnected(ComponentName name) {
149                 Log.startSession("ICSBC.oSD");
150                 synchronized (mLock) {
151                     try {
152                         Log.d(this, "onDisconnected: %s", name);
153                         mIsBound = false;
154                         onDisconnected();
155                     } finally {
156                         Log.endSession();
157                     }
158                 }
159             }
160         };
161 
162         private final InCallServiceInfo mInCallServiceInfo;
163         private boolean mIsConnected = false;
164         private boolean mIsBound = false;
165 
InCallServiceBindingConnection(InCallServiceInfo info)166         public InCallServiceBindingConnection(InCallServiceInfo info) {
167             mInCallServiceInfo = info;
168         }
169 
170         @Override
connect(Call call)171         public boolean connect(Call call) {
172             if (mIsConnected) {
173                 Log.event(call, Log.Events.INFO, "Already connected, ignoring request.");
174                 return true;
175             }
176 
177             Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
178             intent.setComponent(mInCallServiceInfo.getComponentName());
179             if (call != null && !call.isIncoming() && !call.isExternalCall()){
180                 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
181                         call.getIntentExtras());
182                 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
183                         call.getTargetPhoneAccount());
184             }
185 
186             Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
187             mIsConnected = true;
188             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
189                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
190                         UserHandle.CURRENT)) {
191                 Log.w(this, "Failed to connect.");
192                 mIsConnected = false;
193             }
194 
195             return mIsConnected;
196         }
197 
198         @Override
disconnect()199         public void disconnect() {
200             if (mIsConnected) {
201                 mContext.unbindService(mServiceConnection);
202                 mIsConnected = false;
203             } else {
204                 Log.event(null, Log.Events.INFO, "Already disconnected, ignoring request.");
205             }
206         }
207 
208         @Override
dump(IndentingPrintWriter pw)209         public void dump(IndentingPrintWriter pw) {
210             pw.append("BindingConnection [");
211             pw.append(mIsConnected ? "" : "not ").append("connected, ");
212             pw.append(mIsBound ? "" : "not ").append("bound]\n");
213         }
214 
onConnected(IBinder service)215         protected void onConnected(IBinder service) {
216             boolean shouldRemainConnected =
217                     InCallController.this.onConnected(mInCallServiceInfo, service);
218             if (!shouldRemainConnected) {
219                 // Sometimes we can opt to disconnect for certain reasons, like if the
220                 // InCallService rejected our intialization step, or the calls went away
221                 // in the time it took us to bind to the InCallService. In such cases, we go
222                 // ahead and disconnect ourselves.
223                 disconnect();
224             }
225         }
226 
onDisconnected()227         protected void onDisconnected() {
228             InCallController.this.onDisconnected(mInCallServiceInfo.getComponentName());
229             disconnect();  // Unbind explicitly if we get disconnected.
230             if (mListener != null) {
231                 mListener.onDisconnect(InCallServiceBindingConnection.this);
232             }
233         }
234     }
235 
236     /**
237      * A version of the InCallServiceBindingConnection that proxies all calls to a secondary
238      * connection until it finds an emergency call, or the other connection dies. When one of those
239      * two things happen, this class instance will take over the connection.
240      */
241     private class EmergencyInCallServiceConnection extends InCallServiceBindingConnection {
242         private boolean mIsProxying = true;
243         private boolean mIsConnected = false;
244         private final InCallServiceConnection mSubConnection;
245 
246         private Listener mSubListener = new Listener() {
247             @Override
248             public void onDisconnect(InCallServiceConnection subConnection) {
249                 if (subConnection == mSubConnection) {
250                     if (mIsConnected && mIsProxying) {
251                         // At this point we know that we need to be connected to the InCallService
252                         // and we are proxying to the sub connection.  However, the sub-connection
253                         // just died so we need to stop proxying and connect to the system in-call
254                         // service instead.
255                         mIsProxying = false;
256                         connect(null);
257                     }
258                 }
259             }
260         };
261 
EmergencyInCallServiceConnection( InCallServiceInfo info, InCallServiceConnection subConnection)262         public EmergencyInCallServiceConnection(
263                 InCallServiceInfo info, InCallServiceConnection subConnection) {
264 
265             super(info);
266             mSubConnection = subConnection;
267             if (mSubConnection != null) {
268                 mSubConnection.setListener(mSubListener);
269             }
270             mIsProxying = (mSubConnection != null);
271         }
272 
273         @Override
connect(Call call)274         public boolean connect(Call call) {
275             mIsConnected = true;
276             if (mIsProxying) {
277                 if (mSubConnection.connect(call)) {
278                     return true;
279                 }
280                 // Could not connect to child, stop proxying.
281                 mIsProxying = false;
282             }
283 
284             // If we are here, we didn't or could not connect to child. So lets connect ourselves.
285             return super.connect(call);
286         }
287 
288         @Override
disconnect()289         public void disconnect() {
290             Log.i(this, "Disconnect forced!");
291             if (mIsProxying) {
292                 mSubConnection.disconnect();
293             } else {
294                 super.disconnect();
295             }
296             mIsConnected = false;
297         }
298 
299         @Override
setHasEmergency(boolean hasEmergency)300         public void setHasEmergency(boolean hasEmergency) {
301             if (hasEmergency) {
302                 takeControl();
303             }
304         }
305 
306         @Override
onDisconnected()307         protected void onDisconnected() {
308             // Save this here because super.onDisconnected() could force us to explicitly
309             // disconnect() as a cleanup step and that sets mIsConnected to false.
310             boolean shouldReconnect = mIsConnected;
311             super.onDisconnected();
312             // We just disconnected.  Check if we are expected to be connected, and reconnect.
313             if (shouldReconnect && !mIsProxying) {
314                 connect(null);  // reconnect
315             }
316         }
317 
318         @Override
dump(IndentingPrintWriter pw)319         public void dump(IndentingPrintWriter pw) {
320             pw.println("Emergency ICS Connection");
321             pw.increaseIndent();
322             pw.print("Emergency: ");
323             super.dump(pw);
324             if (mSubConnection != null) {
325                 pw.print("Default-Dialer: ");
326                 mSubConnection.dump(pw);
327             }
328             pw.decreaseIndent();
329         }
330 
331         /**
332          * Forces the connection to take control from it's subConnection.
333          */
takeControl()334         private void takeControl() {
335             if (mIsProxying) {
336                 mIsProxying = false;
337                 if (mIsConnected) {
338                     mSubConnection.disconnect();
339                     super.connect(null);
340                 }
341             }
342         }
343     }
344 
345     /**
346      * A version of InCallServiceConnection which switches UI between two separate sub-instances of
347      * InCallServicesConnections.
348      */
349     private class CarSwappingInCallServiceConnection extends InCallServiceConnection {
350         private final InCallServiceConnection mDialerConnection;
351         private final InCallServiceConnection mCarModeConnection;
352         private InCallServiceConnection mCurrentConnection;
353         private boolean mIsCarMode = false;
354         private boolean mIsConnected = false;
355 
CarSwappingInCallServiceConnection( InCallServiceConnection dialerConnection, InCallServiceConnection carModeConnection)356         public CarSwappingInCallServiceConnection(
357                 InCallServiceConnection dialerConnection,
358                 InCallServiceConnection carModeConnection) {
359             mDialerConnection = dialerConnection;
360             mCarModeConnection = carModeConnection;
361             mCurrentConnection = getCurrentConnection();
362         }
363 
setCarMode(boolean isCarMode)364         public synchronized void setCarMode(boolean isCarMode) {
365             Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode);
366             if (isCarMode != mIsCarMode) {
367                 mIsCarMode = isCarMode;
368                 InCallServiceConnection newConnection = getCurrentConnection();
369                 if (newConnection != mCurrentConnection) {
370                     if (mIsConnected) {
371                         mCurrentConnection.disconnect();
372                         newConnection.connect(null);
373                     }
374                     mCurrentConnection = newConnection;
375                 }
376             }
377         }
378 
379         @Override
connect(Call call)380         public boolean connect(Call call) {
381             if (mIsConnected) {
382                 Log.i(this, "already connected");
383                 return true;
384             } else {
385                 if (mCurrentConnection.connect(call)) {
386                     mIsConnected = true;
387                     return true;
388                 }
389             }
390 
391             return false;
392         }
393 
394         @Override
disconnect()395         public void disconnect() {
396             if (mIsConnected) {
397                 mCurrentConnection.disconnect();
398                 mIsConnected = false;
399             } else {
400                 Log.i(this, "already disconnected");
401             }
402         }
403 
404         @Override
setHasEmergency(boolean hasEmergency)405         public void setHasEmergency(boolean hasEmergency) {
406             if (mDialerConnection != null) {
407                 mDialerConnection.setHasEmergency(hasEmergency);
408             }
409             if (mCarModeConnection != null) {
410                 mCarModeConnection.setHasEmergency(hasEmergency);
411             }
412         }
413 
414         @Override
dump(IndentingPrintWriter pw)415         public void dump(IndentingPrintWriter pw) {
416             pw.println("Car Swapping ICS");
417             pw.increaseIndent();
418             if (mDialerConnection != null) {
419                 pw.print("Dialer: ");
420                 mDialerConnection.dump(pw);
421             }
422             if (mCarModeConnection != null) {
423                 pw.print("Car Mode: ");
424                 mCarModeConnection.dump(pw);
425             }
426         }
427 
getCurrentConnection()428         private InCallServiceConnection getCurrentConnection() {
429             if (mIsCarMode && mCarModeConnection != null) {
430                 return mCarModeConnection;
431             } else {
432                 return mDialerConnection;
433             }
434         }
435     }
436 
437     private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection {
438         private final List<InCallServiceBindingConnection> mSubConnections;
439 
NonUIInCallServiceConnectionCollection( List<InCallServiceBindingConnection> subConnections)440         public NonUIInCallServiceConnectionCollection(
441                 List<InCallServiceBindingConnection> subConnections) {
442             mSubConnections = subConnections;
443         }
444 
445         @Override
connect(Call call)446         public boolean connect(Call call) {
447             for (InCallServiceBindingConnection subConnection : mSubConnections) {
448                 subConnection.connect(call);
449             }
450             return true;
451         }
452 
453         @Override
disconnect()454         public void disconnect() {
455             for (InCallServiceBindingConnection subConnection : mSubConnections) {
456                 subConnection.disconnect();
457             }
458         }
459 
460         @Override
dump(IndentingPrintWriter pw)461         public void dump(IndentingPrintWriter pw) {
462             pw.println("Non-UI Connections:");
463             pw.increaseIndent();
464             for (InCallServiceBindingConnection subConnection : mSubConnections) {
465                 subConnection.dump(pw);
466             }
467             pw.decreaseIndent();
468         }
469     }
470 
471     private final Call.Listener mCallListener = new Call.ListenerBase() {
472         @Override
473         public void onConnectionCapabilitiesChanged(Call call) {
474             updateCall(call);
475         }
476 
477         @Override
478         public void onConnectionPropertiesChanged(Call call) {
479             updateCall(call);
480         }
481 
482         @Override
483         public void onCannedSmsResponsesLoaded(Call call) {
484             updateCall(call);
485         }
486 
487         @Override
488         public void onVideoCallProviderChanged(Call call) {
489             updateCall(call, true /* videoProviderChanged */);
490         }
491 
492         @Override
493         public void onStatusHintsChanged(Call call) {
494             updateCall(call);
495         }
496 
497         /**
498          * Listens for changes to extras reported by a Telecom {@link Call}.
499          *
500          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
501          * so we will only trigger an update of the call information if the source of the extras
502          * change was a {@link ConnectionService}.
503          *
504          * @param call The call.
505          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
506          *               {@link Call#SOURCE_INCALL_SERVICE}).
507          * @param extras The extras.
508          */
509         @Override
510         public void onExtrasChanged(Call call, int source, Bundle extras) {
511             // Do not inform InCallServices of changes which originated there.
512             if (source == Call.SOURCE_INCALL_SERVICE) {
513                 return;
514             }
515             updateCall(call);
516         }
517 
518         /**
519          * Listens for changes to extras reported by a Telecom {@link Call}.
520          *
521          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
522          * so we will only trigger an update of the call information if the source of the extras
523          * change was a {@link ConnectionService}.
524          *  @param call The call.
525          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
526          *               {@link Call#SOURCE_INCALL_SERVICE}).
527          * @param keys The extra key removed
528          */
529         @Override
530         public void onExtrasRemoved(Call call, int source, List<String> keys) {
531             // Do not inform InCallServices of changes which originated there.
532             if (source == Call.SOURCE_INCALL_SERVICE) {
533                 return;
534             }
535             updateCall(call);
536         }
537 
538         @Override
539         public void onHandleChanged(Call call) {
540             updateCall(call);
541         }
542 
543         @Override
544         public void onCallerDisplayNameChanged(Call call) {
545             updateCall(call);
546         }
547 
548         @Override
549         public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
550             updateCall(call);
551         }
552 
553         @Override
554         public void onTargetPhoneAccountChanged(Call call) {
555             updateCall(call);
556         }
557 
558         @Override
559         public void onConferenceableCallsChanged(Call call) {
560             updateCall(call);
561         }
562 
563         @Override
564         public void onConnectionEvent(Call call, String event, Bundle extras) {
565             notifyConnectionEvent(call, event, extras);
566         }
567     };
568 
569     private final SystemStateListener mSystemStateListener = new SystemStateListener() {
570         @Override
571         public void onCarModeChanged(boolean isCarMode) {
572             if (mInCallServiceConnection != null) {
573                 mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
574             }
575         }
576     };
577 
578     private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
579     private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
580     private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
581     private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
582     private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
583 
584     /** The in-call app implementations, see {@link IInCallService}. */
585     private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
586 
587     /**
588      * The {@link ComponentName} of the bound In-Call UI Service.
589      */
590     private ComponentName mInCallUIComponentName;
591 
592     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
593 
594     /** The {@link ComponentName} of the default InCall UI. */
595     private final ComponentName mSystemInCallComponentName;
596 
597     private final Context mContext;
598     private final TelecomSystem.SyncRoot mLock;
599     private final CallsManager mCallsManager;
600     private final SystemStateProvider mSystemStateProvider;
601     private final DefaultDialerManagerAdapter mDefaultDialerAdapter;
602     private CarSwappingInCallServiceConnection mInCallServiceConnection;
603     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
604 
InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateProvider systemStateProvider, DefaultDialerManagerAdapter defaultDialerAdapter)605     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
606             SystemStateProvider systemStateProvider,
607             DefaultDialerManagerAdapter defaultDialerAdapter) {
608         mContext = context;
609         mLock = lock;
610         mCallsManager = callsManager;
611         mSystemStateProvider = systemStateProvider;
612         mDefaultDialerAdapter = defaultDialerAdapter;
613 
614         Resources resources = mContext.getResources();
615         mSystemInCallComponentName = new ComponentName(
616                 resources.getString(R.string.ui_default_package),
617                 resources.getString(R.string.incall_default_class));
618 
619         mSystemStateProvider.addListener(mSystemStateListener);
620     }
621 
622     @Override
onCallAdded(Call call)623     public void onCallAdded(Call call) {
624         if (!isBoundToServices()) {
625             bindToServices(call);
626         } else {
627             adjustServiceBindingsForEmergency();
628 
629             Log.i(this, "onCallAdded: %s", call);
630             // Track the call if we don't already know about it.
631             addCall(call);
632 
633             List<ComponentName> componentsUpdated = new ArrayList<>();
634             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
635                 InCallServiceInfo info = entry.getKey();
636 
637                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
638                     continue;
639                 }
640 
641                 componentsUpdated.add(info.getComponentName());
642                 IInCallService inCallService = entry.getValue();
643 
644                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
645                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
646                         info.isExternalCallsSupported());
647                 try {
648                     inCallService.addCall(parcelableCall);
649                 } catch (RemoteException ignored) {
650                 }
651             }
652             Log.i(this, "Call added to components: %s", componentsUpdated);
653         }
654     }
655 
656     @Override
onCallRemoved(Call call)657     public void onCallRemoved(Call call) {
658         Log.i(this, "onCallRemoved: %s", call);
659         if (mCallsManager.getCalls().isEmpty()) {
660             /** Let's add a 2 second delay before we send unbind to the services to hopefully
661              *  give them enough time to process all the pending messages.
662              */
663             Handler handler = new Handler(Looper.getMainLooper());
664             handler.postDelayed(new Runnable("ICC.oCR", mLock) {
665                 @Override
666                 public void loggedRun() {
667                     // Check again to make sure there are no active calls.
668                     if (mCallsManager.getCalls().isEmpty()) {
669                         unbindFromServices();
670                     }
671                 }
672             }.prepare(), Timeouts.getCallRemoveUnbindInCallServicesDelay(
673                             mContext.getContentResolver()));
674         }
675         call.removeListener(mCallListener);
676         mCallIdMapper.removeCall(call);
677     }
678 
679     @Override
onExternalCallChanged(Call call, boolean isExternalCall)680     public void onExternalCallChanged(Call call, boolean isExternalCall) {
681         Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
682 
683         List<ComponentName> componentsUpdated = new ArrayList<>();
684         if (!isExternalCall) {
685             // The call was external but it is no longer external.  We must now add it to any
686             // InCallServices which do not support external calls.
687             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
688                 InCallServiceInfo info = entry.getKey();
689 
690                 if (info.isExternalCallsSupported()) {
691                     // For InCallServices which support external calls, the call will have already
692                     // been added to the connection service, so we do not need to add it again.
693                     continue;
694                 }
695 
696                 componentsUpdated.add(info.getComponentName());
697                 IInCallService inCallService = entry.getValue();
698 
699                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
700                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
701                         info.isExternalCallsSupported());
702                 try {
703                     inCallService.addCall(parcelableCall);
704                 } catch (RemoteException ignored) {
705                 }
706             }
707             Log.i(this, "Previously external call added to components: %s", componentsUpdated);
708         } else {
709             // The call was regular but it is now external.  We must now remove it from any
710             // InCallServices which do not support external calls.
711             // Remove the call by sending a call update indicating the call was disconnected.
712             ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
713                     call,
714                     false /* includeVideoProvider */,
715                     mCallsManager.getPhoneAccountRegistrar(),
716                     false /* supportsExternalCalls */,
717                     android.telecom.Call.STATE_DISCONNECTED /* overrideState */);
718 
719             Log.i(this, "Removing external call %s ==> %s", call, parcelableCall);
720             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
721                 InCallServiceInfo info = entry.getKey();
722                 if (info.isExternalCallsSupported()) {
723                     // For InCallServices which support external calls, we do not need to remove
724                     // the call.
725                     continue;
726                 }
727 
728                 componentsUpdated.add(info.getComponentName());
729                 IInCallService inCallService = entry.getValue();
730 
731                 try {
732                     inCallService.updateCall(parcelableCall);
733                 } catch (RemoteException ignored) {
734                 }
735             }
736             Log.i(this, "External call removed from components: %s", componentsUpdated);
737         }
738     }
739 
740     @Override
onCallStateChanged(Call call, int oldState, int newState)741     public void onCallStateChanged(Call call, int oldState, int newState) {
742         updateCall(call);
743     }
744 
745     @Override
onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)746     public void onConnectionServiceChanged(
747             Call call,
748             ConnectionServiceWrapper oldService,
749             ConnectionServiceWrapper newService) {
750         updateCall(call);
751     }
752 
753     @Override
onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)754     public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
755             CallAudioState newCallAudioState) {
756         if (!mInCallServices.isEmpty()) {
757             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
758                     newCallAudioState);
759             for (IInCallService inCallService : mInCallServices.values()) {
760                 try {
761                     inCallService.onCallAudioStateChanged(newCallAudioState);
762                 } catch (RemoteException ignored) {
763                 }
764             }
765         }
766     }
767 
768     @Override
onCanAddCallChanged(boolean canAddCall)769     public void onCanAddCallChanged(boolean canAddCall) {
770         if (!mInCallServices.isEmpty()) {
771             Log.i(this, "onCanAddCallChanged : %b", canAddCall);
772             for (IInCallService inCallService : mInCallServices.values()) {
773                 try {
774                     inCallService.onCanAddCallChanged(canAddCall);
775                 } catch (RemoteException ignored) {
776                 }
777             }
778         }
779     }
780 
onPostDialWait(Call call, String remaining)781     void onPostDialWait(Call call, String remaining) {
782         if (!mInCallServices.isEmpty()) {
783             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
784             for (IInCallService inCallService : mInCallServices.values()) {
785                 try {
786                     inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
787                 } catch (RemoteException ignored) {
788                 }
789             }
790         }
791     }
792 
793     @Override
onIsConferencedChanged(Call call)794     public void onIsConferencedChanged(Call call) {
795         Log.d(this, "onIsConferencedChanged %s", call);
796         updateCall(call);
797     }
798 
bringToForeground(boolean showDialpad)799     void bringToForeground(boolean showDialpad) {
800         if (!mInCallServices.isEmpty()) {
801             for (IInCallService inCallService : mInCallServices.values()) {
802                 try {
803                     inCallService.bringToForeground(showDialpad);
804                 } catch (RemoteException ignored) {
805                 }
806             }
807         } else {
808             Log.w(this, "Asking to bring unbound in-call UI to foreground.");
809         }
810     }
811 
silenceRinger()812     void silenceRinger() {
813         if (!mInCallServices.isEmpty()) {
814             for (IInCallService inCallService : mInCallServices.values()) {
815                 try {
816                     inCallService.silenceRinger();
817                 } catch (RemoteException ignored) {
818                 }
819             }
820         }
821     }
822 
notifyConnectionEvent(Call call, String event, Bundle extras)823     private void notifyConnectionEvent(Call call, String event, Bundle extras) {
824         if (!mInCallServices.isEmpty()) {
825             for (IInCallService inCallService : mInCallServices.values()) {
826                 try {
827                     Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
828                             (call != null ? call.toString() :"null"),
829                             (event != null ? event : "null") ,
830                             (extras != null ? extras.toString() : "null"));
831                     inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras);
832                 } catch (RemoteException ignored) {
833                 }
834             }
835         }
836     }
837 
838     /**
839      * Unbinds an existing bound connection to the in-call app.
840      */
unbindFromServices()841     private void unbindFromServices() {
842         if (isBoundToServices()) {
843             mInCallServiceConnection.disconnect();
844             mInCallServiceConnection = null;
845             mNonUIInCallServiceConnections.disconnect();
846             mNonUIInCallServiceConnections = null;
847         }
848     }
849 
850     /**
851      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
852      * InCallServices. Method-invoker must check {@link #isBoundToServices()} before invoking.
853      *
854      * @param call The newly added call that triggered the binding to the in-call services.
855      */
856     @VisibleForTesting
bindToServices(Call call)857     public void bindToServices(Call call) {
858         InCallServiceConnection dialerInCall = null;
859         InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
860         Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
861         if (defaultDialerComponentInfo != null &&
862                 !defaultDialerComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
863             dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
864         }
865         Log.i(this, "defaultDialer: " + dialerInCall);
866 
867         InCallServiceInfo systemInCallInfo =  getInCallServiceComponent(mSystemInCallComponentName,
868                 IN_CALL_SERVICE_TYPE_SYSTEM_UI);
869         EmergencyInCallServiceConnection systemInCall =
870                 new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
871         systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
872 
873         InCallServiceConnection carModeInCall = null;
874         InCallServiceInfo carModeComponentInfo = getCarModeComponent();
875         if (carModeComponentInfo != null &&
876                 !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
877             carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
878         }
879 
880         mInCallServiceConnection =
881             new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
882         mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
883         mInCallServiceConnection.connect(call);
884 
885         List<InCallServiceInfo> nonUIInCallComponents =
886                 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
887         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
888         for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
889             nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
890         }
891         mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
892         mNonUIInCallServiceConnections.connect(call);
893     }
894 
getDefaultDialerComponent()895     private InCallServiceInfo getDefaultDialerComponent() {
896         String packageName = mDefaultDialerAdapter.getDefaultDialerApplication(
897                 mContext, mCallsManager.getCurrentUserHandle().getIdentifier());
898         Log.d(this, "Default Dialer package: " + packageName);
899 
900         return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
901     }
902 
getCarModeComponent()903     private InCallServiceInfo getCarModeComponent() {
904         // Seems strange to cast a String to null, but the signatures of getInCallServiceComponent
905         // differ in the types of the first parameter, and passing in null is inherently ambiguous.
906         return getInCallServiceComponent((String) null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
907     }
908 
getInCallServiceComponent(ComponentName componentName, int type)909     private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
910         List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type);
911         if (list != null && !list.isEmpty()) {
912             return list.get(0);
913         } else {
914             // Last Resort: Try to bind to the ComponentName given directly.
915             Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
916                     + componentName +". Trying to bind anyway.");
917             return new InCallServiceInfo(componentName, false);
918         }
919     }
920 
getInCallServiceComponent(String packageName, int type)921     private InCallServiceInfo getInCallServiceComponent(String packageName, int type) {
922         List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type);
923         if (list != null && !list.isEmpty()) {
924             return list.get(0);
925         }
926         return null;
927     }
928 
getInCallServiceComponents(int type)929     private List<InCallServiceInfo> getInCallServiceComponents(int type) {
930         return getInCallServiceComponents(null, null, type);
931     }
932 
getInCallServiceComponents(String packageName, int type)933     private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) {
934         return getInCallServiceComponents(packageName, null, type);
935     }
936 
getInCallServiceComponents(ComponentName componentName, int type)937     private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
938             int type) {
939         return getInCallServiceComponents(null, componentName, type);
940     }
941 
getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType)942     private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
943             ComponentName componentName, int requestedType) {
944 
945         List<InCallServiceInfo> retval = new LinkedList<>();
946 
947         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
948         if (packageName != null) {
949             serviceIntent.setPackage(packageName);
950         }
951         if (componentName != null) {
952             serviceIntent.setComponent(componentName);
953         }
954 
955         PackageManager packageManager = mContext.getPackageManager();
956         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
957                 serviceIntent,
958                 PackageManager.GET_META_DATA,
959                 mCallsManager.getCurrentUserHandle().getIdentifier())) {
960             ServiceInfo serviceInfo = entry.serviceInfo;
961 
962             if (serviceInfo != null) {
963                 boolean isExternalCallsSupported = serviceInfo.metaData != null &&
964                         serviceInfo.metaData.getBoolean(
965                                 TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false);
966                 if (requestedType == 0 || requestedType == getInCallServiceType(entry.serviceInfo,
967                         packageManager)) {
968 
969                     retval.add(new InCallServiceInfo(
970                             new ComponentName(serviceInfo.packageName, serviceInfo.name),
971                             isExternalCallsSupported));
972                 }
973             }
974         }
975 
976         return retval;
977     }
978 
shouldUseCarModeUI()979     private boolean shouldUseCarModeUI() {
980         return mSystemStateProvider.isCarMode();
981     }
982 
983     /**
984      * Returns the type of InCallService described by the specified serviceInfo.
985      */
getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager)986     private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) {
987         // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
988         // enforces that only Telecom can bind to it.
989         boolean hasServiceBindPermission = serviceInfo.permission != null &&
990                 serviceInfo.permission.equals(
991                         Manifest.permission.BIND_INCALL_SERVICE);
992         if (!hasServiceBindPermission) {
993             Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " +
994                     serviceInfo.packageName);
995             return IN_CALL_SERVICE_TYPE_INVALID;
996         }
997 
998         if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) &&
999                 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) {
1000             return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
1001         }
1002 
1003         // Check to see if the service is a car-mode UI type by checking that it has the
1004         // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
1005         // car-mode UI metadata.
1006         boolean hasControlInCallPermission = packageManager.checkPermission(
1007                 Manifest.permission.CONTROL_INCALL_EXPERIENCE,
1008                 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
1009         boolean isCarModeUIService = serviceInfo.metaData != null &&
1010                 serviceInfo.metaData.getBoolean(
1011                         TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) &&
1012                 hasControlInCallPermission;
1013         if (isCarModeUIService) {
1014             return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
1015         }
1016 
1017         // Check to see that it is the default dialer package
1018         boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
1019                 mDefaultDialerAdapter.getDefaultDialerApplication(
1020                     mContext, mCallsManager.getCurrentUserHandle().getIdentifier()));
1021         boolean isUIService = serviceInfo.metaData != null &&
1022                 serviceInfo.metaData.getBoolean(
1023                         TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
1024         if (isDefaultDialerPackage && isUIService) {
1025             return IN_CALL_SERVICE_TYPE_DIALER_UI;
1026         }
1027 
1028         // Also allow any in-call service that has the control-experience permission (to ensure
1029         // that it is a system app) and doesn't claim to show any UI.
1030         if (hasControlInCallPermission && !isUIService) {
1031             return IN_CALL_SERVICE_TYPE_NON_UI;
1032         }
1033 
1034         // Anything else that remains, we will not bind to.
1035         Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b",
1036                 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission,
1037                 isCarModeUIService, isUIService);
1038         return IN_CALL_SERVICE_TYPE_INVALID;
1039     }
1040 
adjustServiceBindingsForEmergency()1041     private void adjustServiceBindingsForEmergency() {
1042         // The connected UI is not the system UI, so lets check if we should switch them
1043         // if there exists an emergency number.
1044         if (mCallsManager.hasEmergencyCall()) {
1045             mInCallServiceConnection.setHasEmergency(true);
1046         }
1047     }
1048 
1049     /**
1050      * Persists the {@link IInCallService} instance and starts the communication between
1051      * this class and in-call app by sending the first update to in-call app. This method is
1052      * called after a successful binding connection is established.
1053      *
1054      * @param info Info about the service, including its {@link ComponentName}.
1055      * @param service The {@link IInCallService} implementation.
1056      * @return True if we successfully connected.
1057      */
onConnected(InCallServiceInfo info, IBinder service)1058     private boolean onConnected(InCallServiceInfo info, IBinder service) {
1059         Trace.beginSection("onConnected: " + info.getComponentName());
1060         Log.i(this, "onConnected to %s", info.getComponentName());
1061 
1062         IInCallService inCallService = IInCallService.Stub.asInterface(service);
1063         mInCallServices.put(info, inCallService);
1064 
1065         try {
1066             inCallService.setInCallAdapter(
1067                     new InCallAdapter(
1068                             mCallsManager,
1069                             mCallIdMapper,
1070                             mLock,
1071                             info.getComponentName().getPackageName()));
1072         } catch (RemoteException e) {
1073             Log.e(this, e, "Failed to set the in-call adapter.");
1074             Trace.endSection();
1075             return false;
1076         }
1077 
1078         // Upon successful connection, send the state of the world to the service.
1079         List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
1080         if (!calls.isEmpty()) {
1081             Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(),
1082                     info.getComponentName());
1083             for (Call call : calls) {
1084                 try {
1085                     if (call.isExternalCall() && !info.isExternalCallsSupported()) {
1086                         continue;
1087                     }
1088 
1089                     // Track the call if we don't already know about it.
1090                     addCall(call);
1091 
1092                     inCallService.addCall(ParcelableCallUtils.toParcelableCall(
1093                             call,
1094                             true /* includeVideoProvider */,
1095                             mCallsManager.getPhoneAccountRegistrar(),
1096                             info.isExternalCallsSupported()));
1097                 } catch (RemoteException ignored) {
1098                 }
1099             }
1100             try {
1101                 inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
1102                 inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
1103             } catch (RemoteException ignored) {
1104             }
1105         } else {
1106             return false;
1107         }
1108         Trace.endSection();
1109         return true;
1110     }
1111 
1112     /**
1113      * Cleans up an instance of in-call app after the service has been unbound.
1114      *
1115      * @param disconnectedComponent The {@link ComponentName} of the service which disconnected.
1116      */
onDisconnected(ComponentName disconnectedComponent)1117     private void onDisconnected(ComponentName disconnectedComponent) {
1118         Log.i(this, "onDisconnected from %s", disconnectedComponent);
1119 
1120         mInCallServices.remove(disconnectedComponent);
1121     }
1122 
1123     /**
1124      * Informs all {@link InCallService} instances of the updated call information.
1125      *
1126      * @param call The {@link Call}.
1127      */
updateCall(Call call)1128     private void updateCall(Call call) {
1129         updateCall(call, false /* videoProviderChanged */);
1130     }
1131 
1132     /**
1133      * Informs all {@link InCallService} instances of the updated call information.
1134      *
1135      * @param call The {@link Call}.
1136      * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
1137      *      otherwise.
1138      */
updateCall(Call call, boolean videoProviderChanged)1139     private void updateCall(Call call, boolean videoProviderChanged) {
1140         if (!mInCallServices.isEmpty()) {
1141             Log.i(this, "Sending updateCall %s", call);
1142             List<ComponentName> componentsUpdated = new ArrayList<>();
1143             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
1144                 InCallServiceInfo info = entry.getKey();
1145                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
1146                     continue;
1147                 }
1148 
1149                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
1150                         call,
1151                         videoProviderChanged /* includeVideoProvider */,
1152                         mCallsManager.getPhoneAccountRegistrar(),
1153                         info.isExternalCallsSupported());
1154                 ComponentName componentName = info.getComponentName();
1155                 IInCallService inCallService = entry.getValue();
1156                 componentsUpdated.add(componentName);
1157 
1158                 try {
1159                     inCallService.updateCall(parcelableCall);
1160                 } catch (RemoteException ignored) {
1161                 }
1162             }
1163             Log.i(this, "Components updated: %s", componentsUpdated);
1164         }
1165     }
1166 
1167     /**
1168      * Adds the call to the list of calls tracked by the {@link InCallController}.
1169      * @param call The call to add.
1170      */
addCall(Call call)1171     private void addCall(Call call) {
1172         if (mCallIdMapper.getCallId(call) == null) {
1173             mCallIdMapper.addCall(call);
1174             call.addListener(mCallListener);
1175         }
1176     }
1177 
isBoundToServices()1178     private boolean isBoundToServices() {
1179         return mInCallServiceConnection != null;
1180     }
1181 
1182     /**
1183      * Dumps the state of the {@link InCallController}.
1184      *
1185      * @param pw The {@code IndentingPrintWriter} to write the state to.
1186      */
dump(IndentingPrintWriter pw)1187     public void dump(IndentingPrintWriter pw) {
1188         pw.println("mInCallServices (InCalls registered):");
1189         pw.increaseIndent();
1190         for (InCallServiceInfo info : mInCallServices.keySet()) {
1191             pw.println(info);
1192         }
1193         pw.decreaseIndent();
1194 
1195         pw.println("ServiceConnections (InCalls bound):");
1196         pw.increaseIndent();
1197         if (mInCallServiceConnection != null) {
1198             mInCallServiceConnection.dump(pw);
1199         }
1200         pw.decreaseIndent();
1201     }
1202 
doesConnectedDialerSupportRinging()1203     public boolean doesConnectedDialerSupportRinging() {
1204         String ringingPackage =  null;
1205         if (mInCallUIComponentName != null) {
1206             ringingPackage = mInCallUIComponentName.getPackageName().trim();
1207         }
1208 
1209         if (TextUtils.isEmpty(ringingPackage)) {
1210             // The current in-call UI returned nothing, so lets use the default dialer.
1211             ringingPackage = DefaultDialerManager.getDefaultDialerApplication(
1212                     mContext, UserHandle.USER_CURRENT);
1213         }
1214         if (TextUtils.isEmpty(ringingPackage)) {
1215             return false;
1216         }
1217 
1218         Intent intent = new Intent(InCallService.SERVICE_INTERFACE)
1219             .setPackage(ringingPackage);
1220         List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
1221                 intent, PackageManager.GET_META_DATA,
1222                 mCallsManager.getCurrentUserHandle().getIdentifier());
1223         if (entries.isEmpty()) {
1224             return false;
1225         }
1226 
1227         ResolveInfo info = entries.get(0);
1228         if (info.serviceInfo == null || info.serviceInfo.metaData == null) {
1229             return false;
1230         }
1231 
1232         return info.serviceInfo.metaData
1233                 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false);
1234     }
1235 
orderCallsWithChildrenFirst(Collection<Call> calls)1236     private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) {
1237         LinkedList<Call> parentCalls = new LinkedList<>();
1238         LinkedList<Call> childCalls = new LinkedList<>();
1239         for (Call call : calls) {
1240             if (call.getChildCalls().size() > 0) {
1241                 parentCalls.add(call);
1242             } else {
1243                 childCalls.add(call);
1244             }
1245         }
1246         childCalls.addAll(parentCalls);
1247         return childCalls;
1248     }
1249 }
1250