• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.telecom;
18 
19 import android.Manifest;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.ServiceConnection;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.content.pm.ServiceInfo;
27 import android.content.res.Resources;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.Trace;
34 import android.os.UserHandle;
35 import android.telecom.CallAudioState;
36 import android.telecom.ConnectionService;
37 import android.telecom.DefaultDialerManager;
38 import android.telecom.InCallService;
39 import android.telecom.ParcelableCall;
40 import android.telecom.TelecomManager;
41 import android.text.TextUtils;
42 import android.util.ArrayMap;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 // TODO: Needed for move to system service: import com.android.internal.R;
46 import com.android.internal.telecom.IInCallService;
47 import com.android.internal.util.IndentingPrintWriter;
48 import com.android.server.telecom.SystemStateProvider.SystemStateListener;
49 import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
50 
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.LinkedList;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Objects;
57 
58 /**
59  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
60  * can send updates to the in-call app. This class is created and owned by CallsManager and retains
61  * a binding to the {@link IInCallService} (implemented by the in-call app).
62  */
63 public final class InCallController extends CallsManagerListenerBase {
64 
65     public class InCallServiceConnection {
66         public class Listener {
onDisconnect(InCallServiceConnection conn)67             public void onDisconnect(InCallServiceConnection conn) {}
68         }
69 
70         protected Listener mListener;
71 
connect(Call call)72         public boolean connect(Call call) { return false; }
disconnect()73         public void disconnect() {}
setHasEmergency(boolean hasEmergency)74         public void setHasEmergency(boolean hasEmergency) {}
setListener(Listener l)75         public void setListener(Listener l) {
76             mListener = l;
77         }
dump(IndentingPrintWriter pw)78         public void dump(IndentingPrintWriter pw) {}
79     }
80 
81     private class InCallServiceInfo {
82         private final ComponentName mComponentName;
83         private boolean mIsExternalCallsSupported;
84         private final int mType;
85 
InCallServiceInfo(ComponentName componentName, boolean isExternalCallsSupported, int type)86         public InCallServiceInfo(ComponentName componentName,
87                 boolean isExternalCallsSupported,
88                 int type) {
89             mComponentName = componentName;
90             mIsExternalCallsSupported = isExternalCallsSupported;
91             mType = type;
92         }
93 
getComponentName()94         public ComponentName getComponentName() {
95             return mComponentName;
96         }
97 
isExternalCallsSupported()98         public boolean isExternalCallsSupported() {
99             return mIsExternalCallsSupported;
100         }
101 
getType()102         public int getType() {
103             return mType;
104         }
105 
106         @Override
equals(Object o)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
hashCode()125         public int hashCode() {
126             return Objects.hash(mComponentName, mIsExternalCallsSupported);
127         }
128 
129         @Override
toString()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 
InCallServiceBindingConnection(InCallServiceInfo info)174         public InCallServiceBindingConnection(InCallServiceInfo info) {
175             mInCallServiceInfo = info;
176         }
177 
178         @Override
connect(Call call)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
disconnect()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
dump(IndentingPrintWriter pw)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 
onConnected(IBinder service)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 
onDisconnected()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 
EmergencyInCallServiceConnection( InCallServiceInfo info, InCallServiceConnection subConnection)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
connect(Call call)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
disconnect()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
setHasEmergency(boolean hasEmergency)314         public void setHasEmergency(boolean hasEmergency) {
315             if (hasEmergency) {
316                 takeControl();
317             }
318         }
319 
320         @Override
onDisconnected()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
dump(IndentingPrintWriter pw)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          */
takeControl()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 
CarSwappingInCallServiceConnection( InCallServiceConnection dialerConnection, InCallServiceConnection carModeConnection)370         public CarSwappingInCallServiceConnection(
371                 InCallServiceConnection dialerConnection,
372                 InCallServiceConnection carModeConnection) {
373             mDialerConnection = dialerConnection;
374             mCarModeConnection = carModeConnection;
375             mCurrentConnection = getCurrentConnection();
376         }
377 
setCarMode(boolean isCarMode)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
connect(Call call)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
disconnect()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
setHasEmergency(boolean hasEmergency)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
dump(IndentingPrintWriter pw)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 
getCurrentConnection()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 
NonUIInCallServiceConnectionCollection( List<InCallServiceBindingConnection> subConnections)454         public NonUIInCallServiceConnectionCollection(
455                 List<InCallServiceBindingConnection> subConnections) {
456             mSubConnections = subConnections;
457         }
458 
459         @Override
connect(Call call)460         public boolean connect(Call call) {
461             for (InCallServiceBindingConnection subConnection : mSubConnections) {
462                 subConnection.connect(call);
463             }
464             return true;
465         }
466 
467         @Override
disconnect()468         public void disconnect() {
469             for (InCallServiceBindingConnection subConnection : mSubConnections) {
470                 subConnection.disconnect();
471             }
472         }
473 
474         @Override
dump(IndentingPrintWriter pw)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 
InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateProvider systemStateProvider, DefaultDialerManagerAdapter defaultDialerAdapter, Timeouts.Adapter timeoutsAdapter)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
onCallAdded(Call call)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
onCallRemoved(Call call)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
onExternalCallChanged(Call call, boolean isExternalCall)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
onCallStateChanged(Call call, int oldState, int newState)757     public void onCallStateChanged(Call call, int oldState, int newState) {
758         updateCall(call);
759     }
760 
761     @Override
onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)762     public void onConnectionServiceChanged(
763             Call call,
764             ConnectionServiceWrapper oldService,
765             ConnectionServiceWrapper newService) {
766         updateCall(call);
767     }
768 
769     @Override
onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)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
onCanAddCallChanged(boolean canAddCall)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 
onPostDialWait(Call call, String remaining)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
onIsConferencedChanged(Call call)810     public void onIsConferencedChanged(Call call) {
811         Log.d(this, "onIsConferencedChanged %s", call);
812         updateCall(call);
813     }
814 
bringToForeground(boolean showDialpad)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 
silenceRinger()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 
notifyConnectionEvent(Call call, String event, Bundle extras)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      */
unbindFromServices()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
bindToServices(Call call)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 
getDefaultDialerComponent()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 
getCarModeComponent()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 
getInCallServiceComponent(ComponentName componentName, int type)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 
getInCallServiceComponent(String packageName, int type)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 
getInCallServiceComponents(int type)949     private List<InCallServiceInfo> getInCallServiceComponents(int type) {
950         return getInCallServiceComponents(null, null, type);
951     }
952 
getInCallServiceComponents(String packageName, int type)953     private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) {
954         return getInCallServiceComponents(packageName, null, type);
955     }
956 
getInCallServiceComponents(ComponentName componentName, int type)957     private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
958             int type) {
959         return getInCallServiceComponents(null, componentName, type);
960     }
961 
getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType)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 
shouldUseCarModeUI()999     private boolean shouldUseCarModeUI() {
1000         return mSystemStateProvider.isCarMode();
1001     }
1002 
1003     /**
1004      * Returns the type of InCallService described by the specified serviceInfo.
1005      */
getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager)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 
adjustServiceBindingsForEmergency()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      */
onConnected(InCallServiceInfo info, IBinder service)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      */
onDisconnected(ComponentName disconnectedComponent)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      */
updateCall(Call call)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      */
updateCall(Call call, boolean videoProviderChanged)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      */
addCall(Call call)1189     private void addCall(Call call) {
1190         if (mCallIdMapper.getCallId(call) == null) {
1191             mCallIdMapper.addCall(call);
1192             call.addListener(mCallListener);
1193         }
1194     }
1195 
isBoundToServices()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      */
dump(IndentingPrintWriter pw)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 
doesConnectedDialerSupportRinging()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 
orderCallsWithChildrenFirst(Collection<Call> calls)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