1 /* 2 * Copyright (C) 2023 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.services.telephony.domainselection; 18 19 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT; 20 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT; 21 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL; 22 import static android.telephony.CarrierConfigManager.ImsEmergency.REDIAL_TIMER_DISABLED; 23 import static android.telephony.PreciseDisconnectCause.EMERGENCY_PERM_FAILURE; 24 25 import android.annotation.NonNull; 26 import android.content.Context; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.os.PersistableBundle; 31 import android.os.SystemProperties; 32 import android.telephony.AccessNetworkConstants; 33 import android.telephony.Annotation.PreciseDisconnectCauses; 34 import android.telephony.CarrierConfigManager; 35 import android.telephony.NetworkRegistrationInfo; 36 import android.telephony.PhoneNumberUtils; 37 import android.telephony.ServiceState; 38 import android.telephony.SubscriptionManager; 39 import android.telephony.TelephonyManager; 40 import android.telephony.emergency.EmergencyNumber; 41 import android.text.TextUtils; 42 import android.util.LocalLog; 43 import android.util.Log; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.Map; 50 51 /** Controls the cross stack redialing. */ 52 public class CrossSimRedialingController extends Handler { 53 private static final String TAG = "CrossSimRedialingCtrl"; 54 private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1); 55 private static final int LOG_SIZE = 50; 56 57 /** An interface of a helper to check emergency number. */ 58 public interface EmergencyNumberHelper { 59 /** 60 * Returns whether the number is an emergency number in the given modem slot. 61 * 62 * @param subId The sub id to be checked. 63 * @param number The number. 64 * @return {@code true} if the number is an emergency number in the given slot. 65 */ isEmergencyNumber(int subId, String number)66 boolean isEmergencyNumber(int subId, String number); 67 } 68 69 @VisibleForTesting 70 public static final int MSG_CROSS_STACK_TIMEOUT = 1; 71 @VisibleForTesting 72 public static final int MSG_QUICK_CROSS_STACK_TIMEOUT = 2; 73 74 private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE); 75 76 private final ArrayList<Integer> mStackSelectionHistory = new ArrayList<>(); 77 private final ArrayList<Integer> mPermanentRejectedSlots = new ArrayList<>(); 78 private final TelephonyManager mTelephonyManager; 79 80 private EmergencyNumberHelper mEmergencyNumberHelper = new EmergencyNumberHelper() { 81 @Override 82 public boolean isEmergencyNumber(int subId, String number) { 83 number = PhoneNumberUtils.stripSeparators(number); 84 if (TextUtils.isEmpty(number)) return false; 85 Map<Integer, List<EmergencyNumber>> lists = null; 86 try { 87 lists = mTelephonyManager.getEmergencyNumberList(); 88 } catch (IllegalStateException ise) { 89 loge("isEmergencyNumber ise=" + ise); 90 } catch (RuntimeException rte) { 91 loge("isEmergencyNumber rte=" + rte); 92 } 93 if (lists == null) return false; 94 95 List<EmergencyNumber> list = lists.get(subId); 96 if (list == null || list.isEmpty()) return false; 97 for (EmergencyNumber eNumber : list) { 98 if (number.equals(eNumber.getNumber())) return true; 99 } 100 return false; 101 } 102 }; 103 104 private int mModemCount; 105 106 /** A cache of the carrier config {@link #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT}. */ 107 private int mCrossStackTimer; 108 /** A cache of the carrier config {@link #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT}. */ 109 private int mQuickCrossStackTimer; 110 /** 111 * A cache of the carrier config 112 * {@link #KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL}. 113 */ 114 private boolean mStartQuickCrossStackTimerWhenInService; 115 116 private String mCallId; 117 private EmergencyCallDomainSelector mSelector; 118 private String mNumber; 119 private int mSlotId; 120 private int mSubId; 121 122 /** 123 * Creates an instance. 124 * 125 * @param context The Context this is associated with. 126 * @param looper The Looper to run the CrossSimRedialingController. 127 */ CrossSimRedialingController(@onNull Context context, @NonNull Looper looper)128 public CrossSimRedialingController(@NonNull Context context, @NonNull Looper looper) { 129 super(looper); 130 131 mTelephonyManager = context.getSystemService(TelephonyManager.class); 132 } 133 134 /** For unit test only */ 135 @VisibleForTesting CrossSimRedialingController(@onNull Context context, @NonNull Looper looper, EmergencyNumberHelper emergencyNumberHelper)136 public CrossSimRedialingController(@NonNull Context context, @NonNull Looper looper, 137 EmergencyNumberHelper emergencyNumberHelper) { 138 this(context, looper); 139 140 mEmergencyNumberHelper = emergencyNumberHelper; 141 } 142 143 /** 144 * Starts the timer. 145 * 146 * @param context The Context this is associated with. 147 * @param selector The instance of {@link EmergencyCallDomainSelector}. 148 * @param callId The call identifier. 149 * @param number The dialing number. 150 * @param inService Indicates that normal service is available. 151 * @param roaming Indicates that it's in roaming or non-domestic network. 152 * @param modemCount The number of active modem count 153 */ startTimer(@onNull Context context, @NonNull EmergencyCallDomainSelector selector, @NonNull String callId, @NonNull String number, boolean inService, boolean roaming, int modemCount)154 public void startTimer(@NonNull Context context, 155 @NonNull EmergencyCallDomainSelector selector, 156 @NonNull String callId, @NonNull String number, 157 boolean inService, boolean roaming, int modemCount) { 158 logi("startTimer callId=" + callId 159 + ", in service=" + inService + ", roaming=" + roaming); 160 161 if (!TextUtils.equals(mCallId, callId)) { 162 logi("startTimer callId changed"); 163 mCallId = callId; 164 mStackSelectionHistory.clear(); 165 mPermanentRejectedSlots.clear(); 166 } 167 mSelector = selector; 168 mSlotId = selector.getSlotId(); 169 mSubId = selector.getSubId(); 170 mNumber = number; 171 mModemCount = modemCount; 172 173 updateCarrierConfiguration(context); 174 175 boolean firstAttempt = !mStackSelectionHistory.contains(mSlotId); 176 logi("startTimer slot=" + mSlotId + ", firstAttempt=" + firstAttempt); 177 mStackSelectionHistory.add(mSlotId); 178 179 if (firstAttempt && mQuickCrossStackTimer > REDIAL_TIMER_DISABLED && !roaming) { 180 if (inService || !mStartQuickCrossStackTimerWhenInService) { 181 logi("startTimer quick timer started"); 182 sendEmptyMessageDelayed(MSG_QUICK_CROSS_STACK_TIMEOUT, 183 mQuickCrossStackTimer); 184 return; 185 } 186 } 187 188 if (mCrossStackTimer > REDIAL_TIMER_DISABLED) { 189 logi("startTimer timer started"); 190 sendEmptyMessageDelayed(MSG_CROSS_STACK_TIMEOUT, mCrossStackTimer); 191 } 192 } 193 194 /** Stops the timers. */ stopTimer()195 public void stopTimer() { 196 logi("stopTimer"); 197 removeMessages(MSG_CROSS_STACK_TIMEOUT); 198 removeMessages(MSG_QUICK_CROSS_STACK_TIMEOUT); 199 } 200 201 /** 202 * Informs the call failure. 203 * @param cause The call failure cause. 204 */ notifyCallFailure(@reciseDisconnectCauses int cause)205 public void notifyCallFailure(@PreciseDisconnectCauses int cause) { 206 logi("notifyCallFailure cause=" + cause); 207 if (cause == EMERGENCY_PERM_FAILURE) { 208 mPermanentRejectedSlots.add(mSlotId); 209 } 210 } 211 212 @Override handleMessage(Message msg)213 public void handleMessage(Message msg) { 214 switch(msg.what) { 215 case MSG_CROSS_STACK_TIMEOUT: 216 case MSG_QUICK_CROSS_STACK_TIMEOUT: 217 handleCrossStackTimeout(); 218 break; 219 default: 220 super.handleMessage(msg); 221 break; 222 } 223 } 224 handleCrossStackTimeout()225 private void handleCrossStackTimeout() { 226 logi("handleCrossStackTimeout"); 227 228 if (isThereOtherSlot()) { 229 mSelector.notifyCrossStackTimerExpired(); 230 } else if (!mPermanentRejectedSlots.isEmpty()) { 231 mSelector.maybeHangupOngoingDialing(); 232 } 233 } 234 235 /** 236 * Returns whether there is another slot with which normal service is available. 237 * 238 * @return {@code true} if there is another slot with which normal service is available. 239 * {@code false} otherwise. 240 */ isThereOtherSlotInService()241 public boolean isThereOtherSlotInService() { 242 return isThereOtherSlot(true); 243 } 244 245 /** 246 * Returns whether there is another slot emergency capable. 247 * 248 * @return {@code true} if there is another slot emergency capable, 249 * {@code false} otherwise. 250 */ isThereOtherSlot()251 public boolean isThereOtherSlot() { 252 return isThereOtherSlot(false); 253 } 254 isThereOtherSlot(boolean networkRegisteredOnly)255 private boolean isThereOtherSlot(boolean networkRegisteredOnly) { 256 logi("isThereOtherSlot modemCount=" + mModemCount); 257 if (mModemCount < 2) return false; 258 259 for (int i = 0; i < mModemCount; i++) { 260 if (i == mSlotId) continue; 261 262 if (mPermanentRejectedSlots.contains(i)) { 263 logi("isThereOtherSlot index=" + i + ", permanent rejected"); 264 continue; 265 } 266 267 int simState = mTelephonyManager.getSimState(i); 268 if (simState != TelephonyManager.SIM_STATE_READY) { 269 logi("isThereOtherSlot index=" + i + ", simState=" + simState); 270 continue; 271 } 272 273 int subId = SubscriptionManager.getSubscriptionId(i); 274 if (mEmergencyNumberHelper.isEmergencyNumber(subId, mNumber)) { 275 logi("isThereOtherSlot index=" + i + "(" + subId + "), found"); 276 if (networkRegisteredOnly) { 277 if (isNetworkRegistered(subId)) { 278 return true; 279 } 280 } else { 281 return true; 282 } 283 } else { 284 logi("isThereOtherSlot index=" + i + "(" + subId + "), not emergency number"); 285 } 286 } 287 288 return false; 289 } 290 isNetworkRegistered(int subId)291 private boolean isNetworkRegistered(int subId) { 292 if (!SubscriptionManager.isValidSubscriptionId(subId)) return false; 293 294 TelephonyManager tm = mTelephonyManager.createForSubscriptionId(subId); 295 ServiceState ss = tm.getServiceState(); 296 if (ss != null) { 297 NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo( 298 NetworkRegistrationInfo.DOMAIN_PS, 299 AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 300 if (nri != null && nri.isNetworkRegistered()) { 301 // PS is IN_SERVICE state. 302 return true; 303 } 304 nri = ss.getNetworkRegistrationInfo( 305 NetworkRegistrationInfo.DOMAIN_CS, 306 AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 307 if (nri != null && nri.isNetworkRegistered()) { 308 // CS is IN_SERVICE state. 309 return true; 310 } 311 } 312 logi("isNetworkRegistered subId=" + subId + " not network registered"); 313 return false; 314 } 315 316 /** 317 * Caches the configuration. 318 */ updateCarrierConfiguration(Context context)319 private void updateCarrierConfiguration(Context context) { 320 CarrierConfigManager configMgr = context.getSystemService(CarrierConfigManager.class); 321 PersistableBundle b = configMgr.getConfigForSubId(mSubId, 322 KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, 323 KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, 324 KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL); 325 if (b == null) { 326 b = CarrierConfigManager.getDefaultConfig(); 327 } 328 329 mCrossStackTimer = b.getInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT) * 1000; 330 mQuickCrossStackTimer = 331 b.getInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT) * 1000; 332 mStartQuickCrossStackTimerWhenInService = 333 b.getBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL); 334 335 logi("updateCarrierConfiguration " 336 + ", crossStackTimer=" + mCrossStackTimer 337 + ", quickCrossStackTimer=" + mQuickCrossStackTimer 338 + ", startQuickTimerInService=" + mStartQuickCrossStackTimerWhenInService); 339 } 340 341 /** Test purpose only. */ 342 @VisibleForTesting getEmergencyNumberHelper()343 public EmergencyNumberHelper getEmergencyNumberHelper() { 344 return mEmergencyNumberHelper; 345 } 346 347 /** Destroys the instance. */ destroy()348 public void destroy() { 349 if (DBG) logd("destroy"); 350 351 removeMessages(MSG_CROSS_STACK_TIMEOUT); 352 removeMessages(MSG_QUICK_CROSS_STACK_TIMEOUT); 353 } 354 logd(String s)355 private void logd(String s) { 356 Log.d(TAG, "[" + mSlotId + "|" + mSubId + "] " + s); 357 } 358 logi(String s)359 private void logi(String s) { 360 Log.i(TAG, "[" + mSlotId + "|" + mSubId + "] " + s); 361 sLocalLog.log(s); 362 } 363 loge(String s)364 private void loge(String s) { 365 Log.e(TAG, "[" + mSlotId + "|" + mSubId + "] " + s); 366 sLocalLog.log(s); 367 } 368 } 369