• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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