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