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