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