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