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 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 
    120         public InCallServiceBindingConnection(ComponentName componentName) {
    121             mComponentName = componentName;
    122         }
    123 
    124         @Override
    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
    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
    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 
    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 
    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 
    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
    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
    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
    253         public void setHasEmergency(boolean hasEmergency) {
    254             if (hasEmergency) {
    255                 takeControl();
    256             }
    257         }
    258 
    259         @Override
    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
    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          */
    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 
    309         public CarSwappingInCallServiceConnection(
    310                 InCallServiceConnection dialerConnection,
    311                 InCallServiceConnection carModeConnection) {
    312             mDialerConnection = dialerConnection;
    313             mCarModeConnection = carModeConnection;
    314             mCurrentConnection = getCurrentConnection();
    315         }
    316 
    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
    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
    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
    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
    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 
    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 
    393         public NonUIInCallServiceConnectionCollection(
    394                 List<InCallServiceBindingConnection> subConnections) {
    395             mSubConnections = subConnections;
    396         }
    397 
    398         @Override
    399         public boolean connect(Call call) {
    400             for (InCallServiceBindingConnection subConnection : mSubConnections) {
    401                 subConnection.connect(call);
    402             }
    403             return true;
    404         }
    405 
    406         @Override
    407         public void disconnect() {
    408             for (InCallServiceBindingConnection subConnection : mSubConnections) {
    409                 subConnection.disconnect();
    410             }
    411         }
    412 
    413         @Override
    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 
    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
    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
    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
    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
    632     public void onCallStateChanged(Call call, int oldState, int newState) {
    633         updateCall(call);
    634     }
    635 
    636     @Override
    637     public void onConnectionServiceChanged(
    638             Call call,
    639             ConnectionServiceWrapper oldService,
    640             ConnectionServiceWrapper newService) {
    641         updateCall(call);
    642     }
    643 
    644     @Override
    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
    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 
    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
    685     public void onIsConferencedChanged(Call call) {
    686         Log.d(this, "onIsConferencedChanged %s", call);
    687         updateCall(call);
    688     }
    689 
    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 
    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 
    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      */
    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
    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 
    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 
    789     private ComponentName getCarModeComponent() {
    790         return getInCallServiceComponent(null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
    791     }
    792 
    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 
    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 
    826     private boolean shouldUseCarModeUI() {
    827         return mSystemStateProvider.isCarMode();
    828     }
    829 
    830     /**
    831      * Returns the type of InCallService described by the specified serviceInfo.
    832      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
   1006     private void addCall(Call call) {
   1007         if (mCallIdMapper.getCallId(call) == null) {
   1008             mCallIdMapper.addCall(call);
   1009             call.addListener(mCallListener);
   1010         }
   1011     }
   1012 
   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      */
   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 
   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 
   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