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