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