• 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.InCallService;
38 import android.telecom.Log;
39 import android.telecom.Logging.Runnable;
40 import android.telecom.ParcelableCall;
41 import android.telecom.TelecomManager;
42 import android.text.TextUtils;
43 import android.util.ArrayMap;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 // TODO: Needed for move to system service: import com.android.internal.R;
47 import com.android.internal.telecom.IInCallService;
48 import com.android.internal.util.IndentingPrintWriter;
49 import com.android.server.telecom.SystemStateHelper.SystemStateListener;
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 import java.util.concurrent.CompletableFuture;
58 import java.util.concurrent.TimeUnit;
59 
60 /**
61  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
62  * can send updates to the in-call app. This class is created and owned by CallsManager and retains
63  * a binding to the {@link IInCallService} (implemented by the in-call app).
64  */
65 public class InCallController extends CallsManagerListenerBase {
66 
67     public class InCallServiceConnection {
68         /**
69          * Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
70          * connection to an InCallService.
71          */
72         public static final int CONNECTION_SUCCEEDED = 1;
73         /**
74          * Indicates that a call to {@link #connect(Call)} has failed because of a binding issue.
75          */
76         public static final int CONNECTION_FAILED = 2;
77         /**
78          * Indicates that a call to {@link #connect(Call)} has been skipped because the
79          * IncallService does not support the type of call..
80          */
81         public static final int CONNECTION_NOT_SUPPORTED = 3;
82 
83         public class Listener {
onDisconnect(InCallServiceConnection conn)84             public void onDisconnect(InCallServiceConnection conn) {}
85         }
86 
87         protected Listener mListener;
88 
connect(Call call)89         public int connect(Call call) { return CONNECTION_FAILED; }
disconnect()90         public void disconnect() {}
isConnected()91         public boolean isConnected() { return false; }
setHasEmergency(boolean hasEmergency)92         public void setHasEmergency(boolean hasEmergency) {}
setListener(Listener l)93         public void setListener(Listener l) {
94             mListener = l;
95         }
getInfo()96         public InCallServiceInfo getInfo() { return null; }
dump(IndentingPrintWriter pw)97         public void dump(IndentingPrintWriter pw) {}
98     }
99 
100     private class InCallServiceInfo {
101         private final ComponentName mComponentName;
102         private boolean mIsExternalCallsSupported;
103         private boolean mIsSelfManagedCallsSupported;
104         private final int mType;
105 
InCallServiceInfo(ComponentName componentName, boolean isExternalCallsSupported, boolean isSelfManageCallsSupported, int type)106         public InCallServiceInfo(ComponentName componentName,
107                 boolean isExternalCallsSupported,
108                 boolean isSelfManageCallsSupported,
109                 int type) {
110             mComponentName = componentName;
111             mIsExternalCallsSupported = isExternalCallsSupported;
112             mIsSelfManagedCallsSupported = isSelfManageCallsSupported;
113             mType = type;
114         }
115 
getComponentName()116         public ComponentName getComponentName() {
117             return mComponentName;
118         }
119 
isExternalCallsSupported()120         public boolean isExternalCallsSupported() {
121             return mIsExternalCallsSupported;
122         }
123 
isSelfManagedCallsSupported()124         public boolean isSelfManagedCallsSupported() {
125             return mIsSelfManagedCallsSupported;
126         }
127 
getType()128         public int getType() {
129             return mType;
130         }
131 
132         @Override
equals(Object o)133         public boolean equals(Object o) {
134             if (this == o) {
135                 return true;
136             }
137             if (o == null || getClass() != o.getClass()) {
138                 return false;
139             }
140 
141             InCallServiceInfo that = (InCallServiceInfo) o;
142 
143             if (mIsExternalCallsSupported != that.mIsExternalCallsSupported) {
144                 return false;
145             }
146             if (mIsSelfManagedCallsSupported != that.mIsSelfManagedCallsSupported) {
147                 return false;
148             }
149             return mComponentName.equals(that.mComponentName);
150 
151         }
152 
153         @Override
hashCode()154         public int hashCode() {
155             return Objects.hash(mComponentName, mIsExternalCallsSupported,
156                     mIsSelfManagedCallsSupported);
157         }
158 
159         @Override
toString()160         public String toString() {
161             return "[" + mComponentName + " supportsExternal? " + mIsExternalCallsSupported +
162                     " supportsSelfMg?" + mIsSelfManagedCallsSupported + "]";
163         }
164     }
165 
166     private class InCallServiceBindingConnection extends InCallServiceConnection {
167 
168         private final ServiceConnection mServiceConnection = new ServiceConnection() {
169             @Override
170             public void onServiceConnected(ComponentName name, IBinder service) {
171                 Log.startSession("ICSBC.oSC");
172                 synchronized (mLock) {
173                     try {
174                         Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);
175                         mIsBound = true;
176                         if (mIsConnected) {
177                             // Only proceed if we are supposed to be connected.
178                             onConnected(service);
179                         }
180                     } finally {
181                         Log.endSession();
182                     }
183                 }
184             }
185 
186             @Override
187             public void onServiceDisconnected(ComponentName name) {
188                 Log.startSession("ICSBC.oSD");
189                 synchronized (mLock) {
190                     try {
191                         Log.d(this, "onDisconnected: %s", name);
192                         mIsBound = false;
193                         onDisconnected();
194                     } finally {
195                         Log.endSession();
196                     }
197                 }
198             }
199         };
200 
201         private final InCallServiceInfo mInCallServiceInfo;
202         private boolean mIsConnected = false;
203         private boolean mIsBound = false;
204 
InCallServiceBindingConnection(InCallServiceInfo info)205         public InCallServiceBindingConnection(InCallServiceInfo info) {
206             mInCallServiceInfo = info;
207         }
208 
209         @Override
connect(Call call)210         public int connect(Call call) {
211             if (mIsConnected) {
212                 Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request.");
213                 return CONNECTION_SUCCEEDED;
214             }
215 
216             if (call != null && call.isSelfManaged() &&
217                     !mInCallServiceInfo.isSelfManagedCallsSupported()) {
218                 Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls",
219                         mInCallServiceInfo);
220                 mIsConnected = false;
221                 return CONNECTION_NOT_SUPPORTED;
222             }
223 
224             Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
225             intent.setComponent(mInCallServiceInfo.getComponentName());
226             if (call != null && !call.isIncoming() && !call.isExternalCall()){
227                 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
228                         call.getIntentExtras());
229                 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
230                         call.getTargetPhoneAccount());
231             }
232 
233             Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
234             mIsConnected = true;
235             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
236                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
237                         | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
238                         UserHandle.CURRENT)) {
239                 Log.w(this, "Failed to connect.");
240                 mIsConnected = false;
241             }
242 
243             if (call != null && mIsConnected) {
244                 call.getAnalytics().addInCallService(
245                         mInCallServiceInfo.getComponentName().flattenToShortString(),
246                         mInCallServiceInfo.getType());
247             }
248 
249             return mIsConnected ? CONNECTION_SUCCEEDED : CONNECTION_FAILED;
250         }
251 
252         @Override
getInfo()253         public InCallServiceInfo getInfo() {
254             return mInCallServiceInfo;
255         }
256 
257         @Override
disconnect()258         public void disconnect() {
259             if (mIsConnected) {
260                 mContext.unbindService(mServiceConnection);
261                 mIsConnected = false;
262             } else {
263                 Log.addEvent(null, LogUtils.Events.INFO, "Already disconnected, ignoring request.");
264             }
265         }
266 
267         @Override
isConnected()268         public boolean isConnected() {
269             return mIsConnected;
270         }
271 
272         @Override
dump(IndentingPrintWriter pw)273         public void dump(IndentingPrintWriter pw) {
274             pw.append("BindingConnection [");
275             pw.append(mIsConnected ? "" : "not ").append("connected, ");
276             pw.append(mIsBound ? "" : "not ").append("bound]\n");
277         }
278 
onConnected(IBinder service)279         protected void onConnected(IBinder service) {
280             boolean shouldRemainConnected =
281                     InCallController.this.onConnected(mInCallServiceInfo, service);
282             if (!shouldRemainConnected) {
283                 // Sometimes we can opt to disconnect for certain reasons, like if the
284                 // InCallService rejected our initialization step, or the calls went away
285                 // in the time it took us to bind to the InCallService. In such cases, we go
286                 // ahead and disconnect ourselves.
287                 disconnect();
288             }
289         }
290 
onDisconnected()291         protected void onDisconnected() {
292             InCallController.this.onDisconnected(mInCallServiceInfo);
293             disconnect();  // Unbind explicitly if we get disconnected.
294             if (mListener != null) {
295                 mListener.onDisconnect(InCallServiceBindingConnection.this);
296             }
297         }
298     }
299 
300     /**
301      * A version of the InCallServiceBindingConnection that proxies all calls to a secondary
302      * connection until it finds an emergency call, or the other connection dies. When one of those
303      * two things happen, this class instance will take over the connection.
304      */
305     private class EmergencyInCallServiceConnection extends InCallServiceBindingConnection {
306         private boolean mIsProxying = true;
307         private boolean mIsConnected = false;
308         private final InCallServiceConnection mSubConnection;
309 
310         private Listener mSubListener = new Listener() {
311             @Override
312             public void onDisconnect(InCallServiceConnection subConnection) {
313                 if (subConnection == mSubConnection) {
314                     if (mIsConnected && mIsProxying) {
315                         // At this point we know that we need to be connected to the InCallService
316                         // and we are proxying to the sub connection.  However, the sub-connection
317                         // just died so we need to stop proxying and connect to the system in-call
318                         // service instead.
319                         mIsProxying = false;
320                         connect(null);
321                     }
322                 }
323             }
324         };
325 
EmergencyInCallServiceConnection( InCallServiceInfo info, InCallServiceConnection subConnection)326         public EmergencyInCallServiceConnection(
327                 InCallServiceInfo info, InCallServiceConnection subConnection) {
328 
329             super(info);
330             mSubConnection = subConnection;
331             if (mSubConnection != null) {
332                 mSubConnection.setListener(mSubListener);
333             }
334             mIsProxying = (mSubConnection != null);
335         }
336 
337         @Override
connect(Call call)338         public int connect(Call call) {
339             mIsConnected = true;
340             if (mIsProxying) {
341                 int result = mSubConnection.connect(call);
342                 mIsConnected = result == CONNECTION_SUCCEEDED;
343                 if (result != CONNECTION_FAILED) {
344                     return result;
345                 }
346                 // Could not connect to child, stop proxying.
347                 mIsProxying = false;
348             }
349 
350             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
351                 mCallsManager.getCurrentUserHandle());
352 
353             if (call != null && call.isIncoming()
354                 && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) {
355               // Add the last emergency call time to the call
356               Bundle extras = new Bundle();
357               extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
358                       mEmergencyCallHelper.getLastEmergencyCallTimeMillis());
359               call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
360             }
361 
362             // If we are here, we didn't or could not connect to child. So lets connect ourselves.
363             return super.connect(call);
364         }
365 
366         @Override
disconnect()367         public void disconnect() {
368             Log.i(this, "Disconnect forced!");
369             if (mIsProxying) {
370                 mSubConnection.disconnect();
371             } else {
372                 super.disconnect();
373                 mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
374             }
375             mIsConnected = false;
376         }
377 
378         @Override
setHasEmergency(boolean hasEmergency)379         public void setHasEmergency(boolean hasEmergency) {
380             if (hasEmergency) {
381                 takeControl();
382             }
383         }
384 
385         @Override
getInfo()386         public InCallServiceInfo getInfo() {
387             if (mIsProxying) {
388                 return mSubConnection.getInfo();
389             } else {
390                 return super.getInfo();
391             }
392         }
393         @Override
onDisconnected()394         protected void onDisconnected() {
395             // Save this here because super.onDisconnected() could force us to explicitly
396             // disconnect() as a cleanup step and that sets mIsConnected to false.
397             boolean shouldReconnect = mIsConnected;
398             super.onDisconnected();
399             // We just disconnected.  Check if we are expected to be connected, and reconnect.
400             if (shouldReconnect && !mIsProxying) {
401                 connect(null);  // reconnect
402             }
403         }
404 
405         @Override
dump(IndentingPrintWriter pw)406         public void dump(IndentingPrintWriter pw) {
407             pw.print("Emergency ICS Connection [");
408             pw.append(mIsProxying ? "" : "not ").append("proxying, ");
409             pw.append(mIsConnected ? "" : "not ").append("connected]\n");
410             pw.increaseIndent();
411             pw.print("Emergency: ");
412             super.dump(pw);
413             if (mSubConnection != null) {
414                 pw.print("Default-Dialer: ");
415                 mSubConnection.dump(pw);
416             }
417             pw.decreaseIndent();
418         }
419 
420         /**
421          * Forces the connection to take control from it's subConnection.
422          */
takeControl()423         private void takeControl() {
424             if (mIsProxying) {
425                 mIsProxying = false;
426                 if (mIsConnected) {
427                     mSubConnection.disconnect();
428                     super.connect(null);
429                 }
430             }
431         }
432     }
433 
434     /**
435      * A version of InCallServiceConnection which switches UI between two separate sub-instances of
436      * InCallServicesConnections.
437      */
438     private class CarSwappingInCallServiceConnection extends InCallServiceConnection {
439         private final InCallServiceConnection mDialerConnection;
440         private final InCallServiceConnection mCarModeConnection;
441         private InCallServiceConnection mCurrentConnection;
442         private boolean mIsCarMode = false;
443         private boolean mIsConnected = false;
444 
CarSwappingInCallServiceConnection( InCallServiceConnection dialerConnection, InCallServiceConnection carModeConnection)445         public CarSwappingInCallServiceConnection(
446                 InCallServiceConnection dialerConnection,
447                 InCallServiceConnection carModeConnection) {
448             mDialerConnection = dialerConnection;
449             mCarModeConnection = carModeConnection;
450             mCurrentConnection = getCurrentConnection();
451         }
452 
setCarMode(boolean isCarMode)453         public synchronized void setCarMode(boolean isCarMode) {
454             Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode);
455             if (isCarMode != mIsCarMode) {
456                 mIsCarMode = isCarMode;
457                 InCallServiceConnection newConnection = getCurrentConnection();
458                 if (newConnection != mCurrentConnection) {
459                     if (mIsConnected) {
460                         mCurrentConnection.disconnect();
461                         int result = newConnection.connect(null);
462                         mIsConnected = result == CONNECTION_SUCCEEDED;
463                     }
464                     mCurrentConnection = newConnection;
465                 }
466             }
467         }
468 
469         @Override
connect(Call call)470         public int connect(Call call) {
471             if (mIsConnected) {
472                 Log.i(this, "already connected");
473                 return CONNECTION_SUCCEEDED;
474             } else {
475                 int result = mCurrentConnection.connect(call);
476                 if (result != CONNECTION_FAILED) {
477                     mIsConnected = result == CONNECTION_SUCCEEDED;
478                     return result;
479                 }
480             }
481 
482             return CONNECTION_FAILED;
483         }
484 
485         @Override
disconnect()486         public void disconnect() {
487             if (mIsConnected) {
488                 mCurrentConnection.disconnect();
489                 mIsConnected = false;
490             } else {
491                 Log.i(this, "already disconnected");
492             }
493         }
494 
495         @Override
isConnected()496         public boolean isConnected() {
497             return mIsConnected;
498         }
499 
500         @Override
setHasEmergency(boolean hasEmergency)501         public void setHasEmergency(boolean hasEmergency) {
502             if (mDialerConnection != null) {
503                 mDialerConnection.setHasEmergency(hasEmergency);
504             }
505             if (mCarModeConnection != null) {
506                 mCarModeConnection.setHasEmergency(hasEmergency);
507             }
508         }
509 
510         @Override
getInfo()511         public InCallServiceInfo getInfo() {
512             return mCurrentConnection.getInfo();
513         }
514 
515         @Override
dump(IndentingPrintWriter pw)516         public void dump(IndentingPrintWriter pw) {
517             pw.print("Car Swapping ICS [");
518             pw.append(mIsConnected ? "" : "not ").append("connected]\n");
519             pw.increaseIndent();
520             if (mDialerConnection != null) {
521                 pw.print("Dialer: ");
522                 mDialerConnection.dump(pw);
523             }
524             if (mCarModeConnection != null) {
525                 pw.print("Car Mode: ");
526                 mCarModeConnection.dump(pw);
527             }
528         }
529 
getCurrentConnection()530         private InCallServiceConnection getCurrentConnection() {
531             if (mIsCarMode && mCarModeConnection != null) {
532                 return mCarModeConnection;
533             } else {
534                 return mDialerConnection;
535             }
536         }
537     }
538 
539     private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection {
540         private final List<InCallServiceBindingConnection> mSubConnections;
541 
NonUIInCallServiceConnectionCollection( List<InCallServiceBindingConnection> subConnections)542         public NonUIInCallServiceConnectionCollection(
543                 List<InCallServiceBindingConnection> subConnections) {
544             mSubConnections = subConnections;
545         }
546 
547         @Override
connect(Call call)548         public int connect(Call call) {
549             for (InCallServiceBindingConnection subConnection : mSubConnections) {
550                 subConnection.connect(call);
551             }
552             return CONNECTION_SUCCEEDED;
553         }
554 
555         @Override
disconnect()556         public void disconnect() {
557             for (InCallServiceBindingConnection subConnection : mSubConnections) {
558                 if (subConnection.isConnected()) {
559                     subConnection.disconnect();
560                 }
561             }
562         }
563 
564         @Override
isConnected()565         public boolean isConnected() {
566             boolean connected = false;
567             for (InCallServiceBindingConnection subConnection : mSubConnections) {
568                 connected = connected || subConnection.isConnected();
569             }
570             return connected;
571         }
572 
573         @Override
dump(IndentingPrintWriter pw)574         public void dump(IndentingPrintWriter pw) {
575             pw.println("Non-UI Connections:");
576             pw.increaseIndent();
577             for (InCallServiceBindingConnection subConnection : mSubConnections) {
578                 subConnection.dump(pw);
579             }
580             pw.decreaseIndent();
581         }
582     }
583 
584     private final Call.Listener mCallListener = new Call.ListenerBase() {
585         @Override
586         public void onConnectionCapabilitiesChanged(Call call) {
587             updateCall(call);
588         }
589 
590         @Override
591         public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
592             updateCall(call, false /* includeVideoProvider */, didRttChange);
593         }
594 
595         @Override
596         public void onCannedSmsResponsesLoaded(Call call) {
597             updateCall(call);
598         }
599 
600         @Override
601         public void onVideoCallProviderChanged(Call call) {
602             updateCall(call, true /* videoProviderChanged */, false);
603         }
604 
605         @Override
606         public void onStatusHintsChanged(Call call) {
607             updateCall(call);
608         }
609 
610         /**
611          * Listens for changes to extras reported by a Telecom {@link Call}.
612          *
613          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
614          * so we will only trigger an update of the call information if the source of the extras
615          * change was a {@link ConnectionService}.
616          *
617          * @param call The call.
618          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
619          *               {@link Call#SOURCE_INCALL_SERVICE}).
620          * @param extras The extras.
621          */
622         @Override
623         public void onExtrasChanged(Call call, int source, Bundle extras) {
624             // Do not inform InCallServices of changes which originated there.
625             if (source == Call.SOURCE_INCALL_SERVICE) {
626                 return;
627             }
628             updateCall(call);
629         }
630 
631         /**
632          * Listens for changes to extras reported by a Telecom {@link Call}.
633          *
634          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
635          * so we will only trigger an update of the call information if the source of the extras
636          * change was a {@link ConnectionService}.
637          *  @param call The call.
638          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
639          *               {@link Call#SOURCE_INCALL_SERVICE}).
640          * @param keys The extra key removed
641          */
642         @Override
643         public void onExtrasRemoved(Call call, int source, List<String> keys) {
644             // Do not inform InCallServices of changes which originated there.
645             if (source == Call.SOURCE_INCALL_SERVICE) {
646                 return;
647             }
648             updateCall(call);
649         }
650 
651         @Override
652         public void onHandleChanged(Call call) {
653             updateCall(call);
654         }
655 
656         @Override
657         public void onCallerDisplayNameChanged(Call call) {
658             updateCall(call);
659         }
660 
661         @Override
662         public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
663             updateCall(call);
664         }
665 
666         @Override
667         public void onTargetPhoneAccountChanged(Call call) {
668             updateCall(call);
669         }
670 
671         @Override
672         public void onConferenceableCallsChanged(Call call) {
673             updateCall(call);
674         }
675 
676         @Override
677         public void onConnectionEvent(Call call, String event, Bundle extras) {
678             notifyConnectionEvent(call, event, extras);
679         }
680 
681         @Override
682         public void onHandoverFailed(Call call, int error) {
683             notifyHandoverFailed(call, error);
684         }
685 
686         @Override
687         public void onHandoverComplete(Call call) {
688             notifyHandoverComplete(call);
689         }
690 
691         @Override
692         public void onRttInitiationFailure(Call call, int reason) {
693             notifyRttInitiationFailure(call, reason);
694             updateCall(call, false, true);
695         }
696 
697         @Override
698         public void onRemoteRttRequest(Call call, int requestId) {
699             notifyRemoteRttRequest(call, requestId);
700         }
701     };
702 
703     private final SystemStateListener mSystemStateListener = new SystemStateListener() {
704         @Override
705         public void onCarModeChanged(boolean isCarMode) {
706             if (mInCallServiceConnection != null) {
707                 mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
708             }
709         }
710     };
711 
712     private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
713     private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
714     private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
715     private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
716     private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
717     private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5;
718 
719     /** The in-call app implementations, see {@link IInCallService}. */
720     private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
721 
722     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
723 
724     /** The {@link ComponentName} of the default InCall UI. */
725     private final ComponentName mSystemInCallComponentName;
726 
727     private final Context mContext;
728     private final TelecomSystem.SyncRoot mLock;
729     private final CallsManager mCallsManager;
730     private final SystemStateHelper mSystemStateHelper;
731     private final Timeouts.Adapter mTimeoutsAdapter;
732     private final DefaultDialerCache mDefaultDialerCache;
733     private final EmergencyCallHelper mEmergencyCallHelper;
734     private CarSwappingInCallServiceConnection mInCallServiceConnection;
735     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
736 
737     // Future that's in a completed state unless we're in the middle of binding to a service.
738     // The future will complete with true if binding succeeds, false if it timed out.
739     private CompletableFuture<Boolean> mBindingFuture = CompletableFuture.completedFuture(true);
740 
InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper)741     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
742             SystemStateHelper systemStateHelper,
743             DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
744             EmergencyCallHelper emergencyCallHelper) {
745         mContext = context;
746         mLock = lock;
747         mCallsManager = callsManager;
748         mSystemStateHelper = systemStateHelper;
749         mTimeoutsAdapter = timeoutsAdapter;
750         mDefaultDialerCache = defaultDialerCache;
751         mEmergencyCallHelper = emergencyCallHelper;
752 
753         Resources resources = mContext.getResources();
754         mSystemInCallComponentName = new ComponentName(
755                 TelecomServiceImpl.getSystemDialerPackage(mContext),
756                 resources.getString(R.string.incall_default_class));
757 
758         mSystemStateHelper.addListener(mSystemStateListener);
759     }
760 
761     @Override
onCallAdded(Call call)762     public void onCallAdded(Call call) {
763         if (!isBoundAndConnectedToServices()) {
764             Log.i(this, "onCallAdded: %s; not bound or connected.", call);
765             // We are not bound, or we're not connected.
766             bindToServices(call);
767         } else {
768             // We are bound, and we are connected.
769             adjustServiceBindingsForEmergency();
770 
771             // This is in case an emergency call is added while there is an existing call.
772             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
773                     mCallsManager.getCurrentUserHandle());
774 
775             Log.i(this, "onCallAdded: %s", call);
776             // Track the call if we don't already know about it.
777             addCall(call);
778 
779             Log.i(this, "mInCallServiceConnection isConnected=%b",
780                     mInCallServiceConnection.isConnected());
781 
782             List<ComponentName> componentsUpdated = new ArrayList<>();
783             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
784                 InCallServiceInfo info = entry.getKey();
785 
786                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
787                     continue;
788                 }
789 
790                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
791                     continue;
792                 }
793 
794                 // Only send the RTT call if it's a UI in-call service
795                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
796 
797                 componentsUpdated.add(info.getComponentName());
798                 IInCallService inCallService = entry.getValue();
799 
800                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
801                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
802                         info.isExternalCallsSupported(), includeRttCall,
803                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
804                 try {
805                     inCallService.addCall(parcelableCall);
806                 } catch (RemoteException ignored) {
807                 }
808             }
809             Log.i(this, "Call added to components: %s", componentsUpdated);
810         }
811     }
812 
813     @Override
onCallRemoved(Call call)814     public void onCallRemoved(Call call) {
815         Log.i(this, "onCallRemoved: %s", call);
816         if (mCallsManager.getCalls().isEmpty()) {
817             /** Let's add a 2 second delay before we send unbind to the services to hopefully
818              *  give them enough time to process all the pending messages.
819              */
820             Handler handler = new Handler(Looper.getMainLooper());
821             handler.postDelayed(new Runnable("ICC.oCR", mLock) {
822                 @Override
823                 public void loggedRun() {
824                     // Check again to make sure there are no active calls.
825                     if (mCallsManager.getCalls().isEmpty()) {
826                         unbindFromServices();
827 
828                         mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
829                     }
830                 }
831             }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
832                             mContext.getContentResolver()));
833         }
834         call.removeListener(mCallListener);
835         mCallIdMapper.removeCall(call);
836     }
837 
838     @Override
onExternalCallChanged(Call call, boolean isExternalCall)839     public void onExternalCallChanged(Call call, boolean isExternalCall) {
840         Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
841 
842         List<ComponentName> componentsUpdated = new ArrayList<>();
843         if (!isExternalCall) {
844             // The call was external but it is no longer external.  We must now add it to any
845             // InCallServices which do not support external calls.
846             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
847                 InCallServiceInfo info = entry.getKey();
848 
849                 if (info.isExternalCallsSupported()) {
850                     // For InCallServices which support external calls, the call will have already
851                     // been added to the connection service, so we do not need to add it again.
852                     continue;
853                 }
854 
855                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
856                     continue;
857                 }
858 
859                 componentsUpdated.add(info.getComponentName());
860                 IInCallService inCallService = entry.getValue();
861 
862                 // Only send the RTT call if it's a UI in-call service
863                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
864 
865                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
866                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
867                         info.isExternalCallsSupported(), includeRttCall,
868                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
869                 try {
870                     inCallService.addCall(parcelableCall);
871                 } catch (RemoteException ignored) {
872                 }
873             }
874             Log.i(this, "Previously external call added to components: %s", componentsUpdated);
875         } else {
876             // The call was regular but it is now external.  We must now remove it from any
877             // InCallServices which do not support external calls.
878             // Remove the call by sending a call update indicating the call was disconnected.
879             Log.i(this, "Removing external call %", call);
880             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
881                 InCallServiceInfo info = entry.getKey();
882                 if (info.isExternalCallsSupported()) {
883                     // For InCallServices which support external calls, we do not need to remove
884                     // the call.
885                     continue;
886                 }
887 
888                 componentsUpdated.add(info.getComponentName());
889                 IInCallService inCallService = entry.getValue();
890 
891                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
892                         call,
893                         false /* includeVideoProvider */,
894                         mCallsManager.getPhoneAccountRegistrar(),
895                         false /* supportsExternalCalls */,
896                         android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
897                         false /* includeRttCall */,
898                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
899                         );
900 
901                 try {
902                     inCallService.updateCall(parcelableCall);
903                 } catch (RemoteException ignored) {
904                 }
905             }
906             Log.i(this, "External call removed from components: %s", componentsUpdated);
907         }
908     }
909 
910     @Override
onCallStateChanged(Call call, int oldState, int newState)911     public void onCallStateChanged(Call call, int oldState, int newState) {
912         updateCall(call);
913     }
914 
915     @Override
onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)916     public void onConnectionServiceChanged(
917             Call call,
918             ConnectionServiceWrapper oldService,
919             ConnectionServiceWrapper newService) {
920         updateCall(call);
921     }
922 
923     @Override
onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)924     public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
925             CallAudioState newCallAudioState) {
926         if (!mInCallServices.isEmpty()) {
927             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
928                     newCallAudioState);
929             for (IInCallService inCallService : mInCallServices.values()) {
930                 try {
931                     inCallService.onCallAudioStateChanged(newCallAudioState);
932                 } catch (RemoteException ignored) {
933                 }
934             }
935         }
936     }
937 
938     @Override
onCanAddCallChanged(boolean canAddCall)939     public void onCanAddCallChanged(boolean canAddCall) {
940         if (!mInCallServices.isEmpty()) {
941             Log.i(this, "onCanAddCallChanged : %b", canAddCall);
942             for (IInCallService inCallService : mInCallServices.values()) {
943                 try {
944                     inCallService.onCanAddCallChanged(canAddCall);
945                 } catch (RemoteException ignored) {
946                 }
947             }
948         }
949     }
950 
onPostDialWait(Call call, String remaining)951     void onPostDialWait(Call call, String remaining) {
952         if (!mInCallServices.isEmpty()) {
953             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
954             for (IInCallService inCallService : mInCallServices.values()) {
955                 try {
956                     inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
957                 } catch (RemoteException ignored) {
958                 }
959             }
960         }
961     }
962 
963     @Override
onIsConferencedChanged(Call call)964     public void onIsConferencedChanged(Call call) {
965         Log.d(this, "onIsConferencedChanged %s", call);
966         updateCall(call);
967     }
968 
969     @Override
onConnectionTimeChanged(Call call)970     public void onConnectionTimeChanged(Call call) {
971         Log.d(this, "onConnectionTimeChanged %s", call);
972         updateCall(call);
973     }
974 
975     @Override
onIsVoipAudioModeChanged(Call call)976     public void onIsVoipAudioModeChanged(Call call) {
977         Log.d(this, "onIsVoipAudioModeChanged %s", call);
978         updateCall(call);
979     }
980 
981     @Override
onConferenceStateChanged(Call call, boolean isConference)982     public void onConferenceStateChanged(Call call, boolean isConference) {
983         Log.d(this, "onConferenceStateChanged %s ,isConf=%b", call, isConference);
984         updateCall(call);
985     }
986 
bringToForeground(boolean showDialpad)987     void bringToForeground(boolean showDialpad) {
988         if (!mInCallServices.isEmpty()) {
989             for (IInCallService inCallService : mInCallServices.values()) {
990                 try {
991                     inCallService.bringToForeground(showDialpad);
992                 } catch (RemoteException ignored) {
993                 }
994             }
995         } else {
996             Log.w(this, "Asking to bring unbound in-call UI to foreground.");
997         }
998     }
999 
silenceRinger()1000     void silenceRinger() {
1001         if (!mInCallServices.isEmpty()) {
1002             for (IInCallService inCallService : mInCallServices.values()) {
1003                 try {
1004                     inCallService.silenceRinger();
1005                 } catch (RemoteException ignored) {
1006                 }
1007             }
1008         }
1009     }
1010 
notifyConnectionEvent(Call call, String event, Bundle extras)1011     private void notifyConnectionEvent(Call call, String event, Bundle extras) {
1012         if (!mInCallServices.isEmpty()) {
1013             for (IInCallService inCallService : mInCallServices.values()) {
1014                 try {
1015                     Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
1016                             (call != null ? call.toString() :"null"),
1017                             (event != null ? event : "null") ,
1018                             (extras != null ? extras.toString() : "null"));
1019                     inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras);
1020                 } catch (RemoteException ignored) {
1021                 }
1022             }
1023         }
1024     }
1025 
notifyRttInitiationFailure(Call call, int reason)1026     private void notifyRttInitiationFailure(Call call, int reason) {
1027         if (!mInCallServices.isEmpty()) {
1028              mInCallServices.entrySet().stream()
1029                     .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
1030                     .forEach((entry) -> {
1031                         try {
1032                             Log.i(this, "notifyRttFailure, call %s, incall %s",
1033                                     call, entry.getKey());
1034                             entry.getValue().onRttInitiationFailure(mCallIdMapper.getCallId(call),
1035                                     reason);
1036                         } catch (RemoteException ignored) {
1037                         }
1038                     });
1039         }
1040     }
1041 
notifyRemoteRttRequest(Call call, int requestId)1042     private void notifyRemoteRttRequest(Call call, int requestId) {
1043         if (!mInCallServices.isEmpty()) {
1044             mInCallServices.entrySet().stream()
1045                     .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
1046                     .forEach((entry) -> {
1047                         try {
1048                             Log.i(this, "notifyRemoteRttRequest, call %s, incall %s",
1049                                     call, entry.getKey());
1050                             entry.getValue().onRttUpgradeRequest(
1051                                     mCallIdMapper.getCallId(call), requestId);
1052                         } catch (RemoteException ignored) {
1053                         }
1054                     });
1055         }
1056     }
1057 
notifyHandoverFailed(Call call, int error)1058     private void notifyHandoverFailed(Call call, int error) {
1059         if (!mInCallServices.isEmpty()) {
1060             for (IInCallService inCallService : mInCallServices.values()) {
1061                 try {
1062                     inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error);
1063                 } catch (RemoteException ignored) {
1064                 }
1065             }
1066         }
1067     }
1068 
notifyHandoverComplete(Call call)1069     private void notifyHandoverComplete(Call call) {
1070         if (!mInCallServices.isEmpty()) {
1071             for (IInCallService inCallService : mInCallServices.values()) {
1072                 try {
1073                     inCallService.onHandoverComplete(mCallIdMapper.getCallId(call));
1074                 } catch (RemoteException ignored) {
1075                 }
1076             }
1077         }
1078     }
1079 
1080     /**
1081      * Unbinds an existing bound connection to the in-call app.
1082      */
unbindFromServices()1083     private void unbindFromServices() {
1084         if (mInCallServiceConnection != null) {
1085             mInCallServiceConnection.disconnect();
1086             mInCallServiceConnection = null;
1087         }
1088         if (mNonUIInCallServiceConnections != null) {
1089             mNonUIInCallServiceConnections.disconnect();
1090             mNonUIInCallServiceConnections = null;
1091         }
1092         mInCallServices.clear();
1093     }
1094 
1095     /**
1096      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
1097      * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking.
1098      *
1099      * @param call The newly added call that triggered the binding to the in-call services.
1100      */
1101     @VisibleForTesting
bindToServices(Call call)1102     public void bindToServices(Call call) {
1103         if (mInCallServiceConnection == null) {
1104             InCallServiceConnection dialerInCall = null;
1105             InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
1106             Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
1107             if (defaultDialerComponentInfo != null &&
1108                     !defaultDialerComponentInfo.getComponentName().equals(
1109                             mSystemInCallComponentName)) {
1110                 dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
1111             }
1112             Log.i(this, "defaultDialer: " + dialerInCall);
1113 
1114             InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
1115                     mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI);
1116             EmergencyInCallServiceConnection systemInCall =
1117                     new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
1118             systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
1119 
1120             InCallServiceConnection carModeInCall = null;
1121             InCallServiceInfo carModeComponentInfo = getCarModeComponent();
1122             if (carModeComponentInfo != null &&
1123                     !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
1124                 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
1125             }
1126 
1127             mInCallServiceConnection =
1128                     new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
1129         }
1130 
1131         mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
1132 
1133         // Actually try binding to the UI InCallService.  If the response
1134         if (mInCallServiceConnection.connect(call) ==
1135                 InCallServiceConnection.CONNECTION_SUCCEEDED) {
1136             // Only connect to the non-ui InCallServices if we actually connected to the main UI
1137             // one.
1138             connectToNonUiInCallServices(call);
1139             mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
1140                     mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
1141                             mContext.getContentResolver()),
1142                     TimeUnit.MILLISECONDS);
1143         } else {
1144             Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
1145         }
1146     }
1147 
connectToNonUiInCallServices(Call call)1148     private void connectToNonUiInCallServices(Call call) {
1149         List<InCallServiceInfo> nonUIInCallComponents =
1150                 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
1151         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
1152         for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
1153             nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
1154         }
1155         List<String> callCompanionApps = mCallsManager
1156                 .getRoleManagerAdapter().getCallCompanionApps();
1157         if (callCompanionApps != null && !callCompanionApps.isEmpty()) {
1158             for(String pkg : callCompanionApps) {
1159                 InCallServiceInfo info = getInCallServiceComponent(pkg,
1160                         IN_CALL_SERVICE_TYPE_COMPANION);
1161                 if (info != null) {
1162                     nonUIInCalls.add(new InCallServiceBindingConnection(info));
1163                 }
1164             }
1165         }
1166         mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
1167         mNonUIInCallServiceConnections.connect(call);
1168     }
1169 
getDefaultDialerComponent()1170     private InCallServiceInfo getDefaultDialerComponent() {
1171         String packageName = mDefaultDialerCache.getDefaultDialerApplication(
1172                 mCallsManager.getCurrentUserHandle().getIdentifier());
1173         Log.d(this, "Default Dialer package: " + packageName);
1174 
1175         return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
1176     }
1177 
getCarModeComponent()1178     private InCallServiceInfo getCarModeComponent() {
1179         // The signatures of getInCallServiceComponent differ in the types of the first parameter,
1180         // and passing in null is inherently ambiguous. (If no car mode component found)
1181         String defaultCarMode = mCallsManager.getRoleManagerAdapter().getCarModeDialerApp();
1182         return getInCallServiceComponent(defaultCarMode, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
1183     }
1184 
getInCallServiceComponent(ComponentName componentName, int type)1185     private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
1186         List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type);
1187         if (list != null && !list.isEmpty()) {
1188             return list.get(0);
1189         } else {
1190             // Last Resort: Try to bind to the ComponentName given directly.
1191             Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
1192                     + componentName +". Trying to bind anyway.");
1193             return new InCallServiceInfo(componentName, false, false, type);
1194         }
1195     }
1196 
getInCallServiceComponent(String packageName, int type)1197     private InCallServiceInfo getInCallServiceComponent(String packageName, int type) {
1198         List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type);
1199         if (list != null && !list.isEmpty()) {
1200             return list.get(0);
1201         }
1202         return null;
1203     }
1204 
getInCallServiceComponents(int type)1205     private List<InCallServiceInfo> getInCallServiceComponents(int type) {
1206         return getInCallServiceComponents(null, null, type);
1207     }
1208 
getInCallServiceComponents(String packageName, int type)1209     private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) {
1210         return getInCallServiceComponents(packageName, null, type);
1211     }
1212 
getInCallServiceComponents(ComponentName componentName, int type)1213     private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
1214             int type) {
1215         return getInCallServiceComponents(null, componentName, type);
1216     }
1217 
getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType)1218     private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
1219             ComponentName componentName, int requestedType) {
1220 
1221         List<InCallServiceInfo> retval = new LinkedList<>();
1222 
1223         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
1224         if (packageName != null) {
1225             serviceIntent.setPackage(packageName);
1226         }
1227         if (componentName != null) {
1228             serviceIntent.setComponent(componentName);
1229         }
1230 
1231         PackageManager packageManager = mContext.getPackageManager();
1232         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
1233                 serviceIntent,
1234                 PackageManager.GET_META_DATA,
1235                 mCallsManager.getCurrentUserHandle().getIdentifier())) {
1236             ServiceInfo serviceInfo = entry.serviceInfo;
1237             if (serviceInfo != null) {
1238                 boolean isExternalCallsSupported = serviceInfo.metaData != null &&
1239                         serviceInfo.metaData.getBoolean(
1240                                 TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false);
1241                 boolean isSelfManageCallsSupported = serviceInfo.metaData != null &&
1242                         serviceInfo.metaData.getBoolean(
1243                                 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
1244 
1245                 int currentType = getInCallServiceType(entry.serviceInfo, packageManager,
1246                         packageName);
1247                 if (requestedType == 0 || requestedType == currentType) {
1248                     if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
1249                         // We enforce the rule that self-managed calls are not supported by non-ui
1250                         // InCallServices.
1251                         isSelfManageCallsSupported = false;
1252                     }
1253                     retval.add(new InCallServiceInfo(
1254                             new ComponentName(serviceInfo.packageName, serviceInfo.name),
1255                             isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
1256                 }
1257             }
1258         }
1259 
1260         return retval;
1261     }
1262 
shouldUseCarModeUI()1263     private boolean shouldUseCarModeUI() {
1264         return mSystemStateHelper.isCarMode();
1265     }
1266 
1267     /**
1268      * Returns the type of InCallService described by the specified serviceInfo.
1269      */
getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager, String packageName)1270     private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager,
1271             String packageName) {
1272         // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
1273         // enforces that only Telecom can bind to it.
1274         boolean hasServiceBindPermission = serviceInfo.permission != null &&
1275                 serviceInfo.permission.equals(
1276                         Manifest.permission.BIND_INCALL_SERVICE);
1277         if (!hasServiceBindPermission) {
1278             Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " +
1279                     serviceInfo.packageName);
1280             return IN_CALL_SERVICE_TYPE_INVALID;
1281         }
1282 
1283         if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) &&
1284                 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) {
1285             return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
1286         }
1287 
1288         // Check to see if the service holds permissions or metadata for third party apps.
1289         boolean isUIService = serviceInfo.metaData != null &&
1290                 serviceInfo.metaData.getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_UI);
1291         boolean isThirdPartyCompanionApp = packageManager.checkPermission(
1292                 Manifest.permission.CALL_COMPANION_APP,
1293                 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED &&
1294                 !isUIService;
1295 
1296         // Check to see if the service is a car-mode UI type by checking that it has the
1297         // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
1298         // car-mode UI metadata.
1299         boolean hasControlInCallPermission = packageManager.checkPermission(
1300                 Manifest.permission.CONTROL_INCALL_EXPERIENCE,
1301                 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
1302         boolean isCarModeUIService = serviceInfo.metaData != null &&
1303                 serviceInfo.metaData.getBoolean(
1304                         TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false);
1305         if (isCarModeUIService) {
1306             // ThirdPartyInCallService shouldn't be used when role manager hasn't assigned any car
1307             // mode role holders, i.e. packageName is null.
1308             if (hasControlInCallPermission || (isThirdPartyCompanionApp && packageName != null)) {
1309                 return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
1310             }
1311         }
1312 
1313         // Check to see that it is the default dialer package
1314         boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
1315                 mDefaultDialerCache.getDefaultDialerApplication(
1316                     mCallsManager.getCurrentUserHandle().getIdentifier()));
1317         if (isDefaultDialerPackage && isUIService) {
1318             return IN_CALL_SERVICE_TYPE_DIALER_UI;
1319         }
1320 
1321         // Also allow any in-call service that has the control-experience permission (to ensure
1322         // that it is a system app) and doesn't claim to show any UI.
1323         if (!isUIService && !isCarModeUIService) {
1324             if (hasControlInCallPermission && !isThirdPartyCompanionApp) {
1325                 return IN_CALL_SERVICE_TYPE_NON_UI;
1326             }
1327             // Third party companion alls without CONTROL_INCALL_EXPERIENCE permission.
1328             if (!hasControlInCallPermission && isThirdPartyCompanionApp) {
1329                 return IN_CALL_SERVICE_TYPE_COMPANION;
1330             }
1331         }
1332 
1333         // Anything else that remains, we will not bind to.
1334         Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b",
1335                 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission,
1336                 isCarModeUIService, isUIService);
1337         return IN_CALL_SERVICE_TYPE_INVALID;
1338     }
1339 
adjustServiceBindingsForEmergency()1340     private void adjustServiceBindingsForEmergency() {
1341         // The connected UI is not the system UI, so lets check if we should switch them
1342         // if there exists an emergency number.
1343         if (mCallsManager.hasEmergencyCall()) {
1344             mInCallServiceConnection.setHasEmergency(true);
1345         }
1346     }
1347 
1348     /**
1349      * Persists the {@link IInCallService} instance and starts the communication between
1350      * this class and in-call app by sending the first update to in-call app. This method is
1351      * called after a successful binding connection is established.
1352      *
1353      * @param info Info about the service, including its {@link ComponentName}.
1354      * @param service The {@link IInCallService} implementation.
1355      * @return True if we successfully connected.
1356      */
onConnected(InCallServiceInfo info, IBinder service)1357     private boolean onConnected(InCallServiceInfo info, IBinder service) {
1358         Trace.beginSection("onConnected: " + info.getComponentName());
1359         Log.i(this, "onConnected to %s", info.getComponentName());
1360 
1361         IInCallService inCallService = IInCallService.Stub.asInterface(service);
1362         mInCallServices.put(info, inCallService);
1363 
1364         try {
1365             inCallService.setInCallAdapter(
1366                     new InCallAdapter(
1367                             mCallsManager,
1368                             mCallIdMapper,
1369                             mLock,
1370                             info.getComponentName().getPackageName()));
1371         } catch (RemoteException e) {
1372             Log.e(this, e, "Failed to set the in-call adapter.");
1373             Trace.endSection();
1374             return false;
1375         }
1376 
1377         // Upon successful connection, send the state of the world to the service.
1378         List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
1379         Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
1380                 "calls", calls.size(), info.getComponentName());
1381         int numCallsSent = 0;
1382         for (Call call : calls) {
1383             try {
1384                 if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) ||
1385                         (call.isExternalCall() && !info.isExternalCallsSupported())) {
1386                     continue;
1387                 }
1388 
1389                 // Only send the RTT call if it's a UI in-call service
1390                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
1391 
1392                 // Track the call if we don't already know about it.
1393                 addCall(call);
1394                 numCallsSent += 1;
1395                 inCallService.addCall(ParcelableCallUtils.toParcelableCall(
1396                         call,
1397                         true /* includeVideoProvider */,
1398                         mCallsManager.getPhoneAccountRegistrar(),
1399                         info.isExternalCallsSupported(),
1400                         includeRttCall,
1401                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI));
1402             } catch (RemoteException ignored) {
1403             }
1404         }
1405         try {
1406             inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
1407             inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
1408         } catch (RemoteException ignored) {
1409         }
1410         mBindingFuture.complete(true);
1411         Log.i(this, "%s calls sent to InCallService.", numCallsSent);
1412         Trace.endSection();
1413         return true;
1414     }
1415 
1416     /**
1417      * Cleans up an instance of in-call app after the service has been unbound.
1418      *
1419      * @param disconnectedInfo The {@link InCallServiceInfo} of the service which disconnected.
1420      */
onDisconnected(InCallServiceInfo disconnectedInfo)1421     private void onDisconnected(InCallServiceInfo disconnectedInfo) {
1422         Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
1423 
1424         mInCallServices.remove(disconnectedInfo);
1425     }
1426 
1427     /**
1428      * Informs all {@link InCallService} instances of the updated call information.
1429      *
1430      * @param call The {@link Call}.
1431      */
updateCall(Call call)1432     private void updateCall(Call call) {
1433         updateCall(call, false /* videoProviderChanged */, false);
1434     }
1435 
1436     /**
1437      * Informs all {@link InCallService} instances of the updated call information.
1438      *
1439      * @param call The {@link Call}.
1440      * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
1441      *      otherwise.
1442      * @param rttInfoChanged {@code true} if any information about the RTT session changed,
1443      * {@code false} otherwise.
1444      */
updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged)1445     private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) {
1446         if (!mInCallServices.isEmpty()) {
1447             Log.i(this, "Sending updateCall %s", call);
1448             List<ComponentName> componentsUpdated = new ArrayList<>();
1449             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
1450                 InCallServiceInfo info = entry.getKey();
1451                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
1452                     continue;
1453                 }
1454 
1455                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
1456                     continue;
1457                 }
1458 
1459                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
1460                         call,
1461                         videoProviderChanged /* includeVideoProvider */,
1462                         mCallsManager.getPhoneAccountRegistrar(),
1463                         info.isExternalCallsSupported(),
1464                         rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()),
1465                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
1466                 ComponentName componentName = info.getComponentName();
1467                 IInCallService inCallService = entry.getValue();
1468                 componentsUpdated.add(componentName);
1469 
1470                 try {
1471                     inCallService.updateCall(parcelableCall);
1472                 } catch (RemoteException ignored) {
1473                 }
1474             }
1475             Log.i(this, "Components updated: %s", componentsUpdated);
1476         }
1477     }
1478 
1479     /**
1480      * Adds the call to the list of calls tracked by the {@link InCallController}.
1481      * @param call The call to add.
1482      */
addCall(Call call)1483     private void addCall(Call call) {
1484         if (mCallIdMapper.getCallId(call) == null) {
1485             mCallIdMapper.addCall(call);
1486             call.addListener(mCallListener);
1487         }
1488     }
1489 
1490     /**
1491      * @return true if we are bound to the UI InCallService and it is connected.
1492      */
isBoundAndConnectedToServices()1493     private boolean isBoundAndConnectedToServices() {
1494         return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
1495     }
1496 
1497     /**
1498      * @return A future that is pending whenever we are in the middle of binding to an
1499      *         incall service.
1500      */
getBindingFuture()1501     public CompletableFuture<Boolean> getBindingFuture() {
1502         return mBindingFuture;
1503     }
1504 
1505     /**
1506      * Dumps the state of the {@link InCallController}.
1507      *
1508      * @param pw The {@code IndentingPrintWriter} to write the state to.
1509      */
dump(IndentingPrintWriter pw)1510     public void dump(IndentingPrintWriter pw) {
1511         pw.println("mInCallServices (InCalls registered):");
1512         pw.increaseIndent();
1513         for (InCallServiceInfo info : mInCallServices.keySet()) {
1514             pw.println(info);
1515         }
1516         pw.decreaseIndent();
1517 
1518         pw.println("ServiceConnections (InCalls bound):");
1519         pw.increaseIndent();
1520         if (mInCallServiceConnection != null) {
1521             mInCallServiceConnection.dump(pw);
1522         }
1523         pw.decreaseIndent();
1524     }
1525 
1526     /**
1527      * @return The package name of the UI which is currently bound, or null if none.
1528      */
getConnectedUi()1529     private ComponentName getConnectedUi() {
1530         InCallServiceInfo connectedUi = mInCallServices.keySet().stream().filter(
1531                 i -> i.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI
1532                         || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
1533                 .findAny()
1534                 .orElse(null);
1535         if (connectedUi != null) {
1536             return connectedUi.mComponentName;
1537         }
1538         return null;
1539     }
1540 
doesConnectedDialerSupportRinging()1541     public boolean doesConnectedDialerSupportRinging() {
1542         String ringingPackage =  null;
1543 
1544         ComponentName connectedPackage = getConnectedUi();
1545         if (connectedPackage != null) {
1546             ringingPackage = connectedPackage.getPackageName().trim();
1547             Log.d(this, "doesConnectedDialerSupportRinging: alreadyConnectedPackage=%s",
1548                     ringingPackage);
1549         }
1550 
1551         if (TextUtils.isEmpty(ringingPackage)) {
1552             // The current in-call UI returned nothing, so lets use the default dialer.
1553             ringingPackage = mDefaultDialerCache.getDefaultDialerApplication(
1554                     mCallsManager.getCurrentUserHandle().getIdentifier());
1555             if (ringingPackage != null) {
1556                 Log.d(this, "doesConnectedDialerSupportRinging: notCurentlyConnectedPackage=%s",
1557                         ringingPackage);
1558             }
1559         }
1560         if (TextUtils.isEmpty(ringingPackage)) {
1561             Log.w(this, "doesConnectedDialerSupportRinging: no default dialer found; oh no!");
1562             return false;
1563         }
1564 
1565         Intent intent = new Intent(InCallService.SERVICE_INTERFACE)
1566             .setPackage(ringingPackage);
1567         List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
1568                 intent, PackageManager.GET_META_DATA,
1569                 mCallsManager.getCurrentUserHandle().getIdentifier());
1570         if (entries.isEmpty()) {
1571             Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's package info"
1572                     + " <sad trombone>");
1573             return false;
1574         }
1575 
1576         ResolveInfo info = entries.get(0);
1577         if (info.serviceInfo == null || info.serviceInfo.metaData == null) {
1578             Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's metadata"
1579                     + " <even sadder trombone>");
1580             return false;
1581         }
1582 
1583         return info.serviceInfo.metaData
1584                 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false);
1585     }
1586 
orderCallsWithChildrenFirst(Collection<Call> calls)1587     private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) {
1588         LinkedList<Call> parentCalls = new LinkedList<>();
1589         LinkedList<Call> childCalls = new LinkedList<>();
1590         for (Call call : calls) {
1591             if (call.getChildCalls().size() > 0) {
1592                 parentCalls.add(call);
1593             } else {
1594                 childCalls.add(call);
1595             }
1596         }
1597         childCalls.addAll(parentCalls);
1598         return childCalls;
1599     }
1600 }
1601