ImsExternalCallTracker.java revision fec523bdc5d2b6dd90212c8076bf34bcc2ab08b2
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.internal.telephony.imsphone;
18
19import com.android.ims.ImsExternalCallState;
20import com.android.internal.telephony.Call;
21import com.android.internal.telephony.Connection;
22import com.android.internal.telephony.Phone;
23
24import android.os.Bundle;
25import android.telecom.PhoneAccountHandle;
26import android.util.ArrayMap;
27import android.util.Log;
28
29import java.util.ArrayList;
30import java.util.Iterator;
31import java.util.List;
32import java.util.Map;
33
34/**
35 * Responsible for tracking external calls known to the system.
36 */
37public class ImsExternalCallTracker {
38
39    public final static String TAG = "ImsExternalCallTracker";
40
41    /**
42     * Extra key used when informing telecom of a new external call using the
43     * {@link android.telecom.TelecomManager#addNewUnknownCall(PhoneAccountHandle, Bundle)} API.
44     * Used to ensure that when Telecom requests the {@link android.telecom.ConnectionService} to
45     * create the connection for the unknown call that we can determine which
46     * {@link ImsExternalConnection} in {@link #mExternalConnections} is the one being requested.
47     */
48    public final static String EXTRA_IMS_EXTERNAL_CALL_ID =
49            "android.telephony.ImsExternalCallTracker.extra.EXTERNAL_CALL_ID";
50
51    /**
52     * Contains a list of the external connections known by the ImsPhoneCallTracker.  These are
53     * connections which originated from a dialog event package and reside on another device.
54     * Used in multi-endpoint (VoLTE for internet connected endpoints) scenarios.
55     */
56    private Map<Integer, ImsExternalConnection> mExternalConnections =
57            new ArrayMap<Integer, ImsExternalConnection>();
58
59    private ImsPhone mPhone;
60
61    public ImsExternalCallTracker(ImsPhone phone) {
62        mPhone = phone;
63    }
64
65    /**
66     * Called when the IMS stack receives a new dialog event package.  Triggers the creation and
67     * update of {@link ImsExternalConnection}s to represent the dialogs in the dialog event
68     * package data.
69     *
70     * @param externalCallStates the {@link ImsExternalCallState} information for the dialog event
71     *                           package.
72     */
73    public void refreshExternalCallState(List<ImsExternalCallState> externalCallStates) {
74        Log.d(TAG, "refreshExternalCallState: depSize = " + externalCallStates.size());
75
76        // Check to see if any call Ids are no longer present in the external call state.  If they
77        // are, the calls are terminated and should be removed.
78        Iterator<Map.Entry<Integer, ImsExternalConnection>> connectionIterator =
79                mExternalConnections.entrySet().iterator();
80        boolean wasCallRemoved = false;
81        while (connectionIterator.hasNext()) {
82            Map.Entry<Integer, ImsExternalConnection> entry = connectionIterator.next();
83            int callId = entry.getKey().intValue();
84
85            if (!containsCallId(externalCallStates, callId)) {
86                ImsExternalConnection externalConnection = entry.getValue();
87                externalConnection.setTerminated();
88                connectionIterator.remove();
89                wasCallRemoved = true;
90            }
91        }
92        // If one or more calls were removed, trigger a notification that will cause the
93        // TelephonyConnection instancse to refresh their state with Telecom.
94        if (wasCallRemoved) {
95            mPhone.notifyPreciseCallStateChanged();
96        }
97
98        // Check for new calls, and updates to existing ones.
99        for (ImsExternalCallState callState : externalCallStates) {
100            if (!mExternalConnections.containsKey(callState.getCallId())) {
101                Log.d(TAG, "refreshExternalCallState: got = " + callState);
102                // If there is a new entry and it is already terminated, don't bother adding it to
103                // telecom.
104                if (callState.getCallState() != ImsExternalCallState.CALL_STATE_CONFIRMED) {
105                    continue;
106                }
107                createExternalConnection(callState);
108            } else{
109                updateExistingConnection(mExternalConnections.get(callState.getCallId()),
110                        callState);
111            }
112        }
113    }
114
115    /**
116     * Finds an external connection given a call Id.
117     *
118     * @param callId The call Id.
119     * @return The {@link Connection}, or {@code null} if no match found.
120     */
121    public Connection getConnectionById(int callId) {
122        return mExternalConnections.get(callId);
123    }
124
125    /**
126     * Given an {@link ImsExternalCallState} instance obtained from a dialog event package,
127     * creates a new instance of {@link ImsExternalConnection} to represent the connection, and
128     * initiates the addition of the new call to Telecom as an unknown call.
129     *
130     * @param state External call state from a dialog event package.
131     */
132    private void createExternalConnection(ImsExternalCallState state) {
133        Log.i(TAG, "createExternalConnection");
134
135        ImsExternalConnection connection = new ImsExternalConnection(mPhone,
136                state.getCallId(), /* Dialog event package call id */
137                state.getAddress().getSchemeSpecificPart() /* phone number */,
138                state.isCallPullable());
139
140        // Add to list of tracked connections.
141        mExternalConnections.put(connection.getCallId(), connection);
142
143        // Note: The notification of unknown connection is ultimately handled by
144        // PstnIncomingCallNotifier#addNewUnknownCall.  That method will ensure that an extra is set
145        // containing the ImsExternalConnection#mCallId so that we have a means of reconciling which
146        // unknown call was added.
147        mPhone.notifyUnknownConnection(connection);
148    }
149
150    /**
151     * Given an existing {@link ImsExternalConnection}, applies any changes found found in a
152     * {@link ImsExternalCallState} instance received from a dialog event package to the connection.
153     *
154     * @param connection The connection to apply changes to.
155     * @param state The new dialog state for the connection.
156     */
157    private void updateExistingConnection(ImsExternalConnection connection,
158            ImsExternalCallState state) {
159        Call.State existingState = connection.getState();
160        Call.State newState = state.getCallState() == ImsExternalCallState.CALL_STATE_CONFIRMED ?
161                Call.State.ACTIVE : Call.State.DISCONNECTED;
162
163        if (existingState != newState) {
164            if (newState == Call.State.ACTIVE) {
165                connection.setActive();
166            } else {
167                connection.setTerminated();
168                mExternalConnections.remove(connection);
169                mPhone.notifyPreciseCallStateChanged();
170            }
171        }
172
173        connection.setIsPullable(state.isCallPullable());
174    }
175
176    /**
177     * Determines if a list of call states obtained from a dialog event package contacts an existing
178     * call Id.
179     *
180     * @param externalCallStates The dialog event package state information.
181     * @param callId The call Id.
182     * @return {@code true} if the state information contains the call Id, {@code false} otherwise.
183     */
184    private boolean containsCallId(List<ImsExternalCallState> externalCallStates, int callId) {
185        for (ImsExternalCallState state : externalCallStates) {
186            if (state.getCallId() == callId) {
187                return true;
188            }
189        }
190
191        return false;
192    }
193}
194