1/**
2 * Copyright (C) 2009 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
17package com.android.internal.telephony;
18
19import android.content.Context;
20import android.os.Build;
21import android.os.PersistableBundle;
22import android.os.SystemProperties;
23import android.telephony.CarrierConfigManager;
24import android.telephony.Rlog;
25import android.text.TextUtils;
26import android.util.Pair;
27
28import com.android.internal.telephony.dataconnection.ApnSetting;
29
30import java.io.FileDescriptor;
31import java.io.PrintWriter;
32import java.util.ArrayList;
33import java.util.Random;
34
35/**
36 * Retry manager allows a simple way to declare a series of
37 * retry timeouts. After creating a RetryManager the configure
38 * method is used to define the sequence. A simple linear series
39 * may be initialized using configure with three integer parameters
40 * The other configure method allows a series to be declared using
41 * a string.
42 *<p>
43 * The format of the configuration string is a series of parameters
44 * separated by a comma. There are two name value pair parameters plus a series
45 * of delay times. The units of of these delay times is unspecified.
46 * The name value pairs which may be specified are:
47 *<ul>
48 *<li>max_retries=<value>
49 *<li>default_randomizationTime=<value>
50 *</ul>
51 *<p>
52 * max_retries is the number of times that incrementRetryCount
53 * maybe called before isRetryNeeded will return false. if value
54 * is infinite then isRetryNeeded will always return true.
55 *
56 * default_randomizationTime will be used as the randomizationTime
57 * for delay times which have no supplied randomizationTime. If
58 * default_randomizationTime is not defined it defaults to 0.
59 *<p>
60 * The other parameters define The series of delay times and each
61 * may have an optional randomization value separated from the
62 * delay time by a colon.
63 *<p>
64 * Examples:
65 * <ul>
66 * <li>3 retries with no randomization value which means its 0:
67 * <ul><li><code>"1000, 2000, 3000"</code></ul>
68 *
69 * <li>10 retries with a 500 default randomization value for each and
70 * the 4..10 retries all using 3000 as the delay:
71 * <ul><li><code>"max_retries=10, default_randomization=500, 1000, 2000, 3000"</code></ul>
72 *
73 * <li>4 retries with a 100 as the default randomization value for the first 2 values and
74 * the other two having specified values of 500:
75 * <ul><li><code>"default_randomization=100, 1000, 2000, 4000:500, 5000:500"</code></ul>
76 *
77 * <li>Infinite number of retries with the first one at 1000, the second at 2000 all
78 * others will be at 3000.
79 * <ul><li><code>"max_retries=infinite,1000,2000,3000</code></ul>
80 * </ul>
81 *
82 * {@hide}
83 */
84public class RetryManager {
85    public static final String LOG_TAG = "RetryManager";
86    public static final boolean DBG = true;
87    public static final boolean VDBG = false; // STOPSHIP if true
88
89    /**
90     * The default retry configuration for default type APN. See above for the syntax.
91     */
92    private static final String DEFAULT_DATA_RETRY_CONFIG = "default_randomization=2000,"
93            + "5000,10000,20000,40000,80000:5000,160000:5000,"
94            + "320000:5000,640000:5000,1280000:5000,1800000:5000";
95
96    /**
97     * Retry configuration for networks other than default type, for example, mms. See above
98     * for the syntax.
99     */
100    private static final String OTHERS_DATA_RETRY_CONFIG =
101            "max_retries=3, 5000, 5000, 5000";
102
103    /**
104     * The default value (in milliseconds) for delay between APN trying (mInterApnDelay)
105     * within the same round
106     */
107    private static final long DEFAULT_INTER_APN_DELAY = 20000;
108
109    /**
110     * The default value (in milliseconds) for delay between APN trying (mFailFastInterApnDelay)
111     * within the same round when we are in fail fast mode
112     */
113    private static final long DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING = 3000;
114
115    /**
116     * The value indicating no retry is needed
117     */
118    public static final long NO_RETRY = -1;
119
120    /**
121     * The value indicating modem did not suggest any retry delay
122     */
123    public static final long NO_SUGGESTED_RETRY_DELAY = -2;
124
125    /**
126     * If the modem suggests a retry delay in the data call setup response, we will retry
127     * the current APN setting again. However, if the modem keeps suggesting retrying the same
128     * APN setting, we'll fall into an infinite loop. Therefore adding a counter to retry up to
129     * MAX_SAME_APN_RETRY times can avoid it.
130     */
131    private static final int MAX_SAME_APN_RETRY = 3;
132
133    /**
134     * The delay (in milliseconds) between APN trying within the same round
135     */
136    private long mInterApnDelay;
137
138    /**
139     * The delay (in milliseconds) between APN trying within the same round when we are in
140     * fail fast mode
141     */
142    private long mFailFastInterApnDelay;
143
144    /**
145     * Modem suggested delay for retrying the current APN
146     */
147    private long mModemSuggestedDelay = NO_SUGGESTED_RETRY_DELAY;
148
149    /**
150     * The counter for same APN retrying. See MAX_SAME_APN_RETRY for the details.
151     */
152    private int mSameApnRetryCount = 0;
153
154    /**
155     * Retry record with times in milli-seconds
156     */
157    private static class RetryRec {
158        RetryRec(int delayTime, int randomizationTime) {
159            mDelayTime = delayTime;
160            mRandomizationTime = randomizationTime;
161        }
162
163        int mDelayTime;
164        int mRandomizationTime;
165    }
166
167    /**
168     * The array of retry records
169     */
170    private ArrayList<RetryRec> mRetryArray = new ArrayList<RetryRec>();
171
172    private Phone mPhone;
173
174    /**
175     * Flag indicating whether retrying forever regardless the maximum retry count mMaxRetryCount
176     */
177    private boolean mRetryForever = false;
178
179    /**
180     * The maximum number of retries to attempt
181     */
182    private int mMaxRetryCount;
183
184    /**
185     * The current number of retries
186     */
187    private int mRetryCount = 0;
188
189    /**
190     * Random number generator. The random delay will be added into retry timer to avoid all devices
191     * around retrying the APN at the same time.
192     */
193    private Random mRng = new Random();
194
195    /**
196     * Retry manager configuration string. See top of the detailed explanation.
197     */
198    private String mConfig;
199
200    /**
201     * The list to store APN setting candidates for data call setup. Most of the carriers only have
202     * one APN, but few carriers have more than one.
203     */
204    private ArrayList<ApnSetting> mWaitingApns = null;
205
206    /**
207     * Index pointing to the current trying APN from mWaitingApns
208     */
209    private int mCurrentApnIndex = -1;
210
211    /**
212     * Apn context type. Could be "default, "mms", "supl", etc...
213     */
214    private String mApnType;
215
216    /**
217     * Retry manager constructor
218     * @param phone Phone object
219     * @param apnType APN type
220     */
221    public RetryManager(Phone phone, String apnType) {
222        mPhone = phone;
223        mApnType = apnType;
224    }
225
226    /**
227     * Configure for using string which allow arbitrary
228     * sequences of times. See class comments for the
229     * string format.
230     *
231     * @return true if successful
232     */
233    private boolean configure(String configStr) {
234        // Strip quotes if present.
235        if ((configStr.startsWith("\"") && configStr.endsWith("\""))) {
236            configStr = configStr.substring(1, configStr.length() - 1);
237        }
238
239        // Reset the retry manager since delay, max retry count, etc...will be reset.
240        reset();
241
242        if (DBG) log("configure: '" + configStr + "'");
243        mConfig = configStr;
244
245        if (!TextUtils.isEmpty(configStr)) {
246            int defaultRandomization = 0;
247
248            if (VDBG) log("configure: not empty");
249
250            String strArray[] = configStr.split(",");
251            for (int i = 0; i < strArray.length; i++) {
252                if (VDBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'");
253                Pair<Boolean, Integer> value;
254                String splitStr[] = strArray[i].split("=", 2);
255                splitStr[0] = splitStr[0].trim();
256                if (VDBG) log("configure: splitStr[0]='" + splitStr[0] + "'");
257                if (splitStr.length > 1) {
258                    splitStr[1] = splitStr[1].trim();
259                    if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
260                    if (TextUtils.equals(splitStr[0], "default_randomization")) {
261                        value = parseNonNegativeInt(splitStr[0], splitStr[1]);
262                        if (!value.first) return false;
263                        defaultRandomization = value.second;
264                    } else if (TextUtils.equals(splitStr[0], "max_retries")) {
265                        if (TextUtils.equals("infinite", splitStr[1])) {
266                            mRetryForever = true;
267                        } else {
268                            value = parseNonNegativeInt(splitStr[0], splitStr[1]);
269                            if (!value.first) return false;
270                            mMaxRetryCount = value.second;
271                        }
272                    } else {
273                        Rlog.e(LOG_TAG, "Unrecognized configuration name value pair: "
274                                        + strArray[i]);
275                        return false;
276                    }
277                } else {
278                    /**
279                     * Assume a retry time with an optional randomization value
280                     * following a ":"
281                     */
282                    splitStr = strArray[i].split(":", 2);
283                    splitStr[0] = splitStr[0].trim();
284                    RetryRec rr = new RetryRec(0, 0);
285                    value = parseNonNegativeInt("delayTime", splitStr[0]);
286                    if (!value.first) return false;
287                    rr.mDelayTime = value.second;
288
289                    // Check if optional randomization value present
290                    if (splitStr.length > 1) {
291                        splitStr[1] = splitStr[1].trim();
292                        if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
293                        value = parseNonNegativeInt("randomizationTime", splitStr[1]);
294                        if (!value.first) return false;
295                        rr.mRandomizationTime = value.second;
296                    } else {
297                        rr.mRandomizationTime = defaultRandomization;
298                    }
299                    mRetryArray.add(rr);
300                }
301            }
302            if (mRetryArray.size() > mMaxRetryCount) {
303                mMaxRetryCount = mRetryArray.size();
304                if (VDBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount);
305            }
306        } else {
307            log("configure: cleared");
308        }
309
310        if (VDBG) log("configure: true");
311        return true;
312    }
313
314    /**
315     * Configure the retry manager
316     * @param forDefault True if the APN support default type
317     */
318    private void configureRetry(boolean forDefault) {
319        String configString = "";
320        try {
321            if (Build.IS_DEBUGGABLE) {
322                // Using system properties is easier for testing from command line.
323                String config = SystemProperties.get("test.data_retry_config");
324                if (!TextUtils.isEmpty(config)) {
325                    configure(config);
326                    return;
327                }
328            }
329
330            CarrierConfigManager configManager = (CarrierConfigManager)
331                    mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
332            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
333
334            mInterApnDelay = b.getLong(
335                    CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG,
336                    DEFAULT_INTER_APN_DELAY);
337            mFailFastInterApnDelay = b.getLong(
338                    CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG,
339                    DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING);
340
341            if (forDefault) {
342                configString = b.getString(
343                        CarrierConfigManager.KEY_CARRIER_DATA_CALL_RETRY_CONFIG_DEFAULT_STRING,
344                        DEFAULT_DATA_RETRY_CONFIG);
345            }
346            else {
347                configString = b.getString(
348                        CarrierConfigManager.KEY_CARRIER_DATA_CALL_RETRY_CONFIG_OTHERS_STRING,
349                        OTHERS_DATA_RETRY_CONFIG);
350            }
351        } catch (NullPointerException ex) {
352            log("Failed to read configuration! Use the hardcoded default value.");
353
354            mInterApnDelay = DEFAULT_INTER_APN_DELAY;
355            mFailFastInterApnDelay = DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING;
356            configString = (forDefault) ? DEFAULT_DATA_RETRY_CONFIG : OTHERS_DATA_RETRY_CONFIG;
357        }
358
359        if (VDBG) {
360            log("mInterApnDelay = " + mInterApnDelay + ", mFailFastInterApnDelay = " +
361                    mFailFastInterApnDelay);
362        }
363
364        configure(configString);
365    }
366
367    /**
368     * Return the timer that should be used to trigger the data reconnection
369     */
370    private int getRetryTimer() {
371        int index;
372        if (mRetryCount < mRetryArray.size()) {
373            index = mRetryCount;
374        } else {
375            index = mRetryArray.size() - 1;
376        }
377
378        int retVal;
379        if ((index >= 0) && (index < mRetryArray.size())) {
380            retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index);
381        } else {
382            retVal = 0;
383        }
384
385        if (DBG) log("getRetryTimer: " + retVal);
386        return retVal;
387    }
388
389    /**
390     * Parse an integer validating the value is not negative.
391     * @param name Name
392     * @param stringValue Value
393     * @return Pair.first == true if stringValue an integer >= 0
394     */
395    private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) {
396        int value;
397        Pair<Boolean, Integer> retVal;
398        try {
399            value = Integer.parseInt(stringValue);
400            retVal = new Pair<Boolean, Integer>(validateNonNegativeInt(name, value), value);
401        } catch (NumberFormatException e) {
402            Rlog.e(LOG_TAG, name + " bad value: " + stringValue, e);
403            retVal = new Pair<Boolean, Integer>(false, 0);
404        }
405        if (VDBG) {
406            log("parseNonNetativeInt: " + name + ", " + stringValue + ", "
407                    + retVal.first + ", " + retVal.second);
408        }
409        return retVal;
410    }
411
412    /**
413     * Validate an integer is >= 0 and logs an error if not
414     * @param name Name
415     * @param value Value
416     * @return Pair.first
417     */
418    private boolean validateNonNegativeInt(String name, int value) {
419        boolean retVal;
420        if (value < 0) {
421            Rlog.e(LOG_TAG, name + " bad value: is < 0");
422            retVal = false;
423        } else {
424            retVal = true;
425        }
426        if (VDBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal);
427        return retVal;
428    }
429
430    /**
431     * Return next random number for the index
432     * @param index Retry index
433     */
434    private int nextRandomizationTime(int index) {
435        int randomTime = mRetryArray.get(index).mRandomizationTime;
436        if (randomTime == 0) {
437            return 0;
438        } else {
439            return mRng.nextInt(randomTime);
440        }
441    }
442
443    /**
444     * Get the next APN setting for data call setup.
445     * @return APN setting to try
446     */
447    public ApnSetting getNextApnSetting() {
448
449        if (mWaitingApns == null || mWaitingApns.size() == 0) {
450            log("Waiting APN list is null or empty.");
451            return null;
452        }
453
454        // If the modem had suggested a retry delay, we should retry the current APN again
455        // (up to MAX_SAME_APN_RETRY times) instead of getting the next APN setting from
456        // our own list.
457        if (mModemSuggestedDelay != NO_SUGGESTED_RETRY_DELAY &&
458                mSameApnRetryCount < MAX_SAME_APN_RETRY) {
459            mSameApnRetryCount++;
460            return mWaitingApns.get(mCurrentApnIndex);
461        }
462
463        mSameApnRetryCount = 0;
464
465        int index = mCurrentApnIndex;
466        // Loop through the APN list to find out the index of next non-permanent failed APN.
467        while (true) {
468            if (++index == mWaitingApns.size()) index = 0;
469
470            // Stop if we find the non-failed APN.
471            if (mWaitingApns.get(index).permanentFailed == false) break;
472
473            // If we've already cycled through all the APNs, that means there is no APN we can try
474            if (index == mCurrentApnIndex) return null;
475        }
476
477        mCurrentApnIndex = index;
478        return mWaitingApns.get(mCurrentApnIndex);
479    }
480
481    /**
482     * Get the delay for trying the next waiting APN from the list.
483     * @param failFastEnabled True if fail fast mode enabled. In this case we'll use a shorter
484     *                        delay.
485     * @return delay in milliseconds
486     */
487    public long getDelayForNextApn(boolean failFastEnabled) {
488
489        if (mWaitingApns == null || mWaitingApns.size() == 0) {
490            log("Waiting APN list is null or empty.");
491            return NO_RETRY;
492        }
493
494        if (mModemSuggestedDelay == NO_RETRY) {
495            log("Modem suggested not retrying.");
496            return NO_RETRY;
497        }
498
499        if (mModemSuggestedDelay != NO_SUGGESTED_RETRY_DELAY &&
500                mSameApnRetryCount < MAX_SAME_APN_RETRY) {
501            // If the modem explicitly suggests a retry delay, we should use it, even in fail fast
502            // mode.
503            log("Modem suggested retry in " + mModemSuggestedDelay + " ms.");
504            return mModemSuggestedDelay;
505        }
506
507        // In order to determine the delay to try next APN, we need to peek the next available APN.
508        // Case 1 - If we will start the next round of APN trying,
509        //    we use the exponential-growth delay. (e.g. 5s, 10s, 30s...etc.)
510        // Case 2 - If we are still within the same round of APN trying,
511        //    we use the fixed standard delay between APNs. (e.g. 20s)
512
513        int index = mCurrentApnIndex;
514        while (true) {
515            if (++index >= mWaitingApns.size()) index = 0;
516
517            // Stop if we find the non-failed APN.
518            if (mWaitingApns.get(index).permanentFailed == false) break;
519
520            // If we've already cycled through all the APNs, that means all APNs have
521            // permanently failed
522            if (index == mCurrentApnIndex) {
523                log("All APNs have permanently failed.");
524                return NO_RETRY;
525            }
526        }
527
528        long delay;
529        if (index <= mCurrentApnIndex) {
530            // Case 1, if the next APN is in the next round.
531            if (!mRetryForever && mRetryCount + 1 > mMaxRetryCount) {
532                log("Reached maximum retry count " + mMaxRetryCount + ".");
533                return NO_RETRY;
534            }
535            delay = getRetryTimer();
536            ++mRetryCount;
537        } else {
538            // Case 2, if the next APN is still in the same round.
539            delay = mInterApnDelay;
540        }
541
542        if (failFastEnabled && delay > mFailFastInterApnDelay) {
543            // If we enable fail fast mode, and the delay we got is longer than
544            // fail-fast delay (mFailFastInterApnDelay), use the fail-fast delay.
545            // If the delay we calculated is already shorter than fail-fast delay,
546            // then ignore fail-fast delay.
547            delay = mFailFastInterApnDelay;
548        }
549
550        return delay;
551    }
552
553    /**
554     * Mark the APN setting permanently failed.
555     * @param apn APN setting to be marked as permanently failed
556     * */
557    public void markApnPermanentFailed(ApnSetting apn) {
558        if (apn != null) {
559            apn.permanentFailed = true;
560        }
561    }
562
563    /**
564     * Reset the retry manager.
565     */
566    private void reset() {
567        mMaxRetryCount = 0;
568        mRetryCount = 0;
569        mCurrentApnIndex = -1;
570        mSameApnRetryCount = 0;
571        mModemSuggestedDelay = NO_SUGGESTED_RETRY_DELAY;
572        mRetryArray.clear();
573    }
574
575    /**
576     * Set waiting APNs for retrying in case needed.
577     * @param waitingApns Waiting APN list
578     */
579    public void setWaitingApns(ArrayList<ApnSetting> waitingApns) {
580
581        if (waitingApns == null) {
582            log("No waiting APNs provided");
583            return;
584        }
585
586        mWaitingApns = waitingApns;
587
588        // Since we replace the entire waiting APN list, we need to re-config this retry manager.
589        configureRetry(mApnType.equals(PhoneConstants.APN_TYPE_DEFAULT));
590
591        for (ApnSetting apn : mWaitingApns) {
592            apn.permanentFailed = false;
593        }
594
595        log("Setting " + mWaitingApns.size() + " waiting APNs.");
596
597        if (VDBG) {
598            for (int i = 0; i < mWaitingApns.size(); i++) {
599                log("  [" + i + "]:" + mWaitingApns.get(i));
600            }
601        }
602    }
603
604    /**
605     * Get the list of waiting APNs.
606     * @return the list of waiting APNs
607     */
608    public ArrayList<ApnSetting> getWaitingApns() {
609        return mWaitingApns;
610    }
611
612    /**
613     * Save the modem suggested delay for retrying the current APN.
614     * This method is called when we get the suggested delay from RIL.
615     * @param delay The delay in milliseconds
616     */
617    public void setModemSuggestedDelay(long delay) {
618        mModemSuggestedDelay = delay;
619    }
620
621    /**
622     * Get the delay between APN setting trying. This is the fixed delay used for APN setting trying
623     * within the same round, comparing to the exponential delay used for different rounds.
624     * @param failFastEnabled True if fail fast mode enabled, which a shorter delay will be used
625     * @return The delay in milliseconds
626     */
627    public long getInterApnDelay(boolean failFastEnabled) {
628        return (failFastEnabled) ? mFailFastInterApnDelay : mInterApnDelay;
629    }
630
631    public String toString() {
632        return "mApnType=" + mApnType + " mRetryCount=" + mRetryCount +
633                " mMaxRetryCount=" + mMaxRetryCount + " mCurrentApnIndex=" + mCurrentApnIndex +
634                " mSameApnRtryCount=" + mSameApnRetryCount + " mModemSuggestedDelay=" +
635                mModemSuggestedDelay + " mRetryForever=" + mRetryForever +
636                " mConfig={" + mConfig + "}";
637    }
638
639    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
640        pw.println("  RetryManager");
641        pw.println("***************************************");
642
643        pw.println("    config = " + mConfig);
644        pw.println("    mApnType = " + mApnType);
645        pw.println("    mCurrentApnIndex = " + mCurrentApnIndex);
646        pw.println("    mRetryCount = " + mRetryCount);
647        pw.println("    mMaxRetryCount = " + mMaxRetryCount);
648        pw.println("    mSameApnRetryCount = " + mSameApnRetryCount);
649        pw.println("    mModemSuggestedDelay = " + mModemSuggestedDelay);
650
651        if (mWaitingApns != null) {
652            pw.println("    APN list: ");
653            for (int i = 0; i < mWaitingApns.size(); i++) {
654                pw.println("      [" + i + "]=" + mWaitingApns.get(i));
655            }
656        }
657
658        pw.println("***************************************");
659        pw.flush();
660    }
661
662    private void log(String s) {
663        Rlog.d(LOG_TAG, "[" + mApnType + "] " + s);
664    }
665}
666