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.Annotation.PreciseDisconnectCauses; 33 import android.telephony.CarrierConfigManager; 34 import android.telephony.TelephonyManager; 35 import android.text.TextUtils; 36 import android.util.LocalLog; 37 import android.util.Log; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.telephony.Phone; 41 import com.android.internal.telephony.PhoneFactory; 42 43 import java.util.ArrayList; 44 45 /** Controls the cross stack redialing. */ 46 public class CrossSimRedialingController extends Handler { 47 private static final String TAG = "CrossSimRedialingCtrl"; 48 private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1); 49 private static final int LOG_SIZE = 50; 50 51 /** An interface of a helper to check emergency number. */ 52 public interface EmergencyNumberHelper { 53 /** 54 * Returns whether the number is an emergency number in the given modem slot. 55 * 56 * @param slotId The slot id to be checked. 57 * @param number The number. 58 * @return {@code true} if the number is an emergency number in the given slot. 59 */ isEmergencyNumber(int slotId, String number)60 boolean isEmergencyNumber(int slotId, String number); 61 } 62 63 @VisibleForTesting 64 public static final int MSG_CROSS_STACK_TIMEOUT = 1; 65 @VisibleForTesting 66 public static final int MSG_QUICK_CROSS_STACK_TIMEOUT = 2; 67 68 private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE); 69 70 private final ArrayList<Integer> mStackSelectionHistory = new ArrayList<>(); 71 private final ArrayList<Integer> mPermanentRejectedSlots = new ArrayList<>(); 72 private final TelephonyManager mTelephonyManager; 73 74 private EmergencyNumberHelper mEmergencyNumberHelper = new EmergencyNumberHelper() { 75 @Override 76 public boolean isEmergencyNumber(int slotId, String number) { 77 // TODO(b/258112541) Add System api to check emergency number per subscription. 78 try { 79 Phone phone = PhoneFactory.getPhone(slotId); 80 if (phone != null 81 && phone.getEmergencyNumberTracker() != null 82 && phone.getEmergencyNumberTracker().isEmergencyNumber(number)) { 83 return true; 84 } 85 } catch (IllegalStateException e) { 86 loge("isEmergencyNumber e=" + e); 87 } 88 return false; 89 } 90 }; 91 92 private int mModemCount; 93 94 /** A cache of the carrier config {@link #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT}. */ 95 private int mCrossStackTimer; 96 /** A cache of the carrier config {@link #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT}. */ 97 private int mQuickCrossStackTimer; 98 /** 99 * A cache of the carrier config 100 * {@link #KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL}. 101 */ 102 private boolean mStartQuickCrossStackTimerWhenInService; 103 104 private String mCallId; 105 private EmergencyCallDomainSelector mSelector; 106 private String mNumber; 107 private int mSlotId; 108 private int mSubId; 109 110 /** 111 * Creates an instance. 112 * 113 * @param context The Context this is associated with. 114 * @param looper The Looper to run the CrossSimRedialingController. 115 */ CrossSimRedialingController(@onNull Context context, @NonNull Looper looper)116 public CrossSimRedialingController(@NonNull Context context, @NonNull Looper looper) { 117 super(looper); 118 119 mTelephonyManager = context.getSystemService(TelephonyManager.class); 120 } 121 122 /** For unit test only */ 123 @VisibleForTesting CrossSimRedialingController(@onNull Context context, @NonNull Looper looper, EmergencyNumberHelper emergencyNumberHelper)124 public CrossSimRedialingController(@NonNull Context context, @NonNull Looper looper, 125 EmergencyNumberHelper emergencyNumberHelper) { 126 this(context, looper); 127 128 mEmergencyNumberHelper = emergencyNumberHelper; 129 } 130 131 /** 132 * Starts the timer. 133 * 134 * @param context The Context this is associated with. 135 * @param selector The instance of {@link EmergencyCallDomainSelector}. 136 * @param callId The call identifier. 137 * @param number The dialing number. 138 * @param inService Indiates that normal service is available. 139 * @param roaming Indicates that it's in roaming or non-domestic network. 140 * @param modemCount The number of active modem count 141 */ startTimer(@onNull Context context, @NonNull EmergencyCallDomainSelector selector, @NonNull String callId, @NonNull String number, boolean inService, boolean roaming, int modemCount)142 public void startTimer(@NonNull Context context, 143 @NonNull EmergencyCallDomainSelector selector, 144 @NonNull String callId, @NonNull String number, 145 boolean inService, boolean roaming, int modemCount) { 146 logi("startTimer callId=" + callId 147 + ", in service=" + inService + ", roaming=" + roaming); 148 149 if (!TextUtils.equals(mCallId, callId)) { 150 logi("startTimer callId changed"); 151 mCallId = callId; 152 mStackSelectionHistory.clear(); 153 mPermanentRejectedSlots.clear(); 154 } 155 mSelector = selector; 156 mSlotId = selector.getSlotId(); 157 mSubId = selector.getSubId(); 158 mNumber = number; 159 mModemCount = modemCount; 160 161 updateCarrierConfiguration(context); 162 163 boolean firstAttempt = !mStackSelectionHistory.contains(mSlotId); 164 logi("startTimer slot=" + mSlotId + ", firstAttempt=" + firstAttempt); 165 mStackSelectionHistory.add(mSlotId); 166 167 if (firstAttempt && mQuickCrossStackTimer > REDIAL_TIMER_DISABLED && !roaming) { 168 if (inService || !mStartQuickCrossStackTimerWhenInService) { 169 logi("startTimer quick timer started"); 170 sendEmptyMessageDelayed(MSG_QUICK_CROSS_STACK_TIMEOUT, 171 mQuickCrossStackTimer); 172 return; 173 } 174 } 175 176 if (mCrossStackTimer > REDIAL_TIMER_DISABLED) { 177 logi("startTimer timer started"); 178 sendEmptyMessageDelayed(MSG_CROSS_STACK_TIMEOUT, mCrossStackTimer); 179 } 180 } 181 182 /** Stops the timers. */ stopTimer()183 public void stopTimer() { 184 logi("stopTimer"); 185 removeMessages(MSG_CROSS_STACK_TIMEOUT); 186 removeMessages(MSG_QUICK_CROSS_STACK_TIMEOUT); 187 } 188 189 /** 190 * Informs the call failure. 191 * @param cause The call failure cause. 192 */ notifyCallFailure(@reciseDisconnectCauses int cause)193 public void notifyCallFailure(@PreciseDisconnectCauses int cause) { 194 logi("notifyCallFailure cause=" + cause); 195 if (cause == EMERGENCY_PERM_FAILURE) { 196 mPermanentRejectedSlots.add(mSlotId); 197 } 198 } 199 200 @Override handleMessage(Message msg)201 public void handleMessage(Message msg) { 202 switch(msg.what) { 203 case MSG_CROSS_STACK_TIMEOUT: 204 case MSG_QUICK_CROSS_STACK_TIMEOUT: 205 handleCrossStackTimeout(); 206 break; 207 default: 208 super.handleMessage(msg); 209 break; 210 } 211 } 212 handleCrossStackTimeout()213 private void handleCrossStackTimeout() { 214 logi("handleCrossStackTimeout"); 215 216 if (isThereOtherSlot()) { 217 mSelector.notifyCrossStackTimerExpired(); 218 } 219 } 220 221 /** 222 * Returns whether there is another slot emergency capable. 223 * 224 * @return {@code true} if there is another slot emergency capable, 225 * {@code false} otherwise. 226 */ isThereOtherSlot()227 public boolean isThereOtherSlot() { 228 logi("isThereOtherSlot modemCount=" + mModemCount); 229 if (mModemCount < 2) return false; 230 231 for (int i = 0; i < mModemCount; i++) { 232 if (i == mSlotId) continue; 233 234 if (mPermanentRejectedSlots.contains(i)) { 235 logi("isThereOtherSlot index=" + i + ", permanent rejected"); 236 continue; 237 } 238 239 int simState = mTelephonyManager.getSimState(i); 240 if (simState != TelephonyManager.SIM_STATE_READY) { 241 logi("isThereOtherSlot index=" + i + ", simState=" + simState); 242 continue; 243 } 244 245 if (mEmergencyNumberHelper.isEmergencyNumber(i, mNumber)) { 246 logi("isThereOtherSlot index=" + i + ", found"); 247 return true; 248 } else { 249 logi("isThereOtherSlot index=" + i + ", not emergency number"); 250 } 251 } 252 253 return false; 254 } 255 256 /** 257 * Caches the configuration. 258 */ updateCarrierConfiguration(Context context)259 private void updateCarrierConfiguration(Context context) { 260 CarrierConfigManager configMgr = context.getSystemService(CarrierConfigManager.class); 261 PersistableBundle b = configMgr.getConfigForSubId(mSubId, 262 KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, 263 KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, 264 KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL); 265 if (b == null) { 266 b = CarrierConfigManager.getDefaultConfig(); 267 } 268 269 mCrossStackTimer = b.getInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT) * 1000; 270 mQuickCrossStackTimer = 271 b.getInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT) * 1000; 272 mStartQuickCrossStackTimerWhenInService = 273 b.getBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL); 274 275 logi("updateCarrierConfiguration " 276 + ", crossStackTimer=" + mCrossStackTimer 277 + ", quickCrossStackTimer=" + mQuickCrossStackTimer 278 + ", startQuickTimerInService=" + mStartQuickCrossStackTimerWhenInService); 279 } 280 281 /** Destroys the instance. */ destroy()282 public void destroy() { 283 if (DBG) logd("destroy"); 284 285 removeMessages(MSG_CROSS_STACK_TIMEOUT); 286 removeMessages(MSG_QUICK_CROSS_STACK_TIMEOUT); 287 } 288 logd(String s)289 private void logd(String s) { 290 Log.d(TAG, "[" + mSlotId + "|" + mSubId + "] " + s); 291 } 292 logi(String s)293 private void logi(String s) { 294 Log.i(TAG, "[" + mSlotId + "|" + mSubId + "] " + s); 295 sLocalLog.log(s); 296 } 297 loge(String s)298 private void loge(String s) { 299 Log.e(TAG, "[" + mSlotId + "|" + mSubId + "] " + s); 300 sLocalLog.log(s); 301 } 302 } 303