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