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