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