1 /* 2 * Copyright (C) 2022 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 17 package com.android.internal.telephony.imsphone; 18 19 import static com.android.internal.telephony.Call.State.DISCONNECTED; 20 import static com.android.internal.telephony.Call.State.IDLE; 21 22 import android.annotation.NonNull; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.internal.telephony.Call; 26 import com.android.internal.telephony.Connection; 27 import com.android.internal.telephony.Phone; 28 import com.android.telephony.Rlog; 29 30 import java.util.ArrayList; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.Comparator; 34 import java.util.HashMap; 35 import java.util.Iterator; 36 import java.util.List; 37 import java.util.Map; 38 39 /** 40 * Contains the state of all IMS calls. 41 */ 42 public class ImsCallInfoTracker { 43 private static final String LOG_TAG = "ImsCallInfoTracker"; 44 private static final boolean DBG = false; 45 46 private final Phone mPhone; 47 private final List<ImsCallInfo> mQueue = new ArrayList<>(); 48 private int mNextIndex = 1; 49 50 private final Map<Connection, ImsCallInfo> mImsCallInfo = new HashMap<>(); 51 ImsCallInfoTracker(Phone phone)52 public ImsCallInfoTracker(Phone phone) { 53 mPhone = phone; 54 } 55 56 /** 57 * Adds a new instance of the IMS call. 58 * 59 * @param c The instance of {@link ImsPhoneConnection}. 60 */ addImsCallStatus(@onNull ImsPhoneConnection c)61 public void addImsCallStatus(@NonNull ImsPhoneConnection c) { 62 if (DBG) Rlog.d(LOG_TAG, "addImsCallStatus"); 63 64 synchronized (mImsCallInfo) { 65 if (mQueue.isEmpty()) { 66 mQueue.add(new ImsCallInfo(mNextIndex++)); 67 } 68 69 Iterator<ImsCallInfo> it = mQueue.iterator(); 70 ImsCallInfo imsCallInfo = it.next(); 71 mQueue.remove(imsCallInfo); 72 73 imsCallInfo.init(c); 74 mImsCallInfo.put(c, imsCallInfo); 75 76 if (!imsCallInfo.shouldIgnoreUpdate()) { 77 notifyImsCallStatus(); 78 } 79 80 if (DBG) dump(); 81 } 82 } 83 84 /** 85 * Updates the list of IMS calls. 86 * 87 * @param c The instance of {@link ImsPhoneConnection}. 88 */ updateImsCallStatus(@onNull ImsPhoneConnection c)89 public void updateImsCallStatus(@NonNull ImsPhoneConnection c) { 90 updateImsCallStatus(c, false, false); 91 } 92 93 /** 94 * Updates the list of IMS calls. 95 * 96 * @param c The instance of {@link ImsPhoneConnection}. 97 * @param holdReceived {@code true} if the remote party held the call. 98 * @param resumeReceived {@code true} if the remote party resumed the call. 99 */ updateImsCallStatus(@onNull ImsPhoneConnection c, boolean holdReceived, boolean resumeReceived)100 public void updateImsCallStatus(@NonNull ImsPhoneConnection c, 101 boolean holdReceived, boolean resumeReceived) { 102 if (DBG) { 103 Rlog.d(LOG_TAG, "updateImsCallStatus holdReceived=" + holdReceived 104 + ", resumeReceived=" + resumeReceived); 105 } 106 107 synchronized (mImsCallInfo) { 108 ImsCallInfo info = mImsCallInfo.get(c); 109 110 if (info == null) { 111 // This happens when the user tries to hangup the call after handover has completed. 112 return; 113 } 114 115 boolean changed = info.update(c, holdReceived, resumeReceived); 116 117 if (changed) notifyImsCallStatus(); 118 119 Call.State state = c.getState(); 120 121 if (DBG) Rlog.d(LOG_TAG, "updateImsCallStatus state=" + state); 122 // Call is disconnected. There are 2 cases in disconnected state: 123 // if silent redial, state == IDLE, otherwise, state == DISCONNECTED. 124 if (state == DISCONNECTED || state == IDLE) { 125 // clear the disconnected call 126 mImsCallInfo.remove(c); 127 info.reset(); 128 if (info.getIndex() < (mNextIndex - 1)) { 129 mQueue.add(info); 130 sort(mQueue); 131 } else { 132 mNextIndex--; 133 } 134 } 135 136 if (DBG) dump(); 137 } 138 } 139 140 /** Clears all orphaned IMS call information. */ clearAllOrphanedConnections()141 public void clearAllOrphanedConnections() { 142 if (DBG) Rlog.d(LOG_TAG, "clearAllOrphanedConnections"); 143 144 Collection<ImsCallInfo> infos = mImsCallInfo.values(); 145 infos.stream().forEach(info -> { info.onDisconnect(); }); 146 notifyImsCallStatus(); 147 clearAllCallInfo(); 148 149 if (DBG) dump(); 150 } 151 152 /** Notifies that SRVCC has completed. */ notifySrvccCompleted()153 public void notifySrvccCompleted() { 154 if (DBG) Rlog.d(LOG_TAG, "notifySrvccCompleted"); 155 156 clearAllCallInfo(); 157 notifyImsCallStatus(); 158 159 if (DBG) dump(); 160 } 161 clearAllCallInfo()162 private void clearAllCallInfo() { 163 try { 164 Collection<ImsCallInfo> infos = mImsCallInfo.values(); 165 infos.stream().forEach(info -> { info.reset(); }); 166 mImsCallInfo.clear(); 167 mQueue.clear(); 168 mNextIndex = 1; 169 } catch (UnsupportedOperationException e) { 170 Rlog.e(LOG_TAG, "e=" + e); 171 } 172 } 173 notifyImsCallStatus()174 private void notifyImsCallStatus() { 175 Collection<ImsCallInfo> infos = mImsCallInfo.values() 176 .stream().filter(info -> !info.shouldIgnoreUpdate()).toList(); 177 ArrayList<ImsCallInfo> imsCallInfo = new ArrayList<ImsCallInfo>(infos); 178 sort(imsCallInfo); 179 mPhone.updateImsCallStatus(imsCallInfo, null); 180 } 181 182 /** 183 * Sorts the list of IMS calls by the call index. 184 * 185 * @param infos The list of IMS calls. 186 */ 187 @VisibleForTesting sort(List<ImsCallInfo> infos)188 public static void sort(List<ImsCallInfo> infos) { 189 Collections.sort(infos, new Comparator<ImsCallInfo>() { 190 @Override 191 public int compare(ImsCallInfo l, ImsCallInfo r) { 192 if (l.getIndex() > r.getIndex()) { 193 return 1; 194 } else if (l.getIndex() < r.getIndex()) { 195 return -1; 196 } 197 return 0; 198 } 199 }); 200 } 201 dump()202 private void dump() { 203 Collection<ImsCallInfo> infos = mImsCallInfo.values(); 204 ArrayList<ImsCallInfo> imsCallInfo = new ArrayList<ImsCallInfo>(infos); 205 sort(imsCallInfo); 206 Rlog.d(LOG_TAG, "imsCallInfos=" + imsCallInfo); 207 } 208 } 209