1/*
2 * Copyright (C) 2016 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.net.Uri;
20import android.telecom.Connection;
21import android.telecom.ParcelableCall;
22import android.telecom.TelecomManager;
23
24import java.util.ArrayList;
25import java.util.List;
26
27/**
28 * Utilities dealing with {@link ParcelableCall}.
29 */
30public class ParcelableCallUtils {
31    private static final int CALL_STATE_OVERRIDE_NONE = -1;
32
33    public static class Converter {
34        public ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider,
35                PhoneAccountRegistrar phoneAccountRegistrar) {
36            return ParcelableCallUtils.toParcelableCall(
37                    call, includeVideoProvider, phoneAccountRegistrar, false);
38        }
39    }
40
41    /**
42     * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
43     *
44     * @param call The {@link Call} to parcel.
45     * @param includeVideoProvider {@code true} if the video provider should be parcelled with the
46     *      {@link Call}, {@code false} otherwise.  Since the {@link ParcelableCall#getVideoCall()}
47     *      method creates a {@link VideoCallImpl} instance on access it is important for the
48     *      recipient of the {@link ParcelableCall} to know if the video provider changed.
49     * @param phoneAccountRegistrar The {@link PhoneAccountRegistrar}.
50     * @param supportsExternalCalls Indicates whether the call should be parcelled for an
51     *      {@link InCallService} which supports external calls or not.
52     */
53    public static ParcelableCall toParcelableCall(
54            Call call,
55            boolean includeVideoProvider,
56            PhoneAccountRegistrar phoneAccountRegistrar,
57            boolean supportsExternalCalls) {
58        return toParcelableCall(call, includeVideoProvider, phoneAccountRegistrar,
59                supportsExternalCalls, CALL_STATE_OVERRIDE_NONE /* overrideState */);
60    }
61
62    /**
63     * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
64     *
65     * @param call The {@link Call} to parcel.
66     * @param includeVideoProvider {@code true} if the video provider should be parcelled with the
67     *      {@link Call}, {@code false} otherwise.  Since the {@link ParcelableCall#getVideoCall()}
68     *      method creates a {@link VideoCallImpl} instance on access it is important for the
69     *      recipient of the {@link ParcelableCall} to know if the video provider changed.
70     * @param phoneAccountRegistrar The {@link PhoneAccountRegistrar}.
71     * @param supportsExternalCalls Indicates whether the call should be parcelled for an
72     *      {@link InCallService} which supports external calls or not.
73     * @param overrideState When not {@link #CALL_STATE_OVERRIDE_NONE}, use the provided state as an
74     *      override to whatever is defined in the call.
75     * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
76     */
77    public static ParcelableCall toParcelableCall(
78            Call call,
79            boolean includeVideoProvider,
80            PhoneAccountRegistrar phoneAccountRegistrar,
81            boolean supportsExternalCalls,
82            int overrideState) {
83        int state;
84        if (overrideState == CALL_STATE_OVERRIDE_NONE) {
85            state = getParcelableState(call, supportsExternalCalls);
86        } else {
87            state = overrideState;
88        }
89        int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
90        int properties = convertConnectionToCallProperties(call.getConnectionProperties());
91        int supportedAudioRoutes = call.getSupportedAudioRoutes();
92
93        if (call.isConference()) {
94            properties |= android.telecom.Call.Details.PROPERTY_CONFERENCE;
95        }
96
97        if (call.isWorkCall()) {
98            properties |= android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL;
99        }
100
101        // If this is a single-SIM device, the "default SIM" will always be the only SIM.
102        boolean isDefaultSmsAccount = phoneAccountRegistrar != null &&
103                phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
104        if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
105            capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
106        }
107
108        if (call.isEmergencyCall()) {
109            capabilities = removeCapability(
110                    capabilities, android.telecom.Call.Details.CAPABILITY_MUTE);
111        }
112
113        if (state == android.telecom.Call.STATE_DIALING) {
114            capabilities = removeCapability(capabilities,
115                    android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
116            capabilities = removeCapability(capabilities,
117                    android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
118        }
119
120        String parentCallId = null;
121        Call parentCall = call.getParentCall();
122        if (parentCall != null) {
123            parentCallId = parentCall.getId();
124        }
125
126        long connectTimeMillis = call.getConnectTimeMillis();
127        List<Call> childCalls = call.getChildCalls();
128        List<String> childCallIds = new ArrayList<>();
129        if (!childCalls.isEmpty()) {
130            long childConnectTimeMillis = Long.MAX_VALUE;
131            for (Call child : childCalls) {
132                if (child.getConnectTimeMillis() > 0) {
133                    childConnectTimeMillis = Math.min(child.getConnectTimeMillis(),
134                            childConnectTimeMillis);
135                }
136                childCallIds.add(child.getId());
137            }
138
139            if (childConnectTimeMillis != Long.MAX_VALUE) {
140                connectTimeMillis = childConnectTimeMillis;
141            }
142        }
143
144        Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
145                call.getHandle() : null;
146        String callerDisplayName = call.getCallerDisplayNamePresentation() ==
147                TelecomManager.PRESENTATION_ALLOWED ?  call.getCallerDisplayName() : null;
148
149        List<Call> conferenceableCalls = call.getConferenceableCalls();
150        List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size());
151        for (Call otherCall : conferenceableCalls) {
152            conferenceableCallIds.add(otherCall.getId());
153        }
154
155        return new ParcelableCall(
156                call.getId(),
157                state,
158                call.getDisconnectCause(),
159                call.getCannedSmsResponses(),
160                capabilities,
161                properties,
162                supportedAudioRoutes,
163                connectTimeMillis,
164                handle,
165                call.getHandlePresentation(),
166                callerDisplayName,
167                call.getCallerDisplayNamePresentation(),
168                call.getGatewayInfo(),
169                call.getTargetPhoneAccount(),
170                includeVideoProvider,
171                includeVideoProvider ? call.getVideoProvider() : null,
172                parentCallId,
173                childCallIds,
174                call.getStatusHints(),
175                call.getVideoState(),
176                conferenceableCallIds,
177                call.getIntentExtras(),
178                call.getExtras());
179    }
180
181    private static int getParcelableState(Call call, boolean supportsExternalCalls) {
182        int state = CallState.NEW;
183        switch (call.getState()) {
184            case CallState.ABORTED:
185            case CallState.DISCONNECTED:
186                state = android.telecom.Call.STATE_DISCONNECTED;
187                break;
188            case CallState.ACTIVE:
189                state = android.telecom.Call.STATE_ACTIVE;
190                break;
191            case CallState.CONNECTING:
192                state = android.telecom.Call.STATE_CONNECTING;
193                break;
194            case CallState.DIALING:
195                state = android.telecom.Call.STATE_DIALING;
196                break;
197            case CallState.PULLING:
198                if (supportsExternalCalls) {
199                    // The InCallService supports external calls, so it must handle
200                    // STATE_PULLING_CALL.
201                    state = android.telecom.Call.STATE_PULLING_CALL;
202                } else {
203                    // The InCallService does NOT support external calls, so remap
204                    // STATE_PULLING_CALL to STATE_DIALING.  In essence, pulling a call can be seen
205                    // as a form of dialing, so it is appropriate for InCallServices which do not
206                    // handle external calls.
207                    state = android.telecom.Call.STATE_DIALING;
208                }
209                break;
210            case CallState.DISCONNECTING:
211                state = android.telecom.Call.STATE_DISCONNECTING;
212                break;
213            case CallState.NEW:
214                state = android.telecom.Call.STATE_NEW;
215                break;
216            case CallState.ON_HOLD:
217                state = android.telecom.Call.STATE_HOLDING;
218                break;
219            case CallState.RINGING:
220                state = android.telecom.Call.STATE_RINGING;
221                break;
222            case CallState.SELECT_PHONE_ACCOUNT:
223                state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT;
224                break;
225        }
226
227        // If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead.
228        // Unless we're disconnect*ED*, in which case leave it at that.
229        if (call.isLocallyDisconnecting() &&
230                (state != android.telecom.Call.STATE_DISCONNECTED)) {
231            state = android.telecom.Call.STATE_DISCONNECTING;
232        }
233        return state;
234    }
235
236    private static final int[] CONNECTION_TO_CALL_CAPABILITY = new int[] {
237        Connection.CAPABILITY_HOLD,
238        android.telecom.Call.Details.CAPABILITY_HOLD,
239
240        Connection.CAPABILITY_SUPPORT_HOLD,
241        android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD,
242
243        Connection.CAPABILITY_MERGE_CONFERENCE,
244        android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE,
245
246        Connection.CAPABILITY_SWAP_CONFERENCE,
247        android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE,
248
249        Connection.CAPABILITY_RESPOND_VIA_TEXT,
250        android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT,
251
252        Connection.CAPABILITY_MUTE,
253        android.telecom.Call.Details.CAPABILITY_MUTE,
254
255        Connection.CAPABILITY_MANAGE_CONFERENCE,
256        android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE,
257
258        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
259        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
260
261        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
262        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
263
264        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
265        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
266
267        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
268        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
269
270        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
271        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
272
273        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
274        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
275
276        Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE,
277        android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE,
278
279        Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
280        android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
281
282        Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
283        android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
284
285        Connection.CAPABILITY_CAN_PAUSE_VIDEO,
286        android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO,
287
288        Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION,
289        android.telecom.Call.Details.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION,
290
291        Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
292        android.telecom.Call.Details.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
293
294        Connection.CAPABILITY_CAN_PULL_CALL,
295        android.telecom.Call.Details.CAPABILITY_CAN_PULL_CALL
296    };
297
298    private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
299        int callCapabilities = 0;
300        for (int i = 0; i < CONNECTION_TO_CALL_CAPABILITY.length; i += 2) {
301            if ((CONNECTION_TO_CALL_CAPABILITY[i] & connectionCapabilities) ==
302                    CONNECTION_TO_CALL_CAPABILITY[i]) {
303
304                callCapabilities |= CONNECTION_TO_CALL_CAPABILITY[i + 1];
305            }
306        }
307        return callCapabilities;
308    }
309
310    private static final int[] CONNECTION_TO_CALL_PROPERTIES = new int[] {
311        Connection.PROPERTY_HIGH_DEF_AUDIO,
312        android.telecom.Call.Details.PROPERTY_HIGH_DEF_AUDIO,
313
314        Connection.PROPERTY_WIFI,
315        android.telecom.Call.Details.PROPERTY_WIFI,
316
317        Connection.PROPERTY_GENERIC_CONFERENCE,
318        android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE,
319
320        Connection.PROPERTY_EMERGENCY_CALLBACK_MODE,
321        android.telecom.Call.Details.PROPERTY_EMERGENCY_CALLBACK_MODE,
322
323        Connection.PROPERTY_IS_EXTERNAL_CALL,
324        android.telecom.Call.Details.PROPERTY_IS_EXTERNAL_CALL,
325
326        Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY,
327        android.telecom.Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY
328    };
329
330    private static int convertConnectionToCallProperties(int connectionProperties) {
331        int callProperties = 0;
332        for (int i = 0; i < CONNECTION_TO_CALL_PROPERTIES.length; i += 2) {
333            if ((CONNECTION_TO_CALL_PROPERTIES[i] & connectionProperties) ==
334                    CONNECTION_TO_CALL_PROPERTIES[i]) {
335
336                callProperties |= CONNECTION_TO_CALL_PROPERTIES[i + 1];
337            }
338        }
339        return callProperties;
340    }
341
342    /**
343     * Removes the specified capability from the set of capabilities bits and returns the new set.
344     */
345    private static int removeCapability(int capabilities, int capability) {
346        return capabilities & ~capability;
347    }
348
349    private ParcelableCallUtils() {}
350}
351