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