• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.telecom;
18 
19 import android.Manifest;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.ServiceConnection;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.content.pm.ServiceInfo;
27 import android.content.res.Resources;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.Trace;
34 import android.os.UserHandle;
35 import android.telecom.CallAudioState;
36 import android.telecom.ConnectionService;
37 import android.telecom.DefaultDialerManager;
38 import android.telecom.InCallService;
39 import android.telecom.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);
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 onHandoverFailed(Call call, int error) {
681             notifyHandoverFailed(call, error);
682         }
683 
684         @Override
685         public void onHandoverComplete(Call call) {
686             notifyHandoverComplete(call);
687         }
688 
689         @Override
690         public void onRttInitiationFailure(Call call, int reason) {
691             notifyRttInitiationFailure(call, reason);
692             updateCall(call, false, true);
693         }
694 
695         @Override
696         public void onRemoteRttRequest(Call call, int requestId) {
697             notifyRemoteRttRequest(call, requestId);
698         }
699     };
700 
701     private final SystemStateListener mSystemStateListener = new SystemStateListener() {
702         @Override
703         public void onCarModeChanged(boolean isCarMode) {
704             if (mInCallServiceConnection != null) {
705                 mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
706             }
707         }
708     };
709 
710     private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
711     private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
712     private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
713     private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
714     private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
715 
716     /** The in-call app implementations, see {@link IInCallService}. */
717     private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
718 
719     /**
720      * The {@link ComponentName} of the bound In-Call UI Service.
721      */
722     private ComponentName mInCallUIComponentName;
723 
724     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
725 
726     /** The {@link ComponentName} of the default InCall UI. */
727     private final ComponentName mSystemInCallComponentName;
728 
729     private final Context mContext;
730     private final TelecomSystem.SyncRoot mLock;
731     private final CallsManager mCallsManager;
732     private final SystemStateProvider mSystemStateProvider;
733     private final Timeouts.Adapter mTimeoutsAdapter;
734     private final DefaultDialerCache mDefaultDialerCache;
735     private final EmergencyCallHelper mEmergencyCallHelper;
736     private CarSwappingInCallServiceConnection mInCallServiceConnection;
737     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
738 
InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateProvider systemStateProvider, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper)739     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
740             SystemStateProvider systemStateProvider,
741             DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
742             EmergencyCallHelper emergencyCallHelper) {
743         mContext = context;
744         mLock = lock;
745         mCallsManager = callsManager;
746         mSystemStateProvider = systemStateProvider;
747         mTimeoutsAdapter = timeoutsAdapter;
748         mDefaultDialerCache = defaultDialerCache;
749         mEmergencyCallHelper = emergencyCallHelper;
750 
751         Resources resources = mContext.getResources();
752         mSystemInCallComponentName = new ComponentName(
753                 resources.getString(R.string.ui_default_package),
754                 resources.getString(R.string.incall_default_class));
755 
756         mSystemStateProvider.addListener(mSystemStateListener);
757     }
758 
759     @Override
onCallAdded(Call call)760     public void onCallAdded(Call call) {
761         if (!isBoundAndConnectedToServices()) {
762             Log.i(this, "onCallAdded: %s; not bound or connected.", call);
763             // We are not bound, or we're not connected.
764             bindToServices(call);
765         } else {
766             // We are bound, and we are connected.
767             adjustServiceBindingsForEmergency();
768 
769             // This is in case an emergency call is added while there is an existing call.
770             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
771                     mCallsManager.getCurrentUserHandle());
772 
773             Log.i(this, "onCallAdded: %s", call);
774             // Track the call if we don't already know about it.
775             addCall(call);
776 
777             Log.i(this, "mInCallServiceConnection isConnected=%b",
778                     mInCallServiceConnection.isConnected());
779 
780             List<ComponentName> componentsUpdated = new ArrayList<>();
781             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
782                 InCallServiceInfo info = entry.getKey();
783 
784                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
785                     continue;
786                 }
787 
788                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
789                     continue;
790                 }
791 
792                 // Only send the RTT call if it's a UI in-call service
793                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
794 
795                 componentsUpdated.add(info.getComponentName());
796                 IInCallService inCallService = entry.getValue();
797 
798                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
799                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
800                         info.isExternalCallsSupported(), includeRttCall);
801                 try {
802                     inCallService.addCall(parcelableCall);
803                 } catch (RemoteException ignored) {
804                 }
805             }
806             Log.i(this, "Call added to components: %s", componentsUpdated);
807         }
808     }
809 
810     @Override
onCallRemoved(Call call)811     public void onCallRemoved(Call call) {
812         Log.i(this, "onCallRemoved: %s", call);
813         if (mCallsManager.getCalls().isEmpty()) {
814             /** Let's add a 2 second delay before we send unbind to the services to hopefully
815              *  give them enough time to process all the pending messages.
816              */
817             Handler handler = new Handler(Looper.getMainLooper());
818             handler.postDelayed(new Runnable("ICC.oCR", mLock) {
819                 @Override
820                 public void loggedRun() {
821                     // Check again to make sure there are no active calls.
822                     if (mCallsManager.getCalls().isEmpty()) {
823                         unbindFromServices();
824 
825                         mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
826                     }
827                 }
828             }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
829                             mContext.getContentResolver()));
830         }
831         call.removeListener(mCallListener);
832         mCallIdMapper.removeCall(call);
833     }
834 
835     @Override
onExternalCallChanged(Call call, boolean isExternalCall)836     public void onExternalCallChanged(Call call, boolean isExternalCall) {
837         Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
838 
839         List<ComponentName> componentsUpdated = new ArrayList<>();
840         if (!isExternalCall) {
841             // The call was external but it is no longer external.  We must now add it to any
842             // InCallServices which do not support external calls.
843             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
844                 InCallServiceInfo info = entry.getKey();
845 
846                 if (info.isExternalCallsSupported()) {
847                     // For InCallServices which support external calls, the call will have already
848                     // been added to the connection service, so we do not need to add it again.
849                     continue;
850                 }
851 
852                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
853                     continue;
854                 }
855 
856                 componentsUpdated.add(info.getComponentName());
857                 IInCallService inCallService = entry.getValue();
858 
859                 // Only send the RTT call if it's a UI in-call service
860                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
861 
862                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
863                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
864                         info.isExternalCallsSupported(), includeRttCall);
865                 try {
866                     inCallService.addCall(parcelableCall);
867                 } catch (RemoteException ignored) {
868                 }
869             }
870             Log.i(this, "Previously external call added to components: %s", componentsUpdated);
871         } else {
872             // The call was regular but it is now external.  We must now remove it from any
873             // InCallServices which do not support external calls.
874             // Remove the call by sending a call update indicating the call was disconnected.
875             ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
876                     call,
877                     false /* includeVideoProvider */,
878                     mCallsManager.getPhoneAccountRegistrar(),
879                     false /* supportsExternalCalls */,
880                     android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
881                     false /* includeRttCall */);
882 
883             Log.i(this, "Removing external call %s ==> %s", call, parcelableCall);
884             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
885                 InCallServiceInfo info = entry.getKey();
886                 if (info.isExternalCallsSupported()) {
887                     // For InCallServices which support external calls, we do not need to remove
888                     // the call.
889                     continue;
890                 }
891 
892                 componentsUpdated.add(info.getComponentName());
893                 IInCallService inCallService = entry.getValue();
894 
895                 try {
896                     inCallService.updateCall(parcelableCall);
897                 } catch (RemoteException ignored) {
898                 }
899             }
900             Log.i(this, "External call removed from components: %s", componentsUpdated);
901         }
902     }
903 
904     @Override
onCallStateChanged(Call call, int oldState, int newState)905     public void onCallStateChanged(Call call, int oldState, int newState) {
906         updateCall(call);
907     }
908 
909     @Override
onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)910     public void onConnectionServiceChanged(
911             Call call,
912             ConnectionServiceWrapper oldService,
913             ConnectionServiceWrapper newService) {
914         updateCall(call);
915     }
916 
917     @Override
onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)918     public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
919             CallAudioState newCallAudioState) {
920         if (!mInCallServices.isEmpty()) {
921             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
922                     newCallAudioState);
923             for (IInCallService inCallService : mInCallServices.values()) {
924                 try {
925                     inCallService.onCallAudioStateChanged(newCallAudioState);
926                 } catch (RemoteException ignored) {
927                 }
928             }
929         }
930     }
931 
932     @Override
onCanAddCallChanged(boolean canAddCall)933     public void onCanAddCallChanged(boolean canAddCall) {
934         if (!mInCallServices.isEmpty()) {
935             Log.i(this, "onCanAddCallChanged : %b", canAddCall);
936             for (IInCallService inCallService : mInCallServices.values()) {
937                 try {
938                     inCallService.onCanAddCallChanged(canAddCall);
939                 } catch (RemoteException ignored) {
940                 }
941             }
942         }
943     }
944 
onPostDialWait(Call call, String remaining)945     void onPostDialWait(Call call, String remaining) {
946         if (!mInCallServices.isEmpty()) {
947             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
948             for (IInCallService inCallService : mInCallServices.values()) {
949                 try {
950                     inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
951                 } catch (RemoteException ignored) {
952                 }
953             }
954         }
955     }
956 
957     @Override
onIsConferencedChanged(Call call)958     public void onIsConferencedChanged(Call call) {
959         Log.d(this, "onIsConferencedChanged %s", call);
960         updateCall(call);
961     }
962 
bringToForeground(boolean showDialpad)963     void bringToForeground(boolean showDialpad) {
964         if (!mInCallServices.isEmpty()) {
965             for (IInCallService inCallService : mInCallServices.values()) {
966                 try {
967                     inCallService.bringToForeground(showDialpad);
968                 } catch (RemoteException ignored) {
969                 }
970             }
971         } else {
972             Log.w(this, "Asking to bring unbound in-call UI to foreground.");
973         }
974     }
975 
silenceRinger()976     void silenceRinger() {
977         if (!mInCallServices.isEmpty()) {
978             for (IInCallService inCallService : mInCallServices.values()) {
979                 try {
980                     inCallService.silenceRinger();
981                 } catch (RemoteException ignored) {
982                 }
983             }
984         }
985     }
986 
notifyConnectionEvent(Call call, String event, Bundle extras)987     private void notifyConnectionEvent(Call call, String event, Bundle extras) {
988         if (!mInCallServices.isEmpty()) {
989             for (IInCallService inCallService : mInCallServices.values()) {
990                 try {
991                     Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
992                             (call != null ? call.toString() :"null"),
993                             (event != null ? event : "null") ,
994                             (extras != null ? extras.toString() : "null"));
995                     inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras);
996                 } catch (RemoteException ignored) {
997                 }
998             }
999         }
1000     }
1001 
notifyRttInitiationFailure(Call call, int reason)1002     private void notifyRttInitiationFailure(Call call, int reason) {
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, "notifyRttFailure, call %s, incall %s",
1009                                     call, entry.getKey());
1010                             entry.getValue().onRttInitiationFailure(mCallIdMapper.getCallId(call),
1011                                     reason);
1012                         } catch (RemoteException ignored) {
1013                         }
1014                     });
1015         }
1016     }
1017 
notifyRemoteRttRequest(Call call, int requestId)1018     private void notifyRemoteRttRequest(Call call, int requestId) {
1019         if (!mInCallServices.isEmpty()) {
1020             mInCallServices.entrySet().stream()
1021                     .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
1022                     .forEach((entry) -> {
1023                         try {
1024                             Log.i(this, "notifyRemoteRttRequest, call %s, incall %s",
1025                                     call, entry.getKey());
1026                             entry.getValue().onRttUpgradeRequest(
1027                                     mCallIdMapper.getCallId(call), requestId);
1028                         } catch (RemoteException ignored) {
1029                         }
1030                     });
1031         }
1032     }
1033 
notifyHandoverFailed(Call call, int error)1034     private void notifyHandoverFailed(Call call, int error) {
1035         if (!mInCallServices.isEmpty()) {
1036             for (IInCallService inCallService : mInCallServices.values()) {
1037                 try {
1038                     inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error);
1039                 } catch (RemoteException ignored) {
1040                 }
1041             }
1042         }
1043     }
1044 
notifyHandoverComplete(Call call)1045     private void notifyHandoverComplete(Call call) {
1046         if (!mInCallServices.isEmpty()) {
1047             for (IInCallService inCallService : mInCallServices.values()) {
1048                 try {
1049                     inCallService.onHandoverComplete(mCallIdMapper.getCallId(call));
1050                 } catch (RemoteException ignored) {
1051                 }
1052             }
1053         }
1054     }
1055 
1056     /**
1057      * Unbinds an existing bound connection to the in-call app.
1058      */
unbindFromServices()1059     private void unbindFromServices() {
1060         if (mInCallServiceConnection != null) {
1061             mInCallServiceConnection.disconnect();
1062             mInCallServiceConnection = null;
1063         }
1064         if (mNonUIInCallServiceConnections != null) {
1065             mNonUIInCallServiceConnections.disconnect();
1066             mNonUIInCallServiceConnections = null;
1067         }
1068         mInCallServices.clear();
1069     }
1070 
1071     /**
1072      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
1073      * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking.
1074      *
1075      * @param call The newly added call that triggered the binding to the in-call services.
1076      */
1077     @VisibleForTesting
bindToServices(Call call)1078     public void bindToServices(Call call) {
1079         if (mInCallServiceConnection == null) {
1080             InCallServiceConnection dialerInCall = null;
1081             InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
1082             Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
1083             if (defaultDialerComponentInfo != null &&
1084                     !defaultDialerComponentInfo.getComponentName().equals(
1085                             mSystemInCallComponentName)) {
1086                 dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
1087             }
1088             Log.i(this, "defaultDialer: " + dialerInCall);
1089 
1090             InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
1091                     mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI);
1092             EmergencyInCallServiceConnection systemInCall =
1093                     new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
1094             systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
1095 
1096             InCallServiceConnection carModeInCall = null;
1097             InCallServiceInfo carModeComponentInfo = getCarModeComponent();
1098             if (carModeComponentInfo != null &&
1099                     !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
1100                 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
1101             }
1102 
1103             mInCallServiceConnection =
1104                     new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
1105         }
1106 
1107         mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
1108 
1109         // Actually try binding to the UI InCallService.  If the response
1110         if (mInCallServiceConnection.connect(call) ==
1111                 InCallServiceConnection.CONNECTION_SUCCEEDED) {
1112             // Only connect to the non-ui InCallServices if we actually connected to the main UI
1113             // one.
1114             connectToNonUiInCallServices(call);
1115         } else {
1116             Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
1117         }
1118     }
1119 
connectToNonUiInCallServices(Call call)1120     private void connectToNonUiInCallServices(Call call) {
1121         List<InCallServiceInfo> nonUIInCallComponents =
1122                 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
1123         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
1124         for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
1125             nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
1126         }
1127         mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
1128         mNonUIInCallServiceConnections.connect(call);
1129     }
1130 
getDefaultDialerComponent()1131     private InCallServiceInfo getDefaultDialerComponent() {
1132         String packageName = mDefaultDialerCache.getDefaultDialerApplication(
1133                 mCallsManager.getCurrentUserHandle().getIdentifier());
1134         Log.d(this, "Default Dialer package: " + packageName);
1135 
1136         return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
1137     }
1138 
getCarModeComponent()1139     private InCallServiceInfo getCarModeComponent() {
1140         // Seems strange to cast a String to null, but the signatures of getInCallServiceComponent
1141         // differ in the types of the first parameter, and passing in null is inherently ambiguous.
1142         return getInCallServiceComponent((String) null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
1143     }
1144 
getInCallServiceComponent(ComponentName componentName, int type)1145     private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
1146         List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type);
1147         if (list != null && !list.isEmpty()) {
1148             return list.get(0);
1149         } else {
1150             // Last Resort: Try to bind to the ComponentName given directly.
1151             Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
1152                     + componentName +". Trying to bind anyway.");
1153             return new InCallServiceInfo(componentName, false, false, type);
1154         }
1155     }
1156 
getInCallServiceComponent(String packageName, int type)1157     private InCallServiceInfo getInCallServiceComponent(String packageName, int type) {
1158         List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type);
1159         if (list != null && !list.isEmpty()) {
1160             return list.get(0);
1161         }
1162         return null;
1163     }
1164 
getInCallServiceComponents(int type)1165     private List<InCallServiceInfo> getInCallServiceComponents(int type) {
1166         return getInCallServiceComponents(null, null, type);
1167     }
1168 
getInCallServiceComponents(String packageName, int type)1169     private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) {
1170         return getInCallServiceComponents(packageName, null, type);
1171     }
1172 
getInCallServiceComponents(ComponentName componentName, int type)1173     private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
1174             int type) {
1175         return getInCallServiceComponents(null, componentName, type);
1176     }
1177 
getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType)1178     private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
1179             ComponentName componentName, int requestedType) {
1180 
1181         List<InCallServiceInfo> retval = new LinkedList<>();
1182 
1183         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
1184         if (packageName != null) {
1185             serviceIntent.setPackage(packageName);
1186         }
1187         if (componentName != null) {
1188             serviceIntent.setComponent(componentName);
1189         }
1190 
1191         PackageManager packageManager = mContext.getPackageManager();
1192         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
1193                 serviceIntent,
1194                 PackageManager.GET_META_DATA,
1195                 mCallsManager.getCurrentUserHandle().getIdentifier())) {
1196             ServiceInfo serviceInfo = entry.serviceInfo;
1197 
1198             if (serviceInfo != null) {
1199                 boolean isExternalCallsSupported = serviceInfo.metaData != null &&
1200                         serviceInfo.metaData.getBoolean(
1201                                 TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false);
1202                 boolean isSelfManageCallsSupported = serviceInfo.metaData != null &&
1203                         serviceInfo.metaData.getBoolean(
1204                                 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
1205 
1206                 int currentType = getInCallServiceType(entry.serviceInfo, packageManager);
1207                 if (requestedType == 0 || requestedType == currentType) {
1208                     if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
1209                         // We enforce the rule that self-managed calls are not supported by non-ui
1210                         // InCallServices.
1211                         isSelfManageCallsSupported = false;
1212                     }
1213                     retval.add(new InCallServiceInfo(
1214                             new ComponentName(serviceInfo.packageName, serviceInfo.name),
1215                             isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
1216                 }
1217             }
1218         }
1219 
1220         return retval;
1221     }
1222 
shouldUseCarModeUI()1223     private boolean shouldUseCarModeUI() {
1224         return mSystemStateProvider.isCarMode();
1225     }
1226 
1227     /**
1228      * Returns the type of InCallService described by the specified serviceInfo.
1229      */
getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager)1230     private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) {
1231         // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
1232         // enforces that only Telecom can bind to it.
1233         boolean hasServiceBindPermission = serviceInfo.permission != null &&
1234                 serviceInfo.permission.equals(
1235                         Manifest.permission.BIND_INCALL_SERVICE);
1236         if (!hasServiceBindPermission) {
1237             Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " +
1238                     serviceInfo.packageName);
1239             return IN_CALL_SERVICE_TYPE_INVALID;
1240         }
1241 
1242         if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) &&
1243                 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) {
1244             return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
1245         }
1246 
1247         // Check to see if the service is a car-mode UI type by checking that it has the
1248         // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
1249         // car-mode UI metadata.
1250         boolean hasControlInCallPermission = packageManager.checkPermission(
1251                 Manifest.permission.CONTROL_INCALL_EXPERIENCE,
1252                 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
1253         boolean isCarModeUIService = serviceInfo.metaData != null &&
1254                 serviceInfo.metaData.getBoolean(
1255                         TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) &&
1256                 hasControlInCallPermission;
1257         if (isCarModeUIService) {
1258             return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
1259         }
1260 
1261         // Check to see that it is the default dialer package
1262         boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
1263                 mDefaultDialerCache.getDefaultDialerApplication(
1264                     mCallsManager.getCurrentUserHandle().getIdentifier()));
1265         boolean isUIService = serviceInfo.metaData != null &&
1266                 serviceInfo.metaData.getBoolean(
1267                         TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
1268         if (isDefaultDialerPackage && isUIService) {
1269             return IN_CALL_SERVICE_TYPE_DIALER_UI;
1270         }
1271 
1272         // Also allow any in-call service that has the control-experience permission (to ensure
1273         // that it is a system app) and doesn't claim to show any UI.
1274         if (hasControlInCallPermission && !isUIService) {
1275             return IN_CALL_SERVICE_TYPE_NON_UI;
1276         }
1277 
1278         // Anything else that remains, we will not bind to.
1279         Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b",
1280                 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission,
1281                 isCarModeUIService, isUIService);
1282         return IN_CALL_SERVICE_TYPE_INVALID;
1283     }
1284 
adjustServiceBindingsForEmergency()1285     private void adjustServiceBindingsForEmergency() {
1286         // The connected UI is not the system UI, so lets check if we should switch them
1287         // if there exists an emergency number.
1288         if (mCallsManager.hasEmergencyCall()) {
1289             mInCallServiceConnection.setHasEmergency(true);
1290         }
1291     }
1292 
1293     /**
1294      * Persists the {@link IInCallService} instance and starts the communication between
1295      * this class and in-call app by sending the first update to in-call app. This method is
1296      * called after a successful binding connection is established.
1297      *
1298      * @param info Info about the service, including its {@link ComponentName}.
1299      * @param service The {@link IInCallService} implementation.
1300      * @return True if we successfully connected.
1301      */
onConnected(InCallServiceInfo info, IBinder service)1302     private boolean onConnected(InCallServiceInfo info, IBinder service) {
1303         Trace.beginSection("onConnected: " + info.getComponentName());
1304         Log.i(this, "onConnected to %s", info.getComponentName());
1305 
1306         IInCallService inCallService = IInCallService.Stub.asInterface(service);
1307         mInCallServices.put(info, inCallService);
1308 
1309         try {
1310             inCallService.setInCallAdapter(
1311                     new InCallAdapter(
1312                             mCallsManager,
1313                             mCallIdMapper,
1314                             mLock,
1315                             info.getComponentName().getPackageName()));
1316         } catch (RemoteException e) {
1317             Log.e(this, e, "Failed to set the in-call adapter.");
1318             Trace.endSection();
1319             return false;
1320         }
1321 
1322         // Upon successful connection, send the state of the world to the service.
1323         List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
1324         Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
1325                 "calls", calls.size(), info.getComponentName());
1326         int numCallsSent = 0;
1327         for (Call call : calls) {
1328             try {
1329                 if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) ||
1330                         (call.isExternalCall() && !info.isExternalCallsSupported())) {
1331                     continue;
1332                 }
1333 
1334                 // Only send the RTT call if it's a UI in-call service
1335                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
1336 
1337                 // Track the call if we don't already know about it.
1338                 addCall(call);
1339                 numCallsSent += 1;
1340                 inCallService.addCall(ParcelableCallUtils.toParcelableCall(
1341                         call,
1342                         true /* includeVideoProvider */,
1343                         mCallsManager.getPhoneAccountRegistrar(),
1344                         info.isExternalCallsSupported(),
1345                         includeRttCall));
1346             } catch (RemoteException ignored) {
1347             }
1348         }
1349         try {
1350             inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
1351             inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
1352         } catch (RemoteException ignored) {
1353         }
1354         Log.i(this, "%s calls sent to InCallService.", numCallsSent);
1355         Trace.endSection();
1356         return true;
1357     }
1358 
1359     /**
1360      * Cleans up an instance of in-call app after the service has been unbound.
1361      *
1362      * @param disconnectedInfo The {@link InCallServiceInfo} of the service which disconnected.
1363      */
onDisconnected(InCallServiceInfo disconnectedInfo)1364     private void onDisconnected(InCallServiceInfo disconnectedInfo) {
1365         Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
1366 
1367         mInCallServices.remove(disconnectedInfo);
1368     }
1369 
1370     /**
1371      * Informs all {@link InCallService} instances of the updated call information.
1372      *
1373      * @param call The {@link Call}.
1374      */
updateCall(Call call)1375     private void updateCall(Call call) {
1376         updateCall(call, false /* videoProviderChanged */, false);
1377     }
1378 
1379     /**
1380      * Informs all {@link InCallService} instances of the updated call information.
1381      *
1382      * @param call The {@link Call}.
1383      * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
1384      *      otherwise.
1385      * @param rttInfoChanged {@code true} if any information about the RTT session changed,
1386      * {@code false} otherwise.
1387      */
updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged)1388     private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) {
1389         if (!mInCallServices.isEmpty()) {
1390             Log.i(this, "Sending updateCall %s", call);
1391             List<ComponentName> componentsUpdated = new ArrayList<>();
1392             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
1393                 InCallServiceInfo info = entry.getKey();
1394                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
1395                     continue;
1396                 }
1397 
1398                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
1399                     continue;
1400                 }
1401 
1402                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
1403                         call,
1404                         videoProviderChanged /* includeVideoProvider */,
1405                         mCallsManager.getPhoneAccountRegistrar(),
1406                         info.isExternalCallsSupported(),
1407                         rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()));
1408                 ComponentName componentName = info.getComponentName();
1409                 IInCallService inCallService = entry.getValue();
1410                 componentsUpdated.add(componentName);
1411 
1412                 try {
1413                     inCallService.updateCall(parcelableCall);
1414                 } catch (RemoteException ignored) {
1415                 }
1416             }
1417             Log.i(this, "Components updated: %s", componentsUpdated);
1418         }
1419     }
1420 
1421     /**
1422      * Adds the call to the list of calls tracked by the {@link InCallController}.
1423      * @param call The call to add.
1424      */
addCall(Call call)1425     private void addCall(Call call) {
1426         if (mCallIdMapper.getCallId(call) == null) {
1427             mCallIdMapper.addCall(call);
1428             call.addListener(mCallListener);
1429         }
1430     }
1431 
1432     /**
1433      * @return true if we are bound to the UI InCallService and it is connected.
1434      */
isBoundAndConnectedToServices()1435     private boolean isBoundAndConnectedToServices() {
1436         return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
1437     }
1438 
1439     /**
1440      * Dumps the state of the {@link InCallController}.
1441      *
1442      * @param pw The {@code IndentingPrintWriter} to write the state to.
1443      */
dump(IndentingPrintWriter pw)1444     public void dump(IndentingPrintWriter pw) {
1445         pw.println("mInCallServices (InCalls registered):");
1446         pw.increaseIndent();
1447         for (InCallServiceInfo info : mInCallServices.keySet()) {
1448             pw.println(info);
1449         }
1450         pw.decreaseIndent();
1451 
1452         pw.println("ServiceConnections (InCalls bound):");
1453         pw.increaseIndent();
1454         if (mInCallServiceConnection != null) {
1455             mInCallServiceConnection.dump(pw);
1456         }
1457         pw.decreaseIndent();
1458     }
1459 
doesConnectedDialerSupportRinging()1460     public boolean doesConnectedDialerSupportRinging() {
1461         String ringingPackage =  null;
1462         if (mInCallUIComponentName != null) {
1463             ringingPackage = mInCallUIComponentName.getPackageName().trim();
1464         }
1465 
1466         if (TextUtils.isEmpty(ringingPackage)) {
1467             // The current in-call UI returned nothing, so lets use the default dialer.
1468             ringingPackage = DefaultDialerManager.getDefaultDialerApplication(
1469                     mContext, UserHandle.USER_CURRENT);
1470         }
1471         if (TextUtils.isEmpty(ringingPackage)) {
1472             return false;
1473         }
1474 
1475         Intent intent = new Intent(InCallService.SERVICE_INTERFACE)
1476             .setPackage(ringingPackage);
1477         List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
1478                 intent, PackageManager.GET_META_DATA,
1479                 mCallsManager.getCurrentUserHandle().getIdentifier());
1480         if (entries.isEmpty()) {
1481             return false;
1482         }
1483 
1484         ResolveInfo info = entries.get(0);
1485         if (info.serviceInfo == null || info.serviceInfo.metaData == null) {
1486             return false;
1487         }
1488 
1489         return info.serviceInfo.metaData
1490                 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false);
1491     }
1492 
orderCallsWithChildrenFirst(Collection<Call> calls)1493     private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) {
1494         LinkedList<Call> parentCalls = new LinkedList<>();
1495         LinkedList<Call> childCalls = new LinkedList<>();
1496         for (Call call : calls) {
1497             if (call.getChildCalls().size() > 0) {
1498                 parentCalls.add(call);
1499             } else {
1500                 childCalls.add(call);
1501             }
1502         }
1503         childCalls.addAll(parentCalls);
1504         return childCalls;
1505     }
1506 }
1507