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