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