1 /*
2  * Copyright (C) 2006 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;
18 
19 import android.annotation.NonNull;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.Context;
22 import android.os.AsyncResult;
23 import android.os.Build;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.os.PersistableBundle;
27 import android.telephony.CarrierConfigManager;
28 import android.telephony.ServiceState;
29 import android.telephony.TelephonyManager;
30 import android.text.TextUtils;
31 
32 import com.android.internal.telephony.flags.FeatureFlags;
33 
34 import java.io.FileDescriptor;
35 import java.io.PrintWriter;
36 import java.util.ArrayList;
37 
38 
39 /**
40  * {@hide}
41  */
42 public abstract class CallTracker extends Handler {
43 
44     private static final boolean DBG_POLL = false;
45 
46     //***** Constants
47 
48     static final int POLL_DELAY_MSEC = 250;
49 
50     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
51     protected int mPendingOperations;
52     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
53     protected boolean mNeedsPoll;
54     protected Message mLastRelevantPoll;
55     protected ArrayList<Connection> mHandoverConnections = new ArrayList<Connection>();
56 
57     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
58     public CommandsInterface mCi;
59 
60     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
61     protected boolean mNumberConverted = false;
62 
63     protected final @NonNull FeatureFlags mFeatureFlags;
64 
65     private final int VALID_COMPARE_LENGTH   = 3;
66 
67     //***** Events
68 
69     protected static final int EVENT_POLL_CALLS_RESULT             = 1;
70     protected static final int EVENT_CALL_STATE_CHANGE             = 2;
71     protected static final int EVENT_REPOLL_AFTER_DELAY            = 3;
72     protected static final int EVENT_OPERATION_COMPLETE            = 4;
73     protected static final int EVENT_GET_LAST_CALL_FAIL_CAUSE      = 5;
74 
75     protected static final int EVENT_SWITCH_RESULT                 = 8;
76     protected static final int EVENT_RADIO_AVAILABLE               = 9;
77     protected static final int EVENT_RADIO_NOT_AVAILABLE           = 10;
78     protected static final int EVENT_CONFERENCE_RESULT             = 11;
79     protected static final int EVENT_SEPARATE_RESULT               = 12;
80     protected static final int EVENT_ECT_RESULT                    = 13;
81     protected static final int EVENT_EXIT_ECM_RESPONSE_CDMA        = 14;
82     protected static final int EVENT_CALL_WAITING_INFO_CDMA        = 15;
83     protected static final int EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA = 16;
84     protected static final int EVENT_THREE_WAY_DIAL_BLANK_FLASH    = 20;
85 
86     @UnsupportedAppUsage
CallTracker(FeatureFlags featureFlags)87     public CallTracker(FeatureFlags featureFlags) {
88         mFeatureFlags = featureFlags;
89     }
90 
pollCallsWhenSafe()91     protected void pollCallsWhenSafe() {
92         mNeedsPoll = true;
93 
94         if (checkNoOperationsPending()) {
95             mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
96             mCi.getCurrentCalls(mLastRelevantPoll);
97         }
98     }
99 
100     protected void
pollCallsAfterDelay()101     pollCallsAfterDelay() {
102         if (mFeatureFlags.preventInvocationRepeatOfRilCallWhenDeviceDoesNotSupportVoice()) {
103             if (!mCi.getHalVersion(TelephonyManager.HAL_SERVICE_VOICE)
104                     .greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
105                 log("Skip polling because HAL_SERVICE_VOICE < RADIO_HAL_VERSION_1.4");
106                 return;
107             }
108         }
109 
110         Message msg = obtainMessage();
111 
112         msg.what = EVENT_REPOLL_AFTER_DELAY;
113         sendMessageDelayed(msg, POLL_DELAY_MSEC);
114     }
115 
116     protected boolean
isCommandExceptionRadioNotAvailable(Throwable e)117     isCommandExceptionRadioNotAvailable(Throwable e) {
118         return e != null && e instanceof CommandException
119                 && ((CommandException)e).getCommandError()
120                         == CommandException.Error.RADIO_NOT_AVAILABLE;
121     }
122 
handlePollCalls(AsyncResult ar)123     protected abstract void handlePollCalls(AsyncResult ar);
124 
getPhone()125     protected abstract Phone getPhone();
126 
getHoConnection(DriverCall dc)127     protected Connection getHoConnection(DriverCall dc) {
128         for (Connection hoConn : mHandoverConnections) {
129             log("getHoConnection - compare number: hoConn= " + hoConn.toString());
130             if (hoConn.getAddress() != null && hoConn.getAddress().contains(dc.number)) {
131                 log("getHoConnection: Handover connection match found = " + hoConn.toString());
132                 return hoConn;
133             }
134         }
135         for (Connection hoConn : mHandoverConnections) {
136             log("getHoConnection: compare state hoConn= " + hoConn.toString());
137             if (hoConn.getStateBeforeHandover() == Call.stateFromDCState(dc.state)) {
138                 log("getHoConnection: Handover connection match found = " + hoConn.toString());
139                 return hoConn;
140             }
141         }
142         return null;
143     }
144 
notifySrvccState(Call.SrvccState state, ArrayList<Connection> c)145     protected void notifySrvccState(Call.SrvccState state, ArrayList<Connection> c) {
146         if (state == Call.SrvccState.STARTED && c != null) {
147             // SRVCC started. Prepare handover connections list
148             mHandoverConnections.addAll(c);
149         } else if (state != Call.SrvccState.COMPLETED) {
150             // SRVCC FAILED/CANCELED. Clear the handover connections list
151             // Individual connections will be removed from the list in handlePollCalls()
152             mHandoverConnections.clear();
153         }
154         log("notifySrvccState: state=" + state.name() + ", mHandoverConnections= "
155                 + mHandoverConnections.toString());
156     }
157 
handleRadioAvailable()158     protected void handleRadioAvailable() {
159         pollCallsWhenSafe();
160     }
161 
162     /**
163      * Obtain a complete message that indicates that this operation
164      * does not require polling of getCurrentCalls(). However, if other
165      * operations that do need getCurrentCalls() are pending or are
166      * scheduled while this operation is pending, the invocation
167      * of getCurrentCalls() will be postponed until this
168      * operation is also complete.
169      */
170     protected Message
obtainNoPollCompleteMessage(int what)171     obtainNoPollCompleteMessage(int what) {
172         mPendingOperations++;
173         mLastRelevantPoll = null;
174         return obtainMessage(what);
175     }
176 
177     /**
178      * @return true if we're idle or there's a call to getCurrentCalls() pending
179      * but nothing else
180      */
181     private boolean
checkNoOperationsPending()182     checkNoOperationsPending() {
183         if (DBG_POLL) log("checkNoOperationsPending: pendingOperations=" +
184                 mPendingOperations);
185         return mPendingOperations == 0;
186     }
187 
convertNumberIfNecessary(Phone phone, String dialNumber)188     protected String convertNumberIfNecessary(Phone phone, String dialNumber) {
189         if (dialNumber == null) {
190             return dialNumber;
191         }
192         String[] convertMaps = null;
193         CarrierConfigManager configManager = (CarrierConfigManager)
194                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
195         PersistableBundle bundle = configManager.getConfigForSubId(phone.getSubId());
196         if (bundle != null) {
197             convertMaps = (shouldPerformInternationalNumberRemapping(phone, bundle))
198                     ? bundle.getStringArray(CarrierConfigManager
199                             .KEY_INTERNATIONAL_ROAMING_DIAL_STRING_REPLACE_STRING_ARRAY)
200                     : bundle.getStringArray(CarrierConfigManager
201                             .KEY_DIAL_STRING_REPLACE_STRING_ARRAY);
202         }
203         if (convertMaps == null) {
204             // By default no replacement is necessary
205             log("convertNumberIfNecessary convertMaps is null");
206             return dialNumber;
207         }
208 
209         log("convertNumberIfNecessary Roaming"
210             + " convertMaps.length " + convertMaps.length
211             + " dialNumber.length() " + dialNumber.length());
212 
213         if (convertMaps.length < 1 || dialNumber.length() < VALID_COMPARE_LENGTH) {
214             return dialNumber;
215         }
216 
217         String[] entry;
218         String outNumber = "";
219         for(String convertMap : convertMaps) {
220             log("convertNumberIfNecessary: " + convertMap);
221             // entry format is  "dialStringToReplace:dialStringReplacement"
222             entry = convertMap.split(":");
223             if (entry != null && entry.length > 1) {
224                 String dsToReplace = entry[0];
225                 String dsReplacement = entry[1];
226                 if (!TextUtils.isEmpty(dsToReplace) && dialNumber.equals(dsToReplace)) {
227                     // Needs to be converted
228                     if (!TextUtils.isEmpty(dsReplacement) && dsReplacement.endsWith("MDN")) {
229                         String mdn = phone.getLine1Number();
230                         if (!TextUtils.isEmpty(mdn)) {
231                             if (mdn.startsWith("+")) {
232                                 outNumber = mdn;
233                             } else {
234                                 outNumber = dsReplacement.substring(0, dsReplacement.length() -3)
235                                         + mdn;
236                             }
237                         }
238                     } else {
239                         outNumber = dsReplacement;
240                     }
241                     break;
242                 }
243             }
244         }
245 
246         if (!TextUtils.isEmpty(outNumber)) {
247             log("convertNumberIfNecessary: convert service number");
248             mNumberConverted = true;
249             return outNumber;
250         }
251 
252         return dialNumber;
253 
254     }
255 
256     /**
257      * Helper function to determine if the phones service is in ROAMING_TYPE_INTERNATIONAL.
258      * @param phone object that contains the service state.
259      * @param bundle object that contains the bundle with mapped dial strings.
260      * @return  true if the phone is in roaming state with a set bundle. Otherwise, false.
261      */
shouldPerformInternationalNumberRemapping(Phone phone, PersistableBundle bundle)262     private boolean shouldPerformInternationalNumberRemapping(Phone phone,
263             PersistableBundle bundle) {
264         if (phone == null || phone.getDefaultPhone() == null) {
265             log("shouldPerformInternationalNumberRemapping: phone was null");
266             return false;
267         }
268 
269         if (bundle.getStringArray(CarrierConfigManager
270                 .KEY_INTERNATIONAL_ROAMING_DIAL_STRING_REPLACE_STRING_ARRAY) == null) {
271             log("shouldPerformInternationalNumberRemapping: did not set the "
272                     + "KEY_INTERNATIONAL_ROAMING_DIAL_STRING_REPLACE_STRING_ARRAY");
273             return false;
274         }
275 
276         return phone.getDefaultPhone().getServiceState().getVoiceRoamingType()
277                 == ServiceState.ROAMING_TYPE_INTERNATIONAL;
278     }
279 
compareGid1(Phone phone, String serviceGid1)280     private boolean compareGid1(Phone phone, String serviceGid1) {
281         String gid1 = phone.getGroupIdLevel1();
282         int gid_length = serviceGid1.length();
283         boolean ret = true;
284 
285         if (serviceGid1 == null || serviceGid1.equals("")) {
286             log("compareGid1 serviceGid is empty, return " + ret);
287             return ret;
288         }
289         // Check if gid1 match service GID1
290         if (!((gid1 != null) && (gid1.length() >= gid_length) &&
291                 gid1.substring(0, gid_length).equalsIgnoreCase(serviceGid1))) {
292             log(" gid1 " + gid1 + " serviceGid1 " + serviceGid1);
293             ret = false;
294         }
295         log("compareGid1 is " + (ret?"Same":"Different"));
296         return ret;
297     }
298 
299     /**
300      * Get the ringing connections which during SRVCC handover.
301      */
getRingingHandoverConnection()302     public Connection getRingingHandoverConnection() {
303         for (Connection hoConn : mHandoverConnections) {
304             if (hoConn.getCall().isRinging()) {
305                 return hoConn;
306             }
307         }
308         return null;
309     }
310 
311     //***** Overridden from Handler
312     @Override
handleMessage(Message msg)313     public abstract void handleMessage (Message msg);
registerForVoiceCallStarted(Handler h, int what, Object obj)314     public abstract void registerForVoiceCallStarted(Handler h, int what, Object obj);
unregisterForVoiceCallStarted(Handler h)315     public abstract void unregisterForVoiceCallStarted(Handler h);
316     @UnsupportedAppUsage
registerForVoiceCallEnded(Handler h, int what, Object obj)317     public abstract void registerForVoiceCallEnded(Handler h, int what, Object obj);
unregisterForVoiceCallEnded(Handler h)318     public abstract void unregisterForVoiceCallEnded(Handler h);
319     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getState()320     public abstract PhoneConstants.State getState();
321     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
log(String msg)322     protected abstract void log(String msg);
323 
324     /**
325      * Called when the call tracker should attempt to reconcile its calls against its underlying
326      * phone implementation and cleanup any stale calls.
327      */
cleanupCalls()328     public void cleanupCalls() {
329         // no base implementation
330     }
331 
dump(FileDescriptor fd, PrintWriter pw, String[] args)332     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
333         pw.println("CallTracker:");
334         pw.println(" mPendingOperations=" + mPendingOperations);
335         pw.println(" mNeedsPoll=" + mNeedsPoll);
336         pw.println(" mLastRelevantPoll=" + mLastRelevantPoll);
337     }
338 }
339