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