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