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