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