• 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.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