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.net.Uri;
     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.AudioState;
     36 import android.telecom.CallAudioState;
     37 import android.telecom.Connection;
     38 import android.telecom.DefaultDialerManager;
     39 import android.telecom.InCallService;
     40 import android.telecom.ParcelableCall;
     41 import android.telecom.TelecomManager;
     42 import android.telecom.VideoCallImpl;
     43 import android.util.ArrayMap;
     44 
     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 
     49 import java.util.ArrayList;
     50 import java.util.Collection;
     51 import java.util.Iterator;
     52 import java.util.List;
     53 import java.util.Map;
     54 import java.util.Objects;
     55 import java.util.concurrent.ConcurrentHashMap;
     56 
     57 /**
     58  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
     59  * can send updates to the in-call app. This class is created and owned by CallsManager and retains
     60  * a binding to the {@link IInCallService} (implemented by the in-call app).
     61  */
     62 public final class InCallController extends CallsManagerListenerBase {
     63     /**
     64      * Used to bind to the in-call app and triggers the start of communication between
     65      * this class and in-call app.
     66      */
     67     private class InCallServiceConnection implements ServiceConnection {
     68         /** {@inheritDoc} */
     69         @Override public void onServiceConnected(ComponentName name, IBinder service) {
     70             Log.d(this, "onServiceConnected: %s", name);
     71             onConnected(name, service);
     72         }
     73 
     74         /** {@inheritDoc} */
     75         @Override public void onServiceDisconnected(ComponentName name) {
     76             Log.d(this, "onDisconnected: %s", name);
     77             onDisconnected(name);
     78         }
     79     }
     80 
     81     private final Call.Listener mCallListener = new Call.ListenerBase() {
     82         @Override
     83         public void onConnectionCapabilitiesChanged(Call call) {
     84             updateCall(call);
     85         }
     86 
     87         @Override
     88         public void onCannedSmsResponsesLoaded(Call call) {
     89             updateCall(call);
     90         }
     91 
     92         @Override
     93         public void onVideoCallProviderChanged(Call call) {
     94             updateCall(call, true /* videoProviderChanged */);
     95         }
     96 
     97         @Override
     98         public void onStatusHintsChanged(Call call) {
     99             updateCall(call);
    100         }
    101 
    102         @Override
    103         public void onExtrasChanged(Call call) {
    104             updateCall(call);
    105         }
    106 
    107         @Override
    108         public void onHandleChanged(Call call) {
    109             updateCall(call);
    110         }
    111 
    112         @Override
    113         public void onCallerDisplayNameChanged(Call call) {
    114             updateCall(call);
    115         }
    116 
    117         @Override
    118         public void onVideoStateChanged(Call call) {
    119             updateCall(call);
    120         }
    121 
    122         @Override
    123         public void onTargetPhoneAccountChanged(Call call) {
    124             updateCall(call);
    125         }
    126 
    127         @Override
    128         public void onConferenceableCallsChanged(Call call) {
    129             updateCall(call);
    130         }
    131     };
    132 
    133     /**
    134      * Maintains a binding connection to the in-call app(s).
    135      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
    136      * load factor before resizing, 1 means we only expect a single thread to
    137      * access the map so make only a single shard
    138      */
    139     private final Map<ComponentName, InCallServiceConnection> mServiceConnections =
    140             new ConcurrentHashMap<ComponentName, InCallServiceConnection>(8, 0.9f, 1);
    141 
    142     /** The in-call app implementations, see {@link IInCallService}. */
    143     private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>();
    144 
    145     /**
    146      * The {@link ComponentName} of the bound In-Call UI Service.
    147      */
    148     private ComponentName mInCallUIComponentName;
    149 
    150     private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
    151 
    152     /** The {@link ComponentName} of the default InCall UI. */
    153     private final ComponentName mSystemInCallComponentName;
    154 
    155     private final Context mContext;
    156     private final TelecomSystem.SyncRoot mLock;
    157     private final CallsManager mCallsManager;
    158 
    159     public InCallController(
    160             Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) {
    161         mContext = context;
    162         mLock = lock;
    163         mCallsManager = callsManager;
    164         Resources resources = mContext.getResources();
    165 
    166         mSystemInCallComponentName = new ComponentName(
    167                 resources.getString(R.string.ui_default_package),
    168                 resources.getString(R.string.incall_default_class));
    169     }
    170 
    171     @Override
    172     public void onCallAdded(Call call) {
    173         if (!isBoundToServices()) {
    174             bindToServices(call);
    175         } else {
    176             adjustServiceBindingsForEmergency();
    177 
    178             Log.i(this, "onCallAdded: %s", call);
    179             // Track the call if we don't already know about it.
    180             addCall(call);
    181 
    182             for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
    183                 ComponentName componentName = entry.getKey();
    184                 IInCallService inCallService = entry.getValue();
    185                 ParcelableCall parcelableCall = toParcelableCall(call,
    186                         true /* includeVideoProvider */);
    187                 try {
    188                     inCallService.addCall(parcelableCall);
    189                 } catch (RemoteException ignored) {
    190                 }
    191             }
    192         }
    193     }
    194 
    195     @Override
    196     public void onCallRemoved(Call call) {
    197         Log.i(this, "onCallRemoved: %s", call);
    198         if (mCallsManager.getCalls().isEmpty()) {
    199             /** Let's add a 2 second delay before we send unbind to the services to hopefully
    200              *  give them enough time to process all the pending messages.
    201              */
    202             Handler handler = new Handler(Looper.getMainLooper());
    203             final Runnable runnableUnbind = new Runnable() {
    204                 @Override
    205                 public void run() {
    206                     synchronized (mLock) {
    207                         // Check again to make sure there are no active calls.
    208                         if (mCallsManager.getCalls().isEmpty()) {
    209                             unbindFromServices();
    210                         }
    211                     }
    212                 }
    213             };
    214             handler.postDelayed(
    215                     runnableUnbind,
    216                     Timeouts.getCallRemoveUnbindInCallServicesDelay(
    217                             mContext.getContentResolver()));
    218         }
    219         call.removeListener(mCallListener);
    220         mCallIdMapper.removeCall(call);
    221     }
    222 
    223     @Override
    224     public void onCallStateChanged(Call call, int oldState, int newState) {
    225         updateCall(call);
    226     }
    227 
    228     @Override
    229     public void onConnectionServiceChanged(
    230             Call call,
    231             ConnectionServiceWrapper oldService,
    232             ConnectionServiceWrapper newService) {
    233         updateCall(call);
    234     }
    235 
    236     @Override
    237     public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
    238             CallAudioState newCallAudioState) {
    239         if (!mInCallServices.isEmpty()) {
    240             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
    241                     newCallAudioState);
    242             for (IInCallService inCallService : mInCallServices.values()) {
    243                 try {
    244                     inCallService.onCallAudioStateChanged(newCallAudioState);
    245                 } catch (RemoteException ignored) {
    246                 }
    247             }
    248         }
    249     }
    250 
    251     @Override
    252     public void onCanAddCallChanged(boolean canAddCall) {
    253         if (!mInCallServices.isEmpty()) {
    254             Log.i(this, "onCanAddCallChanged : %b", canAddCall);
    255             for (IInCallService inCallService : mInCallServices.values()) {
    256                 try {
    257                     inCallService.onCanAddCallChanged(canAddCall);
    258                 } catch (RemoteException ignored) {
    259                 }
    260             }
    261         }
    262     }
    263 
    264     void onPostDialWait(Call call, String remaining) {
    265         if (!mInCallServices.isEmpty()) {
    266             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
    267             for (IInCallService inCallService : mInCallServices.values()) {
    268                 try {
    269                     inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
    270                 } catch (RemoteException ignored) {
    271                 }
    272             }
    273         }
    274     }
    275 
    276     @Override
    277     public void onIsConferencedChanged(Call call) {
    278         Log.d(this, "onIsConferencedChanged %s", call);
    279         updateCall(call);
    280     }
    281 
    282     void bringToForeground(boolean showDialpad) {
    283         if (!mInCallServices.isEmpty()) {
    284             for (IInCallService inCallService : mInCallServices.values()) {
    285                 try {
    286                     inCallService.bringToForeground(showDialpad);
    287                 } catch (RemoteException ignored) {
    288                 }
    289             }
    290         } else {
    291             Log.w(this, "Asking to bring unbound in-call UI to foreground.");
    292         }
    293     }
    294 
    295     /**
    296      * Unbinds an existing bound connection to the in-call app.
    297      */
    298     private void unbindFromServices() {
    299         Iterator<Map.Entry<ComponentName, InCallServiceConnection>> iterator =
    300             mServiceConnections.entrySet().iterator();
    301         while (iterator.hasNext()) {
    302             final Map.Entry<ComponentName, InCallServiceConnection> entry = iterator.next();
    303             Log.i(this, "Unbinding from InCallService %s", entry.getKey());
    304             try {
    305                 mContext.unbindService(entry.getValue());
    306             } catch (Exception e) {
    307                 Log.e(this, e, "Exception while unbinding from InCallService");
    308             }
    309             iterator.remove();
    310         }
    311         mInCallServices.clear();
    312     }
    313 
    314     /**
    315      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
    316      * InCallServices. Method-invoker must check {@link #isBoundToServices()} before invoking.
    317      *
    318      * @param call The newly added call that triggered the binding to the in-call services.
    319      */
    320     private void bindToServices(Call call) {
    321         PackageManager packageManager = mContext.getPackageManager();
    322         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
    323 
    324         List<ComponentName> inCallControlServices = new ArrayList<>();
    325         ComponentName inCallUIService = null;
    326 
    327         for (ResolveInfo entry :
    328                 packageManager.queryIntentServices(serviceIntent, PackageManager.GET_META_DATA)) {
    329             ServiceInfo serviceInfo = entry.serviceInfo;
    330             if (serviceInfo != null) {
    331                 boolean hasServiceBindPermission = serviceInfo.permission != null &&
    332                         serviceInfo.permission.equals(
    333                                 Manifest.permission.BIND_INCALL_SERVICE);
    334                 if (!hasServiceBindPermission) {
    335                     Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission: " +
    336                             serviceInfo.packageName);
    337                     continue;
    338                 }
    339 
    340                 boolean hasControlInCallPermission = packageManager.checkPermission(
    341                         Manifest.permission.CONTROL_INCALL_EXPERIENCE,
    342                         serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
    343                 boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
    344                         DefaultDialerManager.getDefaultDialerApplication(mContext));
    345                 if (!hasControlInCallPermission && !isDefaultDialerPackage) {
    346                     Log.w(this, "Service does not have CONTROL_INCALL_EXPERIENCE permission: %s"
    347                             + " and is not system or default dialer.", serviceInfo.packageName);
    348                     continue;
    349                 }
    350 
    351                 boolean isUIService = serviceInfo.metaData != null &&
    352                         serviceInfo.metaData.getBoolean(
    353                                 TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
    354                 ComponentName componentName = new ComponentName(serviceInfo.packageName,
    355                         serviceInfo.name);
    356                 if (isUIService) {
    357                     // For the main UI service, we always prefer the default dialer.
    358                     if (isDefaultDialerPackage) {
    359                         inCallUIService = componentName;
    360                         Log.i(this, "Found default-dialer's In-Call UI: %s", componentName);
    361                     }
    362                 } else {
    363                     // for non-UI services that have passed our checks, add them to the list of
    364                     // service to bind to.
    365                     inCallControlServices.add(componentName);
    366                 }
    367 
    368             }
    369         }
    370 
    371         // Attempt to bind to the default-dialer InCallService first.
    372         if (inCallUIService != null) {
    373             // skip default dialer if we have an emergency call or if it failed binding.
    374             if (mCallsManager.hasEmergencyCall()) {
    375                 Log.i(this, "Skipping default-dialer because of emergency call");
    376                 inCallUIService = null;
    377             } else if (!bindToInCallService(inCallUIService, call, "def-dialer")) {
    378                 Log.event(call, Log.Events.ERROR_LOG,
    379                         "InCallService UI failed binding: " + inCallUIService);
    380                 inCallUIService = null;
    381             }
    382         }
    383 
    384         if (inCallUIService == null) {
    385             // We failed to connect to the default-dialer service, or none was provided. Switch to
    386             // the system built-in InCallService UI.
    387             inCallUIService = mSystemInCallComponentName;
    388             if (!bindToInCallService(inCallUIService, call, "system")) {
    389                 Log.event(call, Log.Events.ERROR_LOG,
    390                         "InCallService system UI failed binding: " + inCallUIService);
    391             }
    392         }
    393         mInCallUIComponentName = inCallUIService;
    394 
    395         // Bind to the control InCallServices
    396         for (ComponentName componentName : inCallControlServices) {
    397             bindToInCallService(componentName, call, "control");
    398         }
    399     }
    400 
    401     /**
    402      * Binds to the specified InCallService.
    403      */
    404     private boolean bindToInCallService(ComponentName componentName, Call call, String tag) {
    405         if (mInCallServices.containsKey(componentName)) {
    406             Log.i(this, "An InCallService already exists: %s", componentName);
    407             return true;
    408         }
    409 
    410         if (mServiceConnections.containsKey(componentName)) {
    411             Log.w(this, "The service is already bound for this component %s", componentName);
    412             return true;
    413         }
    414 
    415         Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
    416         intent.setComponent(componentName);
    417         if (call != null && !call.isIncoming()){
    418             intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
    419                     call.getIntentExtras());
    420             intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
    421                     call.getTargetPhoneAccount());
    422         }
    423 
    424         Log.i(this, "Attempting to bind to [%s] InCall %s, with %s", tag, componentName, intent);
    425         InCallServiceConnection inCallServiceConnection = new InCallServiceConnection();
    426         if (mContext.bindServiceAsUser(intent, inCallServiceConnection,
    427                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
    428                     UserHandle.CURRENT)) {
    429             mServiceConnections.put(componentName, inCallServiceConnection);
    430             return true;
    431         }
    432 
    433         return false;
    434     }
    435 
    436     private void adjustServiceBindingsForEmergency() {
    437         if (!Objects.equals(mInCallUIComponentName, mSystemInCallComponentName)) {
    438             // The connected UI is not the system UI, so lets check if we should switch them
    439             // if there exists an emergency number.
    440             if (mCallsManager.hasEmergencyCall()) {
    441                 // Lets fake a failure here in order to trigger the switch to the system UI.
    442                 onInCallServiceFailure(mInCallUIComponentName, "emergency adjust");
    443             }
    444         }
    445     }
    446 
    447     /**
    448      * Persists the {@link IInCallService} instance and starts the communication between
    449      * this class and in-call app by sending the first update to in-call app. This method is
    450      * called after a successful binding connection is established.
    451      *
    452      * @param componentName The service {@link ComponentName}.
    453      * @param service The {@link IInCallService} implementation.
    454      */
    455     private void onConnected(ComponentName componentName, IBinder service) {
    456         Trace.beginSection("onConnected: " + componentName);
    457         Log.i(this, "onConnected to %s", componentName);
    458 
    459         IInCallService inCallService = IInCallService.Stub.asInterface(service);
    460         mInCallServices.put(componentName, inCallService);
    461 
    462         try {
    463             inCallService.setInCallAdapter(
    464                     new InCallAdapter(
    465                             mCallsManager,
    466                             mCallIdMapper,
    467                             mLock));
    468         } catch (RemoteException e) {
    469             Log.e(this, e, "Failed to set the in-call adapter.");
    470             Trace.endSection();
    471             onInCallServiceFailure(componentName, "setInCallAdapter");
    472             return;
    473         }
    474 
    475         // Upon successful connection, send the state of the world to the service.
    476         Collection<Call> calls = mCallsManager.getCalls();
    477         if (!calls.isEmpty()) {
    478             Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(),
    479                     componentName);
    480             for (Call call : calls) {
    481                 try {
    482                     // Track the call if we don't already know about it.
    483                     addCall(call);
    484                     inCallService.addCall(toParcelableCall(call, true /* includeVideoProvider */));
    485                 } catch (RemoteException ignored) {
    486                 }
    487             }
    488             onCallAudioStateChanged(
    489                     null,
    490                     mCallsManager.getAudioState());
    491             onCanAddCallChanged(mCallsManager.canAddCall());
    492         } else {
    493             unbindFromServices();
    494         }
    495         Trace.endSection();
    496     }
    497 
    498     /**
    499      * Cleans up an instance of in-call app after the service has been unbound.
    500      *
    501      * @param disconnectedComponent The {@link ComponentName} of the service which disconnected.
    502      */
    503     private void onDisconnected(ComponentName disconnectedComponent) {
    504         Log.i(this, "onDisconnected from %s", disconnectedComponent);
    505 
    506         mInCallServices.remove(disconnectedComponent);
    507         if (mServiceConnections.containsKey(disconnectedComponent)) {
    508             // One of the services that we were bound to has unexpectedly disconnected.
    509             onInCallServiceFailure(disconnectedComponent, "onDisconnect");
    510         }
    511     }
    512 
    513     /**
    514      * Handles non-recoverable failures by the InCallService. This method performs cleanup and
    515      * special handling when the failure is to the UI InCallService.
    516      */
    517     private void onInCallServiceFailure(ComponentName componentName, String tag) {
    518         Log.i(this, "Cleaning up a failed InCallService [%s]: %s", tag, componentName);
    519 
    520         // We always clean up the connections here. Even in the case where we rebind to the UI
    521         // because binding is count based and we could end up double-bound.
    522         mInCallServices.remove(componentName);
    523         InCallServiceConnection serviceConnection = mServiceConnections.remove(componentName);
    524         if (serviceConnection != null) {
    525             // We still need to call unbind even though it disconnected.
    526             mContext.unbindService(serviceConnection);
    527         }
    528 
    529         if (Objects.equals(mInCallUIComponentName, componentName)) {
    530             if (!mCallsManager.hasAnyCalls()) {
    531                 // No calls are left anyway. Lets just disconnect all of them.
    532                 unbindFromServices();
    533                 return;
    534             }
    535 
    536             // Whenever the UI crashes, we automatically revert to the System UI for the
    537             // remainder of the active calls.
    538             mInCallUIComponentName = mSystemInCallComponentName;
    539             bindToInCallService(mInCallUIComponentName, null, "reconnecting");
    540         }
    541     }
    542 
    543     /**
    544      * Informs all {@link InCallService} instances of the updated call information.
    545      *
    546      * @param call The {@link Call}.
    547      */
    548     private void updateCall(Call call) {
    549         updateCall(call, false /* videoProviderChanged */);
    550     }
    551 
    552     /**
    553      * Informs all {@link InCallService} instances of the updated call information.
    554      *
    555      * @param call The {@link Call}.
    556      * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
    557      *      otherwise.
    558      */
    559     private void updateCall(Call call, boolean videoProviderChanged) {
    560         if (!mInCallServices.isEmpty()) {
    561             ParcelableCall parcelableCall = toParcelableCall(call,
    562                     videoProviderChanged /* includeVideoProvider */);
    563             Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall);
    564             List<ComponentName> componentsUpdated = new ArrayList<>();
    565             for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
    566                 ComponentName componentName = entry.getKey();
    567                 IInCallService inCallService = entry.getValue();
    568                 componentsUpdated.add(componentName);
    569                 try {
    570                     inCallService.updateCall(parcelableCall);
    571                 } catch (RemoteException ignored) {
    572                 }
    573             }
    574             Log.i(this, "Components updated: %s", componentsUpdated);
    575         }
    576     }
    577 
    578     /**
    579      * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
    580      *
    581      * @param call The {@link Call} to parcel.
    582      * @param includeVideoProvider {@code true} if the video provider should be parcelled with the
    583      *      {@link Call}, {@code false} otherwise.  Since the {@link ParcelableCall#getVideoCall()}
    584      *      method creates a {@link VideoCallImpl} instance on access it is important for the
    585      *      recipient of the {@link ParcelableCall} to know if the video provider changed.
    586      * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
    587      */
    588     private ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider) {
    589         String callId = mCallIdMapper.getCallId(call);
    590 
    591         int state = getParcelableState(call);
    592         int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
    593         int properties = convertConnectionToCallProperties(call.getConnectionCapabilities());
    594         if (call.isConference()) {
    595             properties |= android.telecom.Call.Details.PROPERTY_CONFERENCE;
    596         }
    597 
    598         // If this is a single-SIM device, the "default SIM" will always be the only SIM.
    599         boolean isDefaultSmsAccount =
    600                 mCallsManager.getPhoneAccountRegistrar()
    601                         .isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
    602         if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
    603             capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
    604         }
    605 
    606         if (call.isEmergencyCall()) {
    607             capabilities = removeCapability(
    608                     capabilities, android.telecom.Call.Details.CAPABILITY_MUTE);
    609         }
    610 
    611         if (state == android.telecom.Call.STATE_DIALING) {
    612             capabilities = removeCapability(capabilities,
    613                     android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
    614             capabilities = removeCapability(capabilities,
    615                     android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
    616         }
    617 
    618         String parentCallId = null;
    619         Call parentCall = call.getParentCall();
    620         if (parentCall != null) {
    621             parentCallId = mCallIdMapper.getCallId(parentCall);
    622         }
    623 
    624         long connectTimeMillis = call.getConnectTimeMillis();
    625         List<Call> childCalls = call.getChildCalls();
    626         List<String> childCallIds = new ArrayList<>();
    627         if (!childCalls.isEmpty()) {
    628             long childConnectTimeMillis = Long.MAX_VALUE;
    629             for (Call child : childCalls) {
    630                 if (child.getConnectTimeMillis() > 0) {
    631                     childConnectTimeMillis = Math.min(child.getConnectTimeMillis(),
    632                             childConnectTimeMillis);
    633                 }
    634                 childCallIds.add(mCallIdMapper.getCallId(child));
    635             }
    636 
    637             if (childConnectTimeMillis != Long.MAX_VALUE) {
    638                 connectTimeMillis = childConnectTimeMillis;
    639             }
    640         }
    641 
    642         Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
    643                 call.getHandle() : null;
    644         String callerDisplayName = call.getCallerDisplayNamePresentation() ==
    645                 TelecomManager.PRESENTATION_ALLOWED ?  call.getCallerDisplayName() : null;
    646 
    647         List<Call> conferenceableCalls = call.getConferenceableCalls();
    648         List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size());
    649         for (Call otherCall : conferenceableCalls) {
    650             String otherId = mCallIdMapper.getCallId(otherCall);
    651             if (otherId != null) {
    652                 conferenceableCallIds.add(otherId);
    653             }
    654         }
    655 
    656         return new ParcelableCall(
    657                 callId,
    658                 state,
    659                 call.getDisconnectCause(),
    660                 call.getCannedSmsResponses(),
    661                 capabilities,
    662                 properties,
    663                 connectTimeMillis,
    664                 handle,
    665                 call.getHandlePresentation(),
    666                 callerDisplayName,
    667                 call.getCallerDisplayNamePresentation(),
    668                 call.getGatewayInfo(),
    669                 call.getTargetPhoneAccount(),
    670                 includeVideoProvider,
    671                 includeVideoProvider ? call.getVideoProvider() : null,
    672                 parentCallId,
    673                 childCallIds,
    674                 call.getStatusHints(),
    675                 call.getVideoState(),
    676                 conferenceableCallIds,
    677                 call.getIntentExtras(),
    678                 call.getExtras());
    679     }
    680 
    681     private static int getParcelableState(Call call) {
    682         int state = CallState.NEW;
    683         switch (call.getState()) {
    684             case CallState.ABORTED:
    685             case CallState.DISCONNECTED:
    686                 state = android.telecom.Call.STATE_DISCONNECTED;
    687                 break;
    688             case CallState.ACTIVE:
    689                 state = android.telecom.Call.STATE_ACTIVE;
    690                 break;
    691             case CallState.CONNECTING:
    692                 state = android.telecom.Call.STATE_CONNECTING;
    693                 break;
    694             case CallState.DIALING:
    695                 state = android.telecom.Call.STATE_DIALING;
    696                 break;
    697             case CallState.DISCONNECTING:
    698                 state = android.telecom.Call.STATE_DISCONNECTING;
    699                 break;
    700             case CallState.NEW:
    701                 state = android.telecom.Call.STATE_NEW;
    702                 break;
    703             case CallState.ON_HOLD:
    704                 state = android.telecom.Call.STATE_HOLDING;
    705                 break;
    706             case CallState.RINGING:
    707                 state = android.telecom.Call.STATE_RINGING;
    708                 break;
    709             case CallState.SELECT_PHONE_ACCOUNT:
    710                 state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT;
    711                 break;
    712         }
    713 
    714         // If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead.
    715         // Unless we're disconnect*ED*, in which case leave it at that.
    716         if (call.isLocallyDisconnecting() &&
    717                 (state != android.telecom.Call.STATE_DISCONNECTED)) {
    718             state = android.telecom.Call.STATE_DISCONNECTING;
    719         }
    720         return state;
    721     }
    722 
    723     private static final int[] CONNECTION_TO_CALL_CAPABILITY = new int[] {
    724         Connection.CAPABILITY_HOLD,
    725         android.telecom.Call.Details.CAPABILITY_HOLD,
    726 
    727         Connection.CAPABILITY_SUPPORT_HOLD,
    728         android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD,
    729 
    730         Connection.CAPABILITY_MERGE_CONFERENCE,
    731         android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE,
    732 
    733         Connection.CAPABILITY_SWAP_CONFERENCE,
    734         android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE,
    735 
    736         Connection.CAPABILITY_RESPOND_VIA_TEXT,
    737         android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT,
    738 
    739         Connection.CAPABILITY_MUTE,
    740         android.telecom.Call.Details.CAPABILITY_MUTE,
    741 
    742         Connection.CAPABILITY_MANAGE_CONFERENCE,
    743         android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE,
    744 
    745         Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
    746         android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
    747 
    748         Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
    749         android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
    750 
    751         Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
    752         android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
    753 
    754         Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
    755         android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
    756 
    757         Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
    758         android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
    759 
    760         Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
    761         android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
    762 
    763         Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE,
    764         android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE,
    765 
    766         Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
    767         android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
    768 
    769         Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
    770         android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
    771 
    772         Connection.CAPABILITY_CAN_PAUSE_VIDEO,
    773         android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO
    774     };
    775 
    776     private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
    777         int callCapabilities = 0;
    778         for (int i = 0; i < CONNECTION_TO_CALL_CAPABILITY.length; i += 2) {
    779             if ((CONNECTION_TO_CALL_CAPABILITY[i] & connectionCapabilities) != 0) {
    780                 callCapabilities |= CONNECTION_TO_CALL_CAPABILITY[i + 1];
    781             }
    782         }
    783         return callCapabilities;
    784     }
    785 
    786     private static final int[] CONNECTION_TO_CALL_PROPERTIES = new int[] {
    787         Connection.CAPABILITY_HIGH_DEF_AUDIO,
    788         android.telecom.Call.Details.PROPERTY_HIGH_DEF_AUDIO,
    789 
    790         Connection.CAPABILITY_WIFI,
    791         android.telecom.Call.Details.PROPERTY_WIFI,
    792 
    793         Connection.CAPABILITY_GENERIC_CONFERENCE,
    794         android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE,
    795 
    796         Connection.CAPABILITY_SHOW_CALLBACK_NUMBER,
    797         android.telecom.Call.Details.PROPERTY_EMERGENCY_CALLBACK_MODE,
    798     };
    799 
    800     private static int convertConnectionToCallProperties(int connectionCapabilities) {
    801         int callProperties = 0;
    802         for (int i = 0; i < CONNECTION_TO_CALL_PROPERTIES.length; i += 2) {
    803             if ((CONNECTION_TO_CALL_PROPERTIES[i] & connectionCapabilities) != 0) {
    804                 callProperties |= CONNECTION_TO_CALL_PROPERTIES[i + 1];
    805             }
    806         }
    807         return callProperties;
    808     }
    809 
    810     /**
    811      * Adds the call to the list of calls tracked by the {@link InCallController}.
    812      * @param call The call to add.
    813      */
    814     private void addCall(Call call) {
    815         if (mCallIdMapper.getCallId(call) == null) {
    816             mCallIdMapper.addCall(call);
    817             call.addListener(mCallListener);
    818         }
    819     }
    820 
    821     private boolean isBoundToServices() {
    822         return !mInCallServices.isEmpty();
    823     }
    824 
    825     /**
    826      * Removes the specified capability from the set of capabilities bits and returns the new set.
    827      */
    828     private static int removeCapability(int capabilities, int capability) {
    829         return capabilities & ~capability;
    830     }
    831 
    832     /**
    833      * Dumps the state of the {@link InCallController}.
    834      *
    835      * @param pw The {@code IndentingPrintWriter} to write the state to.
    836      */
    837     public void dump(IndentingPrintWriter pw) {
    838         pw.println("mInCallServices (InCalls registered):");
    839         pw.increaseIndent();
    840         for (ComponentName componentName : mInCallServices.keySet()) {
    841             pw.println(componentName);
    842         }
    843         pw.decreaseIndent();
    844 
    845         pw.println("mServiceConnections (InCalls bound):");
    846         pw.increaseIndent();
    847         for (ComponentName componentName : mServiceConnections.keySet()) {
    848             pw.println(componentName);
    849         }
    850         pw.decreaseIndent();
    851     }
    852 }
    853