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