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.update(c);
74             mImsCallInfo.put(c, imsCallInfo);
75 
76             notifyImsCallStatus();
77 
78             if (DBG) dump();
79         }
80     }
81 
82     /**
83      * Updates the list of IMS calls.
84      *
85      * @param c The instance of {@link ImsPhoneConnection}.
86      */
updateImsCallStatus(@onNull ImsPhoneConnection c)87     public void updateImsCallStatus(@NonNull ImsPhoneConnection c) {
88         updateImsCallStatus(c, false, false);
89     }
90 
91     /**
92      * Updates the list of IMS calls.
93      *
94      * @param c The instance of {@link ImsPhoneConnection}.
95      * @param holdReceived {@code true} if the remote party held the call.
96      * @param resumeReceived {@code true} if the remote party resumed the call.
97      */
updateImsCallStatus(@onNull ImsPhoneConnection c, boolean holdReceived, boolean resumeReceived)98     public void updateImsCallStatus(@NonNull ImsPhoneConnection c,
99             boolean holdReceived, boolean resumeReceived) {
100         if (DBG) {
101             Rlog.d(LOG_TAG, "updateImsCallStatus holdReceived=" + holdReceived
102                     + ", resumeReceived=" + resumeReceived);
103         }
104 
105         synchronized (mImsCallInfo) {
106             ImsCallInfo info = mImsCallInfo.get(c);
107 
108             if (info == null) {
109                 // This happens when the user tries to hangup the call after handover has completed.
110                 return;
111             }
112 
113             boolean changed = info.update(c, holdReceived, resumeReceived);
114 
115             if (changed) notifyImsCallStatus();
116 
117             Call.State state = c.getState();
118 
119             if (DBG) Rlog.d(LOG_TAG, "updateImsCallStatus state=" + state);
120             // Call is disconnected. There are 2 cases in disconnected state:
121             // if silent redial, state == IDLE, otherwise, state == DISCONNECTED.
122             if (state == DISCONNECTED || state == IDLE) {
123                 // clear the disconnected call
124                 mImsCallInfo.remove(c);
125                 info.reset();
126                 if (info.getIndex() < (mNextIndex - 1)) {
127                     mQueue.add(info);
128                     sort(mQueue);
129                 } else {
130                     mNextIndex--;
131                 }
132             }
133 
134             if (DBG) dump();
135         }
136     }
137 
138     /** Clears all orphaned IMS call information. */
clearAllOrphanedConnections()139     public void clearAllOrphanedConnections() {
140         if (DBG) Rlog.d(LOG_TAG, "clearAllOrphanedConnections");
141 
142         Collection<ImsCallInfo> infos = mImsCallInfo.values();
143         infos.stream().forEach(info -> { info.onDisconnect(); });
144         notifyImsCallStatus();
145         clearAllCallInfo();
146 
147         if (DBG) dump();
148     }
149 
150     /** Notifies that SRVCC has completed. */
notifySrvccCompleted()151     public void notifySrvccCompleted() {
152         if (DBG) Rlog.d(LOG_TAG, "notifySrvccCompleted");
153 
154         clearAllCallInfo();
155         notifyImsCallStatus();
156 
157         if (DBG) dump();
158     }
159 
clearAllCallInfo()160     private void clearAllCallInfo() {
161         try {
162             Collection<ImsCallInfo> infos = mImsCallInfo.values();
163             infos.stream().forEach(info -> { info.reset(); });
164             mImsCallInfo.clear();
165             mQueue.clear();
166             mNextIndex = 1;
167         } catch (UnsupportedOperationException e) {
168             Rlog.e(LOG_TAG, "e=" + e);
169         }
170     }
171 
notifyImsCallStatus()172     private void notifyImsCallStatus() {
173         Collection<ImsCallInfo> infos = mImsCallInfo.values();
174         ArrayList<ImsCallInfo> imsCallInfo = new ArrayList<ImsCallInfo>(infos);
175         sort(imsCallInfo);
176         mPhone.updateImsCallStatus(imsCallInfo, null);
177     }
178 
179     /**
180      * Sorts the list of IMS calls by the call index.
181      *
182      * @param infos The list of IMS calls.
183      */
184     @VisibleForTesting
sort(List<ImsCallInfo> infos)185     public static void sort(List<ImsCallInfo> infos) {
186         Collections.sort(infos, new Comparator<ImsCallInfo>() {
187             @Override
188             public int compare(ImsCallInfo l, ImsCallInfo r) {
189                 if (l.getIndex() > r.getIndex()) {
190                     return 1;
191                 } else if (l.getIndex() < r.getIndex()) {
192                     return -1;
193                 }
194                 return 0;
195             }
196         });
197     }
198 
dump()199     private void dump() {
200         Collection<ImsCallInfo> infos = mImsCallInfo.values();
201         ArrayList<ImsCallInfo> imsCallInfo = new ArrayList<ImsCallInfo>(infos);
202         sort(imsCallInfo);
203         Rlog.d(LOG_TAG, "imsCallInfos=" + imsCallInfo);
204     }
205 }
206