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.annotation.NonNull;
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.ServiceConnection;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.content.pm.ServiceInfo;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.Looper;
37 import android.os.RemoteException;
38 import android.os.Trace;
39 import android.os.UserHandle;
40 import android.telecom.CallAudioState;
41 import android.telecom.ConnectionService;
42 import android.telecom.InCallService;
43 import android.telecom.Log;
44 import android.telecom.Logging.Runnable;
45 import android.telecom.ParcelableCall;
46 import android.telecom.TelecomManager;
47 import android.text.TextUtils;
48 import android.util.ArrayMap;
49 import android.util.ArraySet;
50 
51 import com.android.internal.annotations.VisibleForTesting;
52 // TODO: Needed for move to system service: import com.android.internal.R;
53 import com.android.internal.telecom.IInCallService;
54 import com.android.internal.util.IndentingPrintWriter;
55 import com.android.server.telecom.SystemStateHelper.SystemStateListener;
56 import com.android.server.telecom.ui.NotificationChannelManager;
57 
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collection;
61 import java.util.LinkedList;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Objects;
65 import java.util.Set;
66 import java.util.concurrent.CompletableFuture;
67 import java.util.concurrent.TimeUnit;
68 import java.util.stream.Collectors;
69 
70 /**
71  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
72  * can send updates to the in-call app. This class is created and owned by CallsManager and retains
73  * a binding to the {@link IInCallService} (implemented by the in-call app).
74  */
75 public class InCallController extends CallsManagerListenerBase {
76     public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
77     public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName();
78 
79     public class InCallServiceConnection {
80         /**
81          * Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
82          * connection to an InCallService.
83          */
84         public static final int CONNECTION_SUCCEEDED = 1;
85         /**
86          * Indicates that a call to {@link #connect(Call)} has failed because of a binding issue.
87          */
88         public static final int CONNECTION_FAILED = 2;
89         /**
90          * Indicates that a call to {@link #connect(Call)} has been skipped because the
91          * IncallService does not support the type of call..
92          */
93         public static final int CONNECTION_NOT_SUPPORTED = 3;
94 
95         public class Listener {
onDisconnect(InCallServiceConnection conn, Call call)96             public void onDisconnect(InCallServiceConnection conn, Call call) {}
97         }
98 
99         protected Listener mListener;
100 
connect(Call call)101         public int connect(Call call) { return CONNECTION_FAILED; }
disconnect()102         public void disconnect() {}
isConnected()103         public boolean isConnected() { return false; }
setHasEmergency(boolean hasEmergency)104         public void setHasEmergency(boolean hasEmergency) {}
setListener(Listener l)105         public void setListener(Listener l) {
106             mListener = l;
107         }
getInfo()108         public InCallServiceInfo getInfo() { return null; }
dump(IndentingPrintWriter pw)109         public void dump(IndentingPrintWriter pw) {}
110         public Call mCall;
111     }
112 
113     private class InCallServiceInfo {
114         private final ComponentName mComponentName;
115         private boolean mIsExternalCallsSupported;
116         private boolean mIsSelfManagedCallsSupported;
117         private final int mType;
118         private long mBindingStartTime;
119         private long mDisconnectTime;
120 
InCallServiceInfo(ComponentName componentName, boolean isExternalCallsSupported, boolean isSelfManageCallsSupported, int type)121         public InCallServiceInfo(ComponentName componentName,
122                 boolean isExternalCallsSupported,
123                 boolean isSelfManageCallsSupported,
124                 int type) {
125             mComponentName = componentName;
126             mIsExternalCallsSupported = isExternalCallsSupported;
127             mIsSelfManagedCallsSupported = isSelfManageCallsSupported;
128             mType = type;
129         }
130 
getComponentName()131         public ComponentName getComponentName() {
132             return mComponentName;
133         }
134 
isExternalCallsSupported()135         public boolean isExternalCallsSupported() {
136             return mIsExternalCallsSupported;
137         }
138 
isSelfManagedCallsSupported()139         public boolean isSelfManagedCallsSupported() {
140             return mIsSelfManagedCallsSupported;
141         }
142 
getType()143         public int getType() {
144             return mType;
145         }
146 
getBindingStartTime()147         public long getBindingStartTime() {
148             return mBindingStartTime;
149         }
150 
getDisconnectTime()151         public long getDisconnectTime() {
152             return mDisconnectTime;
153         }
154 
setBindingStartTime(long bindingStartTime)155         public void setBindingStartTime(long bindingStartTime) {
156             mBindingStartTime = bindingStartTime;
157         }
158 
setDisconnectTime(long disconnectTime)159         public void setDisconnectTime(long disconnectTime) {
160             mDisconnectTime = disconnectTime;
161         }
162 
163         @Override
equals(Object o)164         public boolean equals(Object o) {
165             if (this == o) {
166                 return true;
167             }
168             if (o == null || getClass() != o.getClass()) {
169                 return false;
170             }
171 
172             InCallServiceInfo that = (InCallServiceInfo) o;
173 
174             if (mIsExternalCallsSupported != that.mIsExternalCallsSupported) {
175                 return false;
176             }
177             if (mIsSelfManagedCallsSupported != that.mIsSelfManagedCallsSupported) {
178                 return false;
179             }
180             return mComponentName.equals(that.mComponentName);
181 
182         }
183 
184         @Override
hashCode()185         public int hashCode() {
186             return Objects.hash(mComponentName, mIsExternalCallsSupported,
187                     mIsSelfManagedCallsSupported);
188         }
189 
190         @Override
toString()191         public String toString() {
192             return "[" + mComponentName + " supportsExternal? " + mIsExternalCallsSupported +
193                     " supportsSelfMg?" + mIsSelfManagedCallsSupported + "]";
194         }
195     }
196 
197     private class InCallServiceBindingConnection extends InCallServiceConnection {
198 
199         private final ServiceConnection mServiceConnection = new ServiceConnection() {
200             @Override
201             public void onServiceConnected(ComponentName name, IBinder service) {
202                 Log.startSession("ICSBC.oSC");
203                 synchronized (mLock) {
204                     try {
205                         Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);
206                         mIsBound = true;
207                         if (mIsConnected) {
208                             // Only proceed if we are supposed to be connected.
209                             onConnected(service);
210                         }
211                     } finally {
212                         Log.endSession();
213                     }
214                 }
215             }
216 
217             @Override
218             public void onServiceDisconnected(ComponentName name) {
219                 Log.startSession("ICSBC.oSD");
220                 synchronized (mLock) {
221                     try {
222                         Log.d(this, "onDisconnected: %s", name);
223                         mIsBound = false;
224                         onDisconnected();
225                     } finally {
226                         Log.endSession();
227                     }
228                 }
229             }
230 
231             @Override
232             public void onNullBinding(ComponentName name) {
233                 Log.startSession("ICSBC.oNB");
234                 synchronized (mLock) {
235                     try {
236                         Log.d(this, "onNullBinding: %s", name);
237                         mIsNullBinding = true;
238                         mIsBound = false;
239                         onDisconnected();
240                     } finally {
241                         Log.endSession();
242                     }
243                 }
244             }
245 
246             @Override
247             public void onBindingDied(ComponentName name) {
248                 Log.startSession("ICSBC.oBD");
249                 synchronized (mLock) {
250                     try {
251                         Log.d(this, "onBindingDied: %s", name);
252                         mIsBound = false;
253                         onDisconnected();
254                     } finally {
255                         Log.endSession();
256                     }
257                 }
258             }
259         };
260 
261         private final InCallServiceInfo mInCallServiceInfo;
262         private boolean mIsConnected = false;
263         private boolean mIsBound = false;
264         private boolean mIsNullBinding = false;
265         private NotificationManager mNotificationManager;
266 
InCallServiceBindingConnection(InCallServiceInfo info)267         public InCallServiceBindingConnection(InCallServiceInfo info) {
268             mInCallServiceInfo = info;
269         }
270 
271         @Override
connect(Call call)272         public int connect(Call call) {
273             if (mIsConnected) {
274                 Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request.");
275                 return CONNECTION_SUCCEEDED;
276             }
277 
278             if (call != null && call.isSelfManaged() &&
279                     !mInCallServiceInfo.isSelfManagedCallsSupported()) {
280                 Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls",
281                         mInCallServiceInfo);
282                 mIsConnected = false;
283                 return CONNECTION_NOT_SUPPORTED;
284             }
285 
286             Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
287             intent.setComponent(mInCallServiceInfo.getComponentName());
288             if (call != null && !call.isIncoming() && !call.isExternalCall()){
289                 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
290                         call.getIntentExtras());
291                 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
292                         call.getTargetPhoneAccount());
293             }
294 
295             Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
296             mIsConnected = true;
297             mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
298             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
299                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
300                         | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
301                         UserHandle.CURRENT)) {
302                 Log.w(this, "Failed to connect.");
303                 mIsConnected = false;
304             }
305 
306             if (mIsConnected && call != null) {
307                 mCall = call;
308             }
309             Log.i(this, "mCall: %s, mIsConnected: %s", mCall, mIsConnected);
310 
311             return mIsConnected ? CONNECTION_SUCCEEDED : CONNECTION_FAILED;
312         }
313 
314         @Override
getInfo()315         public InCallServiceInfo getInfo() {
316             return mInCallServiceInfo;
317         }
318 
319         @Override
disconnect()320         public void disconnect() {
321             if (mIsConnected) {
322                 mInCallServiceInfo.setDisconnectTime(mClockProxy.elapsedRealtime());
323                 Log.i(InCallController.this, "ICSBC#disconnect: unbinding after %s ms;"
324                                 + "%s. isCrashed: %s", mInCallServiceInfo.mDisconnectTime
325                                 - mInCallServiceInfo.mBindingStartTime,
326                         mInCallServiceInfo, mIsNullBinding);
327                 String packageName = mInCallServiceInfo.getComponentName().getPackageName();
328                 mContext.unbindService(mServiceConnection);
329                 mIsConnected = false;
330                 if (mIsNullBinding && mInCallServiceInfo.getType() != IN_CALL_SERVICE_TYPE_NON_UI) {
331                     // Non-UI InCallServices are allowed to return null from onBind if they don't
332                     // want to handle calls at the moment, so don't report them to the user as
333                     // crashed.
334                     sendCrashedInCallServiceNotification(packageName);
335                 }
336                 if (mCall != null) {
337                     mCall.getAnalytics().addInCallService(
338                             mInCallServiceInfo.getComponentName().flattenToShortString(),
339                             mInCallServiceInfo.getType(),
340                             mInCallServiceInfo.getDisconnectTime()
341                                     - mInCallServiceInfo.getBindingStartTime(), mIsNullBinding);
342                 }
343             } else {
344                 Log.i(InCallController.this, "ICSBC#disconnect: already disconnected; %s",
345                         mInCallServiceInfo);
346                 Log.addEvent(null, LogUtils.Events.INFO, "Already disconnected, ignoring request.");
347             }
348         }
349 
350         @Override
isConnected()351         public boolean isConnected() {
352             return mIsConnected;
353         }
354 
355         @Override
dump(IndentingPrintWriter pw)356         public void dump(IndentingPrintWriter pw) {
357             pw.print("BindingConnection [");
358             pw.print(mIsConnected ? "" : "not ");
359             pw.print("connected, ");
360             pw.print(mIsBound ? "" : "not ");
361             pw.print("bound, ");
362             pw.print(mInCallServiceInfo);
363             pw.println("\n");
364         }
365 
onConnected(IBinder service)366         protected void onConnected(IBinder service) {
367             boolean shouldRemainConnected =
368                     InCallController.this.onConnected(mInCallServiceInfo, service);
369             if (!shouldRemainConnected) {
370                 // Sometimes we can opt to disconnect for certain reasons, like if the
371                 // InCallService rejected our initialization step, or the calls went away
372                 // in the time it took us to bind to the InCallService. In such cases, we go
373                 // ahead and disconnect ourselves.
374                 disconnect();
375             }
376         }
377 
onDisconnected()378         protected void onDisconnected() {
379             InCallController.this.onDisconnected(mInCallServiceInfo);
380             disconnect();  // Unbind explicitly if we get disconnected.
381             if (mListener != null) {
382                 mListener.onDisconnect(InCallServiceBindingConnection.this, mCall);
383             }
384         }
385     }
386 
387     /**
388      * A version of the InCallServiceBindingConnection that proxies all calls to a secondary
389      * connection until it finds an emergency call, or the other connection dies. When one of those
390      * two things happen, this class instance will take over the connection.
391      */
392     private class EmergencyInCallServiceConnection extends InCallServiceBindingConnection {
393         private boolean mIsProxying = true;
394         private boolean mIsConnected = false;
395         private final InCallServiceConnection mSubConnection;
396 
397         private Listener mSubListener = new Listener() {
398             @Override
399             public void onDisconnect(InCallServiceConnection subConnection, Call call) {
400                 if (subConnection == mSubConnection) {
401                     if (mIsConnected && mIsProxying) {
402                         // At this point we know that we need to be connected to the InCallService
403                         // and we are proxying to the sub connection.  However, the sub-connection
404                         // just died so we need to stop proxying and connect to the system in-call
405                         // service instead.
406                         mIsProxying = false;
407                         connect(call);
408                     }
409                 }
410             }
411         };
412 
EmergencyInCallServiceConnection( InCallServiceInfo info, InCallServiceConnection subConnection)413         public EmergencyInCallServiceConnection(
414                 InCallServiceInfo info, InCallServiceConnection subConnection) {
415 
416             super(info);
417             mSubConnection = subConnection;
418             if (mSubConnection != null) {
419                 mSubConnection.setListener(mSubListener);
420             }
421             mIsProxying = (mSubConnection != null);
422         }
423 
424         @Override
connect(Call call)425         public int connect(Call call) {
426             mIsConnected = true;
427             if (mIsProxying) {
428                 int result = mSubConnection.connect(call);
429                 mIsConnected = result == CONNECTION_SUCCEEDED;
430                 if (result != CONNECTION_FAILED) {
431                     return result;
432                 }
433                 // Could not connect to child, stop proxying.
434                 mIsProxying = false;
435             }
436 
437             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
438                 mCallsManager.getCurrentUserHandle());
439 
440             if (call != null && call.isIncoming()
441                 && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) {
442               // Add the last emergency call time to the call
443               Bundle extras = new Bundle();
444               extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
445                       mEmergencyCallHelper.getLastEmergencyCallTimeMillis());
446               call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
447             }
448 
449             // If we are here, we didn't or could not connect to child. So lets connect ourselves.
450             return super.connect(call);
451         }
452 
453         @Override
disconnect()454         public void disconnect() {
455             Log.i(this, "Disconnecting from InCallService");
456             if (mIsProxying) {
457                 mSubConnection.disconnect();
458             } else {
459                 super.disconnect();
460                 mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
461             }
462             mIsConnected = false;
463         }
464 
465         @Override
setHasEmergency(boolean hasEmergency)466         public void setHasEmergency(boolean hasEmergency) {
467             if (hasEmergency) {
468                 takeControl();
469             }
470         }
471 
472         @Override
getInfo()473         public InCallServiceInfo getInfo() {
474             if (mIsProxying) {
475                 return mSubConnection.getInfo();
476             } else {
477                 return super.getInfo();
478             }
479         }
480         @Override
onDisconnected()481         protected void onDisconnected() {
482             // Save this here because super.onDisconnected() could force us to explicitly
483             // disconnect() as a cleanup step and that sets mIsConnected to false.
484             boolean shouldReconnect = mIsConnected;
485             super.onDisconnected();
486             // We just disconnected.  Check if we are expected to be connected, and reconnect.
487             if (shouldReconnect && !mIsProxying) {
488                 connect(mCall);  // reconnect
489             }
490         }
491 
492         @Override
dump(IndentingPrintWriter pw)493         public void dump(IndentingPrintWriter pw) {
494             pw.print("Emergency ICS Connection [");
495             pw.append(mIsProxying ? "" : "not ").append("proxying, ");
496             pw.append(mIsConnected ? "" : "not ").append("connected]\n");
497             pw.increaseIndent();
498             pw.print("Emergency: ");
499             super.dump(pw);
500             if (mSubConnection != null) {
501                 pw.print("Default-Dialer: ");
502                 mSubConnection.dump(pw);
503             }
504             pw.decreaseIndent();
505         }
506 
507         /**
508          * Forces the connection to take control from it's subConnection.
509          */
takeControl()510         private void takeControl() {
511             if (mIsProxying) {
512                 mIsProxying = false;
513                 if (mIsConnected) {
514                     mSubConnection.disconnect();
515                     super.connect(null);
516                 }
517             }
518         }
519     }
520 
521     /**
522      * A version of InCallServiceConnection which switches UI between two separate sub-instances of
523      * InCallServicesConnections.
524      */
525     private class CarSwappingInCallServiceConnection extends InCallServiceConnection {
526         private final InCallServiceConnection mDialerConnection;
527         private InCallServiceConnection mCarModeConnection;
528         private InCallServiceConnection mCurrentConnection;
529         private boolean mIsCarMode = false;
530         private boolean mIsConnected = false;
531 
CarSwappingInCallServiceConnection( InCallServiceConnection dialerConnection, InCallServiceConnection carModeConnection)532         public CarSwappingInCallServiceConnection(
533                 InCallServiceConnection dialerConnection,
534                 InCallServiceConnection carModeConnection) {
535             mDialerConnection = dialerConnection;
536             mCarModeConnection = carModeConnection;
537             mCurrentConnection = getCurrentConnection();
538         }
539 
540         /**
541          * Called when we move to a state where calls are present on the device.  Chooses the
542          * {@link InCallService} to which we should connect.
543          * @param isCarMode {@code true} if device is in car mode, {@code false} otherwise.
544          */
chooseInitialInCallService(boolean isCarMode)545         public synchronized void chooseInitialInCallService(boolean isCarMode) {
546             Log.i(this, "chooseInitialInCallService: " + mIsCarMode + " => " + isCarMode);
547             if (isCarMode != mIsCarMode) {
548                 mIsCarMode = isCarMode;
549                 InCallServiceConnection newConnection = getCurrentConnection();
550                 if (newConnection != mCurrentConnection) {
551                     if (mIsConnected) {
552                         mCurrentConnection.disconnect();
553                     }
554                     int result = newConnection.connect(null);
555                     mIsConnected = result == CONNECTION_SUCCEEDED;
556                     mCurrentConnection = newConnection;
557                 }
558             }
559         }
560 
561         /**
562          * Invoked when {@link CarModeTracker} has determined that the device is no longer in car
563          * mode (i.e. has no car mode {@link InCallService}).
564          *
565          * Switches back to the default dialer app.
566          */
disableCarMode()567         public synchronized void disableCarMode() {
568             mIsCarMode = false;
569             if (mIsConnected) {
570                 mCurrentConnection.disconnect();
571             }
572 
573             mCurrentConnection = mDialerConnection;
574             int result = mDialerConnection.connect(null);
575             mIsConnected = result == CONNECTION_SUCCEEDED;
576         }
577 
578         /**
579          * Changes the active {@link InCallService} to a car mode app.  Called whenever the device
580          * changes to car mode or the currently active car mode app changes.
581          * @param packageName The package name of the car mode app.
582          */
changeCarModeApp(String packageName)583         public synchronized void changeCarModeApp(String packageName) {
584             Log.i(this, "changeCarModeApp: isCarModeNow=" + mIsCarMode);
585 
586             InCallServiceInfo currentConnectionInfo = mCurrentConnection == null ? null
587                     : mCurrentConnection.getInfo();
588             InCallServiceInfo carModeConnectionInfo =
589                     getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
590 
591             if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) {
592                 Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => "
593                         + carModeConnectionInfo);
594                 if (mIsConnected) {
595                     mCurrentConnection.disconnect();
596                 }
597 
598                 if (carModeConnectionInfo != null) {
599                     // Valid car mode app.
600                     mCarModeConnection = mCurrentConnection =
601                             new InCallServiceBindingConnection(carModeConnectionInfo);
602                     mIsCarMode = true;
603                 } else {
604                     // Invalid car mode app; don't expect this but should handle it gracefully.
605                     mCarModeConnection = null;
606                     mIsCarMode = false;
607                     mCurrentConnection = mDialerConnection;
608                 }
609 
610                 int result = mCurrentConnection.connect(null);
611                 mIsConnected = result == CONNECTION_SUCCEEDED;
612             } else {
613                 Log.i(this, "changeCarModeApp: unchanged; " + currentConnectionInfo + " => "
614                         + carModeConnectionInfo);
615             }
616         }
617 
618         @Override
connect(Call call)619         public int connect(Call call) {
620             if (mIsConnected) {
621                 Log.i(this, "already connected");
622                 return CONNECTION_SUCCEEDED;
623             } else {
624                 int result = mCurrentConnection.connect(call);
625                 if (result != CONNECTION_FAILED) {
626                     mIsConnected = result == CONNECTION_SUCCEEDED;
627                     return result;
628                 }
629             }
630 
631             return CONNECTION_FAILED;
632         }
633 
634         @Override
disconnect()635         public void disconnect() {
636             if (mIsConnected) {
637                 Log.i(InCallController.this, "CSICSC: disconnect %s", mCurrentConnection);
638                 mCurrentConnection.disconnect();
639                 mIsConnected = false;
640             } else {
641                 Log.i(this, "already disconnected");
642             }
643         }
644 
645         @Override
isConnected()646         public boolean isConnected() {
647             return mIsConnected;
648         }
649 
650         @Override
setHasEmergency(boolean hasEmergency)651         public void setHasEmergency(boolean hasEmergency) {
652             if (mDialerConnection != null) {
653                 mDialerConnection.setHasEmergency(hasEmergency);
654             }
655             if (mCarModeConnection != null) {
656                 mCarModeConnection.setHasEmergency(hasEmergency);
657             }
658         }
659 
660         @Override
getInfo()661         public InCallServiceInfo getInfo() {
662             return mCurrentConnection.getInfo();
663         }
664 
665         @Override
dump(IndentingPrintWriter pw)666         public void dump(IndentingPrintWriter pw) {
667             pw.print("Car Swapping ICS [");
668             pw.append(mIsConnected ? "" : "not ").append("connected]\n");
669             pw.increaseIndent();
670             if (mDialerConnection != null) {
671                 pw.print("Dialer: ");
672                 mDialerConnection.dump(pw);
673             }
674             if (mCarModeConnection != null) {
675                 pw.print("Car Mode: ");
676                 mCarModeConnection.dump(pw);
677             }
678         }
679 
getCurrentConnection()680         private InCallServiceConnection getCurrentConnection() {
681             if (mIsCarMode && mCarModeConnection != null) {
682                 return mCarModeConnection;
683             } else {
684                 return mDialerConnection;
685             }
686         }
687     }
688 
689     private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection {
690         private final List<InCallServiceBindingConnection> mSubConnections;
691 
NonUIInCallServiceConnectionCollection( List<InCallServiceBindingConnection> subConnections)692         public NonUIInCallServiceConnectionCollection(
693                 List<InCallServiceBindingConnection> subConnections) {
694             mSubConnections = subConnections;
695         }
696 
697         @Override
connect(Call call)698         public int connect(Call call) {
699             for (InCallServiceBindingConnection subConnection : mSubConnections) {
700                 subConnection.connect(call);
701             }
702             return CONNECTION_SUCCEEDED;
703         }
704 
705         @Override
disconnect()706         public void disconnect() {
707             for (InCallServiceBindingConnection subConnection : mSubConnections) {
708                 if (subConnection.isConnected()) {
709                     subConnection.disconnect();
710                 }
711             }
712         }
713 
714         @Override
isConnected()715         public boolean isConnected() {
716             boolean connected = false;
717             for (InCallServiceBindingConnection subConnection : mSubConnections) {
718                 connected = connected || subConnection.isConnected();
719             }
720             return connected;
721         }
722 
723         @Override
dump(IndentingPrintWriter pw)724         public void dump(IndentingPrintWriter pw) {
725             pw.println("Non-UI Connections:");
726             pw.increaseIndent();
727             for (InCallServiceBindingConnection subConnection : mSubConnections) {
728                 subConnection.dump(pw);
729             }
730             pw.decreaseIndent();
731         }
732 
addConnections(List<InCallServiceBindingConnection> newConnections)733         public void addConnections(List<InCallServiceBindingConnection> newConnections) {
734             // connect() needs to be called with a Call object. Since we're in the middle of any
735             // possible number of calls right now, choose an arbitrary one from the ones that
736             // InCallController is tracking.
737             if (mCallIdMapper.getCalls().isEmpty()) {
738                 Log.w(InCallController.this, "No calls tracked while adding new NonUi incall");
739                 return;
740             }
741             Call callToConnectWith = mCallIdMapper.getCalls().iterator().next();
742             for (InCallServiceBindingConnection newConnection : newConnections) {
743                 newConnection.connect(callToConnectWith);
744             }
745         }
746     }
747 
748     private final Call.Listener mCallListener = new Call.ListenerBase() {
749         @Override
750         public void onConnectionCapabilitiesChanged(Call call) {
751             updateCall(call);
752         }
753 
754         @Override
755         public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
756             updateCall(call, false /* includeVideoProvider */, didRttChange);
757         }
758 
759         @Override
760         public void onCannedSmsResponsesLoaded(Call call) {
761             updateCall(call);
762         }
763 
764         @Override
765         public void onVideoCallProviderChanged(Call call) {
766             updateCall(call, true /* videoProviderChanged */, false);
767         }
768 
769         @Override
770         public void onStatusHintsChanged(Call call) {
771             updateCall(call);
772         }
773 
774         /**
775          * Listens for changes to extras reported by a Telecom {@link Call}.
776          *
777          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
778          * so we will only trigger an update of the call information if the source of the extras
779          * change was a {@link ConnectionService}.
780          *
781          * @param call The call.
782          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
783          *               {@link Call#SOURCE_INCALL_SERVICE}).
784          * @param extras The extras.
785          */
786         @Override
787         public void onExtrasChanged(Call call, int source, Bundle extras) {
788             // Do not inform InCallServices of changes which originated there.
789             if (source == Call.SOURCE_INCALL_SERVICE) {
790                 return;
791             }
792             updateCall(call);
793         }
794 
795         /**
796          * Listens for changes to extras reported by a Telecom {@link Call}.
797          *
798          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
799          * so we will only trigger an update of the call information if the source of the extras
800          * change was a {@link ConnectionService}.
801          *  @param call The call.
802          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
803          *               {@link Call#SOURCE_INCALL_SERVICE}).
804          * @param keys The extra key removed
805          */
806         @Override
807         public void onExtrasRemoved(Call call, int source, List<String> keys) {
808             // Do not inform InCallServices of changes which originated there.
809             if (source == Call.SOURCE_INCALL_SERVICE) {
810                 return;
811             }
812             updateCall(call);
813         }
814 
815         @Override
816         public void onHandleChanged(Call call) {
817             updateCall(call);
818         }
819 
820         @Override
821         public void onCallerDisplayNameChanged(Call call) {
822             updateCall(call);
823         }
824 
825         @Override
826         public void onCallDirectionChanged(Call call) {
827             updateCall(call);
828         }
829 
830         @Override
831         public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
832             updateCall(call);
833         }
834 
835         @Override
836         public void onTargetPhoneAccountChanged(Call call) {
837             updateCall(call);
838         }
839 
840         @Override
841         public void onConferenceableCallsChanged(Call call) {
842             updateCall(call);
843         }
844 
845         @Override
846         public void onConnectionEvent(Call call, String event, Bundle extras) {
847             notifyConnectionEvent(call, event, extras);
848         }
849 
850         @Override
851         public void onHandoverFailed(Call call, int error) {
852             notifyHandoverFailed(call, error);
853         }
854 
855         @Override
856         public void onHandoverComplete(Call call) {
857             notifyHandoverComplete(call);
858         }
859 
860         @Override
861         public void onRttInitiationFailure(Call call, int reason) {
862             notifyRttInitiationFailure(call, reason);
863             updateCall(call, false, true);
864         }
865 
866         @Override
867         public void onRemoteRttRequest(Call call, int requestId) {
868             notifyRemoteRttRequest(call, requestId);
869         }
870     };
871 
872     private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
873         @Override
874         public void onReceive(Context context, Intent intent) {
875             Log.startSession("ICC.pCR");
876             try {
877                 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
878                     synchronized (mLock) {
879                         String changedPackage = intent.getData().getSchemeSpecificPart();
880                         List<InCallServiceBindingConnection> componentsToBind =
881                                 Arrays.stream(intent.getStringArrayExtra(
882                                         Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST))
883                                         .map((className) ->
884                                                 ComponentName.createRelative(changedPackage,
885                                                         className))
886                                         .filter(mKnownNonUiInCallServices::contains)
887                                         .flatMap(componentName -> getInCallServiceComponents(
888                                                 componentName,
889                                                 IN_CALL_SERVICE_TYPE_NON_UI).stream())
890                                         .map(InCallServiceBindingConnection::new)
891                                         .collect(Collectors.toList());
892 
893                         if (mNonUIInCallServiceConnections != null) {
894                             mNonUIInCallServiceConnections.addConnections(componentsToBind);
895                         }
896                     }
897                 }
898             } finally {
899                 Log.endSession();
900             }
901         }
902     };
903 
904     private final SystemStateListener mSystemStateListener =
905             (priority, packageName, isCarMode) -> InCallController.this.handleCarModeChange(
906                     priority, packageName, isCarMode);
907 
908     private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
909     private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
910     private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
911     private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
912     private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
913     private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5;
914 
915     /** The in-call app implementations, see {@link IInCallService}. */
916     private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
917 
918     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
919 
920     private final Context mContext;
921     private final TelecomSystem.SyncRoot mLock;
922     private final CallsManager mCallsManager;
923     private final SystemStateHelper mSystemStateHelper;
924     private final Timeouts.Adapter mTimeoutsAdapter;
925     private final DefaultDialerCache mDefaultDialerCache;
926     private final EmergencyCallHelper mEmergencyCallHelper;
927     private final Handler mHandler = new Handler(Looper.getMainLooper());
928     private CarSwappingInCallServiceConnection mInCallServiceConnection;
929     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
930     private final ClockProxy mClockProxy;
931 
932     // A set of known non-UI in call services on the device, including those that are disabled.
933     // We track this so that we can efficiently bind to them when we're notified that a new
934     // component has been enabled.
935     private Set<ComponentName> mKnownNonUiInCallServices = new ArraySet<>();
936 
937     // Future that's in a completed state unless we're in the middle of binding to a service.
938     // The future will complete with true if binding succeeds, false if it timed out.
939     private CompletableFuture<Boolean> mBindingFuture = CompletableFuture.completedFuture(true);
940 
941     private final CarModeTracker mCarModeTracker;
942 
InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker, ClockProxy clockProxy)943     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
944             SystemStateHelper systemStateHelper,
945             DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
946             EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker,
947             ClockProxy clockProxy) {
948         mContext = context;
949         mLock = lock;
950         mCallsManager = callsManager;
951         mSystemStateHelper = systemStateHelper;
952         mTimeoutsAdapter = timeoutsAdapter;
953         mDefaultDialerCache = defaultDialerCache;
954         mEmergencyCallHelper = emergencyCallHelper;
955         mCarModeTracker = carModeTracker;
956         mSystemStateHelper.addListener(mSystemStateListener);
957         mClockProxy = clockProxy;
958     }
959 
960     @Override
onCallAdded(Call call)961     public void onCallAdded(Call call) {
962         if (!isBoundAndConnectedToServices()) {
963             Log.i(this, "onCallAdded: %s; not bound or connected.", call);
964             // We are not bound, or we're not connected.
965             bindToServices(call);
966         } else {
967             // We are bound, and we are connected.
968             adjustServiceBindingsForEmergency();
969 
970             // This is in case an emergency call is added while there is an existing call.
971             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
972                     mCallsManager.getCurrentUserHandle());
973 
974             Log.i(this, "onCallAdded: %s", call);
975             // Track the call if we don't already know about it.
976             addCall(call);
977 
978             Log.i(this, "mInCallServiceConnection isConnected=%b",
979                     mInCallServiceConnection.isConnected());
980 
981             List<ComponentName> componentsUpdated = new ArrayList<>();
982             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
983                 InCallServiceInfo info = entry.getKey();
984 
985                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
986                     continue;
987                 }
988 
989                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
990                     continue;
991                 }
992 
993                 // Only send the RTT call if it's a UI in-call service
994                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
995 
996                 componentsUpdated.add(info.getComponentName());
997                 IInCallService inCallService = entry.getValue();
998 
999                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
1000                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
1001                         info.isExternalCallsSupported(), includeRttCall,
1002                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
1003                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
1004                 try {
1005                     inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
1006                 } catch (RemoteException ignored) {
1007                 }
1008             }
1009             Log.i(this, "Call added to components: %s", componentsUpdated);
1010         }
1011     }
1012 
1013     @Override
onCallRemoved(Call call)1014     public void onCallRemoved(Call call) {
1015         Log.i(this, "onCallRemoved: %s", call);
1016         if (mCallsManager.getCalls().isEmpty()) {
1017             /** Let's add a 2 second delay before we send unbind to the services to hopefully
1018              *  give them enough time to process all the pending messages.
1019              */
1020             mHandler.postDelayed(new Runnable("ICC.oCR", mLock) {
1021                 @Override
1022                 public void loggedRun() {
1023                     // Check again to make sure there are no active calls.
1024                     if (mCallsManager.getCalls().isEmpty()) {
1025                         unbindFromServices();
1026 
1027                         mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
1028                     }
1029                 }
1030             }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
1031                             mContext.getContentResolver()));
1032         }
1033         call.removeListener(mCallListener);
1034         mCallIdMapper.removeCall(call);
1035     }
1036 
1037     @Override
onExternalCallChanged(Call call, boolean isExternalCall)1038     public void onExternalCallChanged(Call call, boolean isExternalCall) {
1039         Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
1040 
1041         List<ComponentName> componentsUpdated = new ArrayList<>();
1042         if (!isExternalCall) {
1043             // The call was external but it is no longer external.  We must now add it to any
1044             // InCallServices which do not support external calls.
1045             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
1046                 InCallServiceInfo info = entry.getKey();
1047 
1048                 if (info.isExternalCallsSupported()) {
1049                     // For InCallServices which support external calls, the call will have already
1050                     // been added to the connection service, so we do not need to add it again.
1051                     continue;
1052                 }
1053 
1054                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
1055                     continue;
1056                 }
1057 
1058                 componentsUpdated.add(info.getComponentName());
1059                 IInCallService inCallService = entry.getValue();
1060 
1061                 // Only send the RTT call if it's a UI in-call service
1062                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
1063 
1064                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
1065                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
1066                         info.isExternalCallsSupported(), includeRttCall,
1067                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
1068                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
1069                 try {
1070                     inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
1071                 } catch (RemoteException ignored) {
1072                 }
1073             }
1074             Log.i(this, "Previously external call added to components: %s", componentsUpdated);
1075         } else {
1076             // The call was regular but it is now external.  We must now remove it from any
1077             // InCallServices which do not support external calls.
1078             // Remove the call by sending a call update indicating the call was disconnected.
1079             Log.i(this, "Removing external call %s", call);
1080             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
1081                 InCallServiceInfo info = entry.getKey();
1082                 if (info.isExternalCallsSupported()) {
1083                     // For InCallServices which support external calls, we do not need to remove
1084                     // the call.
1085                     continue;
1086                 }
1087 
1088                 componentsUpdated.add(info.getComponentName());
1089                 IInCallService inCallService = entry.getValue();
1090 
1091                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
1092                         call,
1093                         false /* includeVideoProvider */,
1094                         mCallsManager.getPhoneAccountRegistrar(),
1095                         false /* supportsExternalCalls */,
1096                         android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
1097                         false /* includeRttCall */,
1098                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
1099                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI
1100                         );
1101 
1102                 try {
1103                     inCallService.updateCall(
1104                             sanitizeParcelableCallForService(info, parcelableCall));
1105                 } catch (RemoteException ignored) {
1106                 }
1107             }
1108             Log.i(this, "External call removed from components: %s", componentsUpdated);
1109         }
1110     }
1111 
1112     @Override
onCallStateChanged(Call call, int oldState, int newState)1113     public void onCallStateChanged(Call call, int oldState, int newState) {
1114         updateCall(call);
1115     }
1116 
1117     @Override
onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)1118     public void onConnectionServiceChanged(
1119             Call call,
1120             ConnectionServiceWrapper oldService,
1121             ConnectionServiceWrapper newService) {
1122         updateCall(call);
1123     }
1124 
1125     @Override
onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)1126     public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
1127             CallAudioState newCallAudioState) {
1128         if (!mInCallServices.isEmpty()) {
1129             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
1130                     newCallAudioState);
1131             for (IInCallService inCallService : mInCallServices.values()) {
1132                 try {
1133                     inCallService.onCallAudioStateChanged(newCallAudioState);
1134                 } catch (RemoteException ignored) {
1135                 }
1136             }
1137         }
1138     }
1139 
1140     @Override
onCanAddCallChanged(boolean canAddCall)1141     public void onCanAddCallChanged(boolean canAddCall) {
1142         if (!mInCallServices.isEmpty()) {
1143             Log.i(this, "onCanAddCallChanged : %b", canAddCall);
1144             for (IInCallService inCallService : mInCallServices.values()) {
1145                 try {
1146                     inCallService.onCanAddCallChanged(canAddCall);
1147                 } catch (RemoteException ignored) {
1148                 }
1149             }
1150         }
1151     }
1152 
onPostDialWait(Call call, String remaining)1153     void onPostDialWait(Call call, String remaining) {
1154         if (!mInCallServices.isEmpty()) {
1155             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
1156             for (IInCallService inCallService : mInCallServices.values()) {
1157                 try {
1158                     inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
1159                 } catch (RemoteException ignored) {
1160                 }
1161             }
1162         }
1163     }
1164 
1165     @Override
onIsConferencedChanged(Call call)1166     public void onIsConferencedChanged(Call call) {
1167         Log.d(this, "onIsConferencedChanged %s", call);
1168         updateCall(call);
1169     }
1170 
1171     @Override
onConnectionTimeChanged(Call call)1172     public void onConnectionTimeChanged(Call call) {
1173         Log.d(this, "onConnectionTimeChanged %s", call);
1174         updateCall(call);
1175     }
1176 
1177     @Override
onIsVoipAudioModeChanged(Call call)1178     public void onIsVoipAudioModeChanged(Call call) {
1179         Log.d(this, "onIsVoipAudioModeChanged %s", call);
1180         updateCall(call);
1181     }
1182 
1183     @Override
onConferenceStateChanged(Call call, boolean isConference)1184     public void onConferenceStateChanged(Call call, boolean isConference) {
1185         Log.d(this, "onConferenceStateChanged %s ,isConf=%b", call, isConference);
1186         updateCall(call);
1187     }
1188 
1189     @Override
onCdmaConferenceSwap(Call call)1190     public void onCdmaConferenceSwap(Call call) {
1191         Log.d(this, "onCdmaConferenceSwap %s", call);
1192         updateCall(call);
1193     }
1194 
bringToForeground(boolean showDialpad)1195     void bringToForeground(boolean showDialpad) {
1196         if (!mInCallServices.isEmpty()) {
1197             for (IInCallService inCallService : mInCallServices.values()) {
1198                 try {
1199                     inCallService.bringToForeground(showDialpad);
1200                 } catch (RemoteException ignored) {
1201                 }
1202             }
1203         } else {
1204             Log.w(this, "Asking to bring unbound in-call UI to foreground.");
1205         }
1206     }
1207 
silenceRinger()1208     void silenceRinger() {
1209         if (!mInCallServices.isEmpty()) {
1210             for (IInCallService inCallService : mInCallServices.values()) {
1211                 try {
1212                     inCallService.silenceRinger();
1213                 } catch (RemoteException ignored) {
1214                 }
1215             }
1216         }
1217     }
1218 
notifyConnectionEvent(Call call, String event, Bundle extras)1219     private void notifyConnectionEvent(Call call, String event, Bundle extras) {
1220         if (!mInCallServices.isEmpty()) {
1221             for (IInCallService inCallService : mInCallServices.values()) {
1222                 try {
1223                     Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
1224                             (call != null ? call.toString() :"null"),
1225                             (event != null ? event : "null") ,
1226                             (extras != null ? extras.toString() : "null"));
1227                     inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras);
1228                 } catch (RemoteException ignored) {
1229                 }
1230             }
1231         }
1232     }
1233 
notifyRttInitiationFailure(Call call, int reason)1234     private void notifyRttInitiationFailure(Call call, int reason) {
1235         if (!mInCallServices.isEmpty()) {
1236              mInCallServices.entrySet().stream()
1237                     .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
1238                     .forEach((entry) -> {
1239                         try {
1240                             Log.i(this, "notifyRttFailure, call %s, incall %s",
1241                                     call, entry.getKey());
1242                             entry.getValue().onRttInitiationFailure(mCallIdMapper.getCallId(call),
1243                                     reason);
1244                         } catch (RemoteException ignored) {
1245                         }
1246                     });
1247         }
1248     }
1249 
notifyRemoteRttRequest(Call call, int requestId)1250     private void notifyRemoteRttRequest(Call call, int requestId) {
1251         if (!mInCallServices.isEmpty()) {
1252             mInCallServices.entrySet().stream()
1253                     .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
1254                     .forEach((entry) -> {
1255                         try {
1256                             Log.i(this, "notifyRemoteRttRequest, call %s, incall %s",
1257                                     call, entry.getKey());
1258                             entry.getValue().onRttUpgradeRequest(
1259                                     mCallIdMapper.getCallId(call), requestId);
1260                         } catch (RemoteException ignored) {
1261                         }
1262                     });
1263         }
1264     }
1265 
notifyHandoverFailed(Call call, int error)1266     private void notifyHandoverFailed(Call call, int error) {
1267         if (!mInCallServices.isEmpty()) {
1268             for (IInCallService inCallService : mInCallServices.values()) {
1269                 try {
1270                     inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error);
1271                 } catch (RemoteException ignored) {
1272                 }
1273             }
1274         }
1275     }
1276 
notifyHandoverComplete(Call call)1277     private void notifyHandoverComplete(Call call) {
1278         if (!mInCallServices.isEmpty()) {
1279             for (IInCallService inCallService : mInCallServices.values()) {
1280                 try {
1281                     inCallService.onHandoverComplete(mCallIdMapper.getCallId(call));
1282                 } catch (RemoteException ignored) {
1283                 }
1284             }
1285         }
1286     }
1287 
1288     /**
1289      * Unbinds an existing bound connection to the in-call app.
1290      */
unbindFromServices()1291     private void unbindFromServices() {
1292         try {
1293             mContext.unregisterReceiver(mPackageChangedReceiver);
1294         } catch (IllegalArgumentException e) {
1295             // Ignore this -- we may or may not have registered it, but when we bind, we want to
1296             // unregister no matter what.
1297         }
1298         if (mInCallServiceConnection != null) {
1299             mInCallServiceConnection.disconnect();
1300             mInCallServiceConnection = null;
1301         }
1302         if (mNonUIInCallServiceConnections != null) {
1303             mNonUIInCallServiceConnections.disconnect();
1304             mNonUIInCallServiceConnections = null;
1305         }
1306         mInCallServices.clear();
1307     }
1308 
1309     /**
1310      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
1311      * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking.
1312      *
1313      * @param call The newly added call that triggered the binding to the in-call services.
1314      */
1315     @VisibleForTesting
bindToServices(Call call)1316     public void bindToServices(Call call) {
1317         if (mInCallServiceConnection == null) {
1318             InCallServiceConnection dialerInCall = null;
1319             InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
1320             Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
1321             if (defaultDialerComponentInfo != null &&
1322                     !defaultDialerComponentInfo.getComponentName().equals(
1323                             mDefaultDialerCache.getSystemDialerComponent())) {
1324                 dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
1325             }
1326             Log.i(this, "defaultDialer: " + dialerInCall);
1327 
1328             InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
1329                     mDefaultDialerCache.getSystemDialerComponent(), IN_CALL_SERVICE_TYPE_SYSTEM_UI);
1330             EmergencyInCallServiceConnection systemInCall =
1331                     new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
1332             systemInCall.setHasEmergency(mCallsManager.isInEmergencyCall());
1333 
1334             InCallServiceConnection carModeInCall = null;
1335             InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent();
1336             if (carModeComponentInfo != null &&
1337                     !carModeComponentInfo.getComponentName().equals(
1338                             mDefaultDialerCache.getSystemDialerComponent())) {
1339                 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
1340             }
1341 
1342             mInCallServiceConnection =
1343                     new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
1344         }
1345 
1346         mInCallServiceConnection.chooseInitialInCallService(shouldUseCarModeUI());
1347 
1348         // Actually try binding to the UI InCallService.  If the response
1349         if (mInCallServiceConnection.connect(call) ==
1350                 InCallServiceConnection.CONNECTION_SUCCEEDED) {
1351             // Only connect to the non-ui InCallServices if we actually connected to the main UI
1352             // one.
1353             connectToNonUiInCallServices(call);
1354             mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
1355                     mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
1356                             mContext.getContentResolver()),
1357                     TimeUnit.MILLISECONDS);
1358         } else {
1359             Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
1360         }
1361     }
1362 
connectToNonUiInCallServices(Call call)1363     private void connectToNonUiInCallServices(Call call) {
1364         List<InCallServiceInfo> nonUIInCallComponents =
1365                 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
1366         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
1367         for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
1368             nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
1369         }
1370         List<String> callCompanionApps = mCallsManager
1371                 .getRoleManagerAdapter().getCallCompanionApps();
1372         if (callCompanionApps != null && !callCompanionApps.isEmpty()) {
1373             for(String pkg : callCompanionApps) {
1374                 InCallServiceInfo info = getInCallServiceComponent(pkg,
1375                         IN_CALL_SERVICE_TYPE_COMPANION);
1376                 if (info != null) {
1377                     nonUIInCalls.add(new InCallServiceBindingConnection(info));
1378                 }
1379             }
1380         }
1381         mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
1382         mNonUIInCallServiceConnections.connect(call);
1383 
1384         IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
1385         packageChangedFilter.addDataScheme("package");
1386         mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter);
1387     }
1388 
getDefaultDialerComponent()1389     private InCallServiceInfo getDefaultDialerComponent() {
1390         String packageName = mDefaultDialerCache.getDefaultDialerApplication(
1391                 mCallsManager.getCurrentUserHandle().getIdentifier());
1392         String systemPackageName = mDefaultDialerCache.getSystemDialerApplication();
1393         Log.d(this, "Default Dialer package: " + packageName);
1394 
1395         InCallServiceInfo defaultDialerComponent =
1396                 (systemPackageName != null && systemPackageName.equals(packageName))
1397                 ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI)
1398                 : getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
1399         if (packageName != null && defaultDialerComponent == null) {
1400             // The in call service of default phone app is disabled, send notification.
1401             sendCrashedInCallServiceNotification(packageName);
1402         }
1403         return defaultDialerComponent;
1404     }
1405 
getCurrentCarModeComponent()1406     private InCallServiceInfo getCurrentCarModeComponent() {
1407         return getInCallServiceComponent(mCarModeTracker.getCurrentCarModePackage(),
1408                 IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
1409     }
1410 
getInCallServiceComponent(ComponentName componentName, int type)1411     private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
1412         List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type);
1413         if (list != null && !list.isEmpty()) {
1414             return list.get(0);
1415         } else {
1416             // Last Resort: Try to bind to the ComponentName given directly.
1417             Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
1418                     + componentName +". Trying to bind anyway.");
1419             return new InCallServiceInfo(componentName, false, false, type);
1420         }
1421     }
1422 
getInCallServiceComponent(String packageName, int type)1423     private InCallServiceInfo getInCallServiceComponent(String packageName, int type) {
1424         List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type);
1425         if (list != null && !list.isEmpty()) {
1426             return list.get(0);
1427         }
1428         return null;
1429     }
1430 
getInCallServiceComponents(int type)1431     private List<InCallServiceInfo> getInCallServiceComponents(int type) {
1432         return getInCallServiceComponents(null, null, type);
1433     }
1434 
getInCallServiceComponents(String packageName, int type)1435     private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) {
1436         return getInCallServiceComponents(packageName, null, type);
1437     }
1438 
getInCallServiceComponents(ComponentName componentName, int type)1439     private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
1440             int type) {
1441         return getInCallServiceComponents(null, componentName, type);
1442     }
1443 
getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType)1444     private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
1445             ComponentName componentName, int requestedType) {
1446 
1447         List<InCallServiceInfo> retval = new LinkedList<>();
1448 
1449         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
1450         if (packageName != null) {
1451             serviceIntent.setPackage(packageName);
1452         }
1453         if (componentName != null) {
1454             serviceIntent.setComponent(componentName);
1455         }
1456 
1457         PackageManager packageManager = mContext.getPackageManager();
1458         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
1459                 serviceIntent,
1460                 PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS,
1461                 mCallsManager.getCurrentUserHandle().getIdentifier())) {
1462             ServiceInfo serviceInfo = entry.serviceInfo;
1463             if (serviceInfo != null) {
1464                 boolean isExternalCallsSupported = serviceInfo.metaData != null &&
1465                         serviceInfo.metaData.getBoolean(
1466                                 TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false);
1467                 boolean isSelfManageCallsSupported = serviceInfo.metaData != null &&
1468                         serviceInfo.metaData.getBoolean(
1469                                 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
1470 
1471                 int currentType = getInCallServiceType(entry.serviceInfo, packageManager,
1472                         packageName);
1473                 ComponentName foundComponentName =
1474                         new ComponentName(serviceInfo.packageName, serviceInfo.name);
1475                 if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
1476                     mKnownNonUiInCallServices.add(foundComponentName);
1477                 }
1478                 if (serviceInfo.enabled && (requestedType == 0 || requestedType == currentType)) {
1479                     retval.add(new InCallServiceInfo(foundComponentName,
1480                             isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
1481                 }
1482             }
1483         }
1484 
1485         return retval;
1486     }
1487 
shouldUseCarModeUI()1488     private boolean shouldUseCarModeUI() {
1489         return mCarModeTracker.isInCarMode();
1490     }
1491 
1492     /**
1493      * Returns the type of InCallService described by the specified serviceInfo.
1494      */
getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager, String packageName)1495     private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager,
1496             String packageName) {
1497         // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
1498         // enforces that only Telecom can bind to it.
1499         boolean hasServiceBindPermission = serviceInfo.permission != null &&
1500                 serviceInfo.permission.equals(
1501                         Manifest.permission.BIND_INCALL_SERVICE);
1502         if (!hasServiceBindPermission) {
1503             Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " +
1504                     serviceInfo.packageName);
1505             return IN_CALL_SERVICE_TYPE_INVALID;
1506         }
1507 
1508         if (mDefaultDialerCache.getSystemDialerApplication().equals(serviceInfo.packageName) &&
1509                 mDefaultDialerCache.getSystemDialerComponent().getClassName()
1510                         .equals(serviceInfo.name)) {
1511             return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
1512         }
1513 
1514         // Check to see if the service holds permissions or metadata for third party apps.
1515         boolean isUIService = serviceInfo.metaData != null &&
1516                 serviceInfo.metaData.getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_UI);
1517 
1518         // Check to see if the service is a car-mode UI type by checking that it has the
1519         // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
1520         // car-mode UI metadata.
1521         // We check the permission grant on all of the packages contained in the InCallService's
1522         // same UID to see if any of them have been granted the permission.  This accomodates the
1523         // CTS tests, which have some shared UID stuff going on in order to work.  It also still
1524         // obeys the permission model since a single APK typically normally only has a single UID.
1525         String[] uidPackages = packageManager.getPackagesForUid(serviceInfo.applicationInfo.uid);
1526         boolean hasControlInCallPermission = Arrays.stream(uidPackages).anyMatch(
1527                 p -> packageManager.checkPermission(
1528                         Manifest.permission.CONTROL_INCALL_EXPERIENCE,
1529                         p) == PackageManager.PERMISSION_GRANTED);
1530         boolean isCarModeUIService = serviceInfo.metaData != null &&
1531                 serviceInfo.metaData.getBoolean(
1532                         TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false);
1533         if (isCarModeUIService && hasControlInCallPermission) {
1534             return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
1535         }
1536 
1537         // Check to see that it is the default dialer package
1538         boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
1539                 mDefaultDialerCache.getDefaultDialerApplication(
1540                     mCallsManager.getCurrentUserHandle().getIdentifier()));
1541         if (isDefaultDialerPackage && isUIService) {
1542             return IN_CALL_SERVICE_TYPE_DIALER_UI;
1543         }
1544 
1545         // Also allow any in-call service that has the control-experience permission (to ensure
1546         // that it is a system app) and doesn't claim to show any UI.
1547         if (!isUIService && !isCarModeUIService && hasControlInCallPermission) {
1548             return IN_CALL_SERVICE_TYPE_NON_UI;
1549         }
1550 
1551         // Anything else that remains, we will not bind to.
1552         Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b",
1553                 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission,
1554                 isCarModeUIService, isUIService);
1555         return IN_CALL_SERVICE_TYPE_INVALID;
1556     }
1557 
adjustServiceBindingsForEmergency()1558     private void adjustServiceBindingsForEmergency() {
1559         // The connected UI is not the system UI, so lets check if we should switch them
1560         // if there exists an emergency number.
1561         if (mCallsManager.isInEmergencyCall()) {
1562             mInCallServiceConnection.setHasEmergency(true);
1563         }
1564     }
1565 
1566     /**
1567      * Persists the {@link IInCallService} instance and starts the communication between
1568      * this class and in-call app by sending the first update to in-call app. This method is
1569      * called after a successful binding connection is established.
1570      *
1571      * @param info Info about the service, including its {@link ComponentName}.
1572      * @param service The {@link IInCallService} implementation.
1573      * @return True if we successfully connected.
1574      */
onConnected(InCallServiceInfo info, IBinder service)1575     private boolean onConnected(InCallServiceInfo info, IBinder service) {
1576         Log.i(this, "onConnected to %s", info.getComponentName());
1577 
1578         IInCallService inCallService = IInCallService.Stub.asInterface(service);
1579         mInCallServices.put(info, inCallService);
1580 
1581         try {
1582             inCallService.setInCallAdapter(
1583                     new InCallAdapter(
1584                             mCallsManager,
1585                             mCallIdMapper,
1586                             mLock,
1587                             info.getComponentName().getPackageName()));
1588         } catch (RemoteException e) {
1589             Log.e(this, e, "Failed to set the in-call adapter.");
1590             Trace.endSection();
1591             return false;
1592         }
1593 
1594         // Upon successful connection, send the state of the world to the service.
1595         List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
1596         Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
1597                 "calls", calls.size(), info.getComponentName());
1598         int numCallsSent = 0;
1599         for (Call call : calls) {
1600             try {
1601                 if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) ||
1602                         (call.isExternalCall() && !info.isExternalCallsSupported())) {
1603                     continue;
1604                 }
1605 
1606                 // Only send the RTT call if it's a UI in-call service
1607                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
1608 
1609                 // Track the call if we don't already know about it.
1610                 addCall(call);
1611                 numCallsSent += 1;
1612                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
1613                         call,
1614                         true /* includeVideoProvider */,
1615                         mCallsManager.getPhoneAccountRegistrar(),
1616                         info.isExternalCallsSupported(),
1617                         includeRttCall,
1618                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
1619                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
1620                 inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
1621             } catch (RemoteException ignored) {
1622             }
1623         }
1624         try {
1625             inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
1626             inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
1627         } catch (RemoteException ignored) {
1628         }
1629         // Don't complete the binding future for non-ui incalls
1630         if (info.getType() != IN_CALL_SERVICE_TYPE_NON_UI) {
1631             mBindingFuture.complete(true);
1632         }
1633 
1634         Log.i(this, "%s calls sent to InCallService.", numCallsSent);
1635         return true;
1636     }
1637 
1638     /**
1639      * Cleans up an instance of in-call app after the service has been unbound.
1640      *
1641      * @param disconnectedInfo The {@link InCallServiceInfo} of the service which disconnected.
1642      */
onDisconnected(InCallServiceInfo disconnectedInfo)1643     private void onDisconnected(InCallServiceInfo disconnectedInfo) {
1644         Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
1645 
1646         mInCallServices.remove(disconnectedInfo);
1647     }
1648 
1649     /**
1650      * Informs all {@link InCallService} instances of the updated call information.
1651      *
1652      * @param call The {@link Call}.
1653      */
updateCall(Call call)1654     private void updateCall(Call call) {
1655         updateCall(call, false /* videoProviderChanged */, false);
1656     }
1657 
1658     /**
1659      * Informs all {@link InCallService} instances of the updated call information.
1660      *
1661      * @param call The {@link Call}.
1662      * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
1663      *      otherwise.
1664      * @param rttInfoChanged {@code true} if any information about the RTT session changed,
1665      * {@code false} otherwise.
1666      */
updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged)1667     private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) {
1668         if (!mInCallServices.isEmpty()) {
1669             Log.i(this, "Sending updateCall %s", call);
1670             List<ComponentName> componentsUpdated = new ArrayList<>();
1671             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
1672                 InCallServiceInfo info = entry.getKey();
1673                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
1674                     continue;
1675                 }
1676 
1677                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
1678                     continue;
1679                 }
1680 
1681                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
1682                         call,
1683                         videoProviderChanged /* includeVideoProvider */,
1684                         mCallsManager.getPhoneAccountRegistrar(),
1685                         info.isExternalCallsSupported(),
1686                         rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()),
1687                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
1688                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
1689                 ComponentName componentName = info.getComponentName();
1690                 IInCallService inCallService = entry.getValue();
1691                 componentsUpdated.add(componentName);
1692 
1693                 try {
1694                     inCallService.updateCall(
1695                             sanitizeParcelableCallForService(info, parcelableCall));
1696                 } catch (RemoteException ignored) {
1697                 }
1698             }
1699             Log.i(this, "Components updated: %s", componentsUpdated);
1700         }
1701     }
1702 
1703     /**
1704      * Adds the call to the list of calls tracked by the {@link InCallController}.
1705      * @param call The call to add.
1706      */
addCall(Call call)1707     private void addCall(Call call) {
1708         if (mCallIdMapper.getCallId(call) == null) {
1709             mCallIdMapper.addCall(call);
1710             call.addListener(mCallListener);
1711         }
1712     }
1713 
1714     /**
1715      * @return true if we are bound to the UI InCallService and it is connected.
1716      */
isBoundAndConnectedToServices()1717     private boolean isBoundAndConnectedToServices() {
1718         return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
1719     }
1720 
1721     /**
1722      * @return A future that is pending whenever we are in the middle of binding to an
1723      *         incall service.
1724      */
getBindingFuture()1725     public CompletableFuture<Boolean> getBindingFuture() {
1726         return mBindingFuture;
1727     }
1728 
1729     /**
1730      * Dumps the state of the {@link InCallController}.
1731      *
1732      * @param pw The {@code IndentingPrintWriter} to write the state to.
1733      */
dump(IndentingPrintWriter pw)1734     public void dump(IndentingPrintWriter pw) {
1735         pw.println("mInCallServices (InCalls registered):");
1736         pw.increaseIndent();
1737         for (InCallServiceInfo info : mInCallServices.keySet()) {
1738             pw.println(info);
1739         }
1740         pw.decreaseIndent();
1741 
1742         pw.println("ServiceConnections (InCalls bound):");
1743         pw.increaseIndent();
1744         if (mInCallServiceConnection != null) {
1745             mInCallServiceConnection.dump(pw);
1746         }
1747         pw.decreaseIndent();
1748 
1749         mCarModeTracker.dump(pw);
1750     }
1751 
1752     /**
1753      * @return The package name of the UI which is currently bound, or null if none.
1754      */
getConnectedUi()1755     private ComponentName getConnectedUi() {
1756         InCallServiceInfo connectedUi = mInCallServices.keySet().stream().filter(
1757                 i -> i.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI
1758                         || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
1759                 .findAny()
1760                 .orElse(null);
1761         if (connectedUi != null) {
1762             return connectedUi.mComponentName;
1763         }
1764         return null;
1765     }
1766 
doesConnectedDialerSupportRinging()1767     public boolean doesConnectedDialerSupportRinging() {
1768         String ringingPackage =  null;
1769 
1770         ComponentName connectedPackage = getConnectedUi();
1771         if (connectedPackage != null) {
1772             ringingPackage = connectedPackage.getPackageName().trim();
1773             Log.d(this, "doesConnectedDialerSupportRinging: alreadyConnectedPackage=%s",
1774                     ringingPackage);
1775         }
1776 
1777         if (TextUtils.isEmpty(ringingPackage)) {
1778             // The current in-call UI returned nothing, so lets use the default dialer.
1779             ringingPackage = mDefaultDialerCache.getRoleManagerAdapter().getDefaultDialerApp(
1780                     mCallsManager.getCurrentUserHandle().getIdentifier());
1781             if (ringingPackage != null) {
1782                 Log.d(this, "doesConnectedDialerSupportRinging: notCurentlyConnectedPackage=%s",
1783                         ringingPackage);
1784             }
1785         }
1786         if (TextUtils.isEmpty(ringingPackage)) {
1787             Log.w(this, "doesConnectedDialerSupportRinging: no default dialer found; oh no!");
1788             return false;
1789         }
1790 
1791         Intent intent = new Intent(InCallService.SERVICE_INTERFACE)
1792             .setPackage(ringingPackage);
1793         List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
1794                 intent, PackageManager.GET_META_DATA,
1795                 mCallsManager.getCurrentUserHandle().getIdentifier());
1796         if (entries.isEmpty()) {
1797             Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's package info"
1798                     + " <sad trombone>");
1799             return false;
1800         }
1801 
1802         ResolveInfo info = entries.get(0);
1803         if (info.serviceInfo == null || info.serviceInfo.metaData == null) {
1804             Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's metadata"
1805                     + " <even sadder trombone>");
1806             return false;
1807         }
1808 
1809         return info.serviceInfo.metaData
1810                 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false);
1811     }
1812 
orderCallsWithChildrenFirst(Collection<Call> calls)1813     private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) {
1814         LinkedList<Call> parentCalls = new LinkedList<>();
1815         LinkedList<Call> childCalls = new LinkedList<>();
1816         for (Call call : calls) {
1817             if (call.getChildCalls().size() > 0) {
1818                 parentCalls.add(call);
1819             } else {
1820                 childCalls.add(call);
1821             }
1822         }
1823         childCalls.addAll(parentCalls);
1824         return childCalls;
1825     }
1826 
sanitizeParcelableCallForService( InCallServiceInfo info, ParcelableCall parcelableCall)1827     private ParcelableCall sanitizeParcelableCallForService(
1828             InCallServiceInfo info, ParcelableCall parcelableCall) {
1829         ParcelableCall.ParcelableCallBuilder builder =
1830                 ParcelableCall.ParcelableCallBuilder.fromParcelableCall(parcelableCall);
1831         // Check for contacts permission. If it's not there, remove the contactsDisplayName.
1832         PackageManager pm = mContext.getPackageManager();
1833         if (pm.checkPermission(Manifest.permission.READ_CONTACTS,
1834                 info.getComponentName().getPackageName()) != PackageManager.PERMISSION_GRANTED) {
1835             builder.setContactDisplayName(null);
1836         }
1837 
1838         // TODO: move all the other service-specific sanitizations in here
1839         return builder.createParcelableCall();
1840     }
1841 
1842     @VisibleForTesting
getHandler()1843     public Handler getHandler() {
1844         return mHandler;
1845     }
1846 
1847     /**
1848      * Determines if the specified package is a valid car mode {@link InCallService}.
1849      * @param packageName The package name to check.
1850      * @return {@code true} if the package has a valid car mode {@link InCallService} defined,
1851      * {@code false} otherwise.
1852      */
isCarModeInCallService(@onNull String packageName)1853     private boolean isCarModeInCallService(@NonNull String packageName) {
1854         InCallServiceInfo info =
1855                 getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
1856         return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
1857     }
1858 
handleCarModeChange(int priority, String packageName, boolean isCarMode)1859     public void handleCarModeChange(int priority, String packageName, boolean isCarMode) {
1860         Log.i(this, "handleCarModeChange: packageName=%s, priority=%d, isCarMode=%b",
1861                 packageName, priority, isCarMode);
1862         if (!isCarModeInCallService(packageName)) {
1863             Log.i(this, "handleCarModeChange: not a valid InCallService; packageName=%s",
1864                     packageName);
1865             return;
1866         }
1867 
1868         if (isCarMode) {
1869             mCarModeTracker.handleEnterCarMode(priority, packageName);
1870         } else {
1871             mCarModeTracker.handleExitCarMode(priority, packageName);
1872         }
1873 
1874         if (mInCallServiceConnection != null) {
1875             Log.i(this, "handleCarModeChange: car mode apps: %s",
1876                     mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
1877             if (shouldUseCarModeUI()) {
1878                 mInCallServiceConnection.changeCarModeApp(
1879                         mCarModeTracker.getCurrentCarModePackage());
1880             } else {
1881                 mInCallServiceConnection.disableCarMode();
1882             }
1883         }
1884     }
1885 
sendCrashedInCallServiceNotification(String packageName)1886     private void sendCrashedInCallServiceNotification(String packageName) {
1887         PackageManager packageManager = mContext.getPackageManager();
1888         CharSequence appName;
1889         try {
1890             appName = packageManager.getApplicationLabel(
1891                     packageManager.getApplicationInfo(packageName, 0));
1892             if (TextUtils.isEmpty(appName)) {
1893                 appName = packageName;
1894             }
1895         } catch (PackageManager.NameNotFoundException e) {
1896             appName = packageName;
1897         }
1898         NotificationManager notificationManager = (NotificationManager) mContext
1899                 .getSystemService(Context.NOTIFICATION_SERVICE);
1900         Notification.Builder builder = new Notification.Builder(mContext,
1901                 NotificationChannelManager.CHANNEL_ID_IN_CALL_SERVICE_CRASH);
1902         builder.setSmallIcon(R.drawable.ic_phone)
1903                 .setColor(mContext.getResources().getColor(R.color.theme_color))
1904                 .setContentTitle(
1905                         mContext.getText(
1906                                 R.string.notification_crashedInCallService_title))
1907                 .setStyle(new Notification.BigTextStyle()
1908                         .bigText(mContext.getString(
1909                                 R.string.notification_crashedInCallService_body,
1910                                 appName)));
1911         notificationManager.notify(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
1912                 builder.build());
1913     }
1914 }
1915