Methods Summary |
---|
protected long | currentTimeMillis()Gets the current time. Can be overridden for unit testing.
return System.currentTimeMillis();
|
public long | getLastAttemptTimeMillis()Return the last time the operation was attempted. Does not modify any state.
return Math.max(
mStorage.getLong(PREFIX + "lastSuccessTimeMillis", 0),
mStorage.getLong(PREFIX + "lastErrorTimeMillis", 0));
|
public long | getLastSuccessTimeMillis()Return the last time the operation completed. Does not modify any state.
return mStorage.getLong(PREFIX + "lastSuccessTimeMillis", 0);
|
public long | getNextTimeMillis(com.android.common.OperationScheduler$Options options)Compute the time of the next operation. Does not modify any state
(unless the clock rolls backwards, in which case timers are reset).
boolean enabledState = mStorage.getBoolean(PREFIX + "enabledState", true);
if (!enabledState) return Long.MAX_VALUE;
boolean permanentError = mStorage.getBoolean(PREFIX + "permanentError", false);
if (permanentError) return Long.MAX_VALUE;
// We do quite a bit of limiting to prevent a clock rollback from totally
// hosing the scheduler. Times which are supposed to be in the past are
// clipped to the current time so we don't languish forever.
int errorCount = mStorage.getInt(PREFIX + "errorCount", 0);
long now = currentTimeMillis();
long lastSuccessTimeMillis = getTimeBefore(PREFIX + "lastSuccessTimeMillis", now);
long lastErrorTimeMillis = getTimeBefore(PREFIX + "lastErrorTimeMillis", now);
long triggerTimeMillis = mStorage.getLong(PREFIX + "triggerTimeMillis", Long.MAX_VALUE);
long moratoriumSetMillis = getTimeBefore(PREFIX + "moratoriumSetTimeMillis", now);
long moratoriumTimeMillis = getTimeBefore(PREFIX + "moratoriumTimeMillis",
moratoriumSetMillis + options.maxMoratoriumMillis);
long time = triggerTimeMillis;
if (options.periodicIntervalMillis > 0) {
time = Math.min(time, lastSuccessTimeMillis + options.periodicIntervalMillis);
}
time = Math.max(time, moratoriumTimeMillis);
time = Math.max(time, lastSuccessTimeMillis + options.minTriggerMillis);
if (errorCount > 0) {
int shift = errorCount-1;
// backoffExponentialMillis is an int, so we can safely
// double it 30 times without overflowing a long.
if (shift > 30) shift = 30;
long backoff = options.backoffFixedMillis +
(options.backoffIncrementalMillis * errorCount) +
(((long)options.backoffExponentialMillis) << shift);
// Treat backoff like a moratorium: don't let the backoff
// time grow too large.
if (moratoriumTimeMillis > 0 && backoff > moratoriumTimeMillis) {
backoff = moratoriumTimeMillis;
}
time = Math.max(time, lastErrorTimeMillis + backoff);
}
return time;
|
private long | getTimeBefore(java.lang.String name, long max)Fetch a {@link SharedPreferences} property, but force it to be before
a certain time, updating the value if necessary. This is to recover
gracefully from clock rollbacks which could otherwise strand our timers.
long time = mStorage.getLong(name, 0);
if (time > max) {
time = max;
SharedPreferencesCompat.apply(mStorage.edit().putLong(name, time));
}
return time;
|
public void | onPermanentError()Report a permanent error that will not go away until further notice.
No operation will be scheduled until {@link #resetPermanentError()}
is called. Commonly used for authentication failures (which are reset
when the accounts database is updated).
SharedPreferencesCompat.apply(mStorage.edit().putBoolean(PREFIX + "permanentError", true));
|
public void | onSuccess()Report successful completion of an operation. Resets all error
counters, clears any trigger directives, and records the success.
resetTransientError();
resetPermanentError();
SharedPreferencesCompat.apply(mStorage.edit()
.remove(PREFIX + "errorCount")
.remove(PREFIX + "lastErrorTimeMillis")
.remove(PREFIX + "permanentError")
.remove(PREFIX + "triggerTimeMillis")
.putLong(PREFIX + "lastSuccessTimeMillis", currentTimeMillis()));
|
public void | onTransientError()Report a transient error (usually a network failure). Increments
the error count and records the time of the latest error for backoff
purposes.
SharedPreferences.Editor editor = mStorage.edit();
editor.putLong(PREFIX + "lastErrorTimeMillis", currentTimeMillis());
editor.putInt(PREFIX + "errorCount",
mStorage.getInt(PREFIX + "errorCount", 0) + 1);
SharedPreferencesCompat.apply(editor);
|
public static com.android.common.OperationScheduler$Options | parseOptions(java.lang.String spec, com.android.common.OperationScheduler$Options options)Parse scheduler options supplied in this string form:
backoff=(fixed)+(incremental)[+(exponential)] max=(maxmoratorium) min=(mintrigger) [period=](interval)
All values are times in (possibly fractional) seconds (not milliseconds).
Omitted settings are left at whatever existing default value was passed in.
The default options: backoff=0+5 max=86400 min=0 period=0
Fractions are OK: backoff=+2.5 period=10.0
The "period=" can be omitted: 3600
for (String param : spec.split(" +")) {
if (param.length() == 0) continue;
if (param.startsWith("backoff=")) {
String[] pieces = param.substring(8).split("\\+");
if (pieces.length > 3) {
throw new IllegalArgumentException("bad value for backoff: [" + spec + "]");
}
if (pieces.length > 0 && pieces[0].length() > 0) {
options.backoffFixedMillis = parseSeconds(pieces[0]);
}
if (pieces.length > 1 && pieces[1].length() > 0) {
options.backoffIncrementalMillis = parseSeconds(pieces[1]);
}
if (pieces.length > 2 && pieces[2].length() > 0) {
options.backoffExponentialMillis = (int)parseSeconds(pieces[2]);
}
} else if (param.startsWith("max=")) {
options.maxMoratoriumMillis = parseSeconds(param.substring(4));
} else if (param.startsWith("min=")) {
options.minTriggerMillis = parseSeconds(param.substring(4));
} else if (param.startsWith("period=")) {
options.periodicIntervalMillis = parseSeconds(param.substring(7));
} else {
options.periodicIntervalMillis = parseSeconds(param);
}
}
return options;
|
private static long | parseSeconds(java.lang.String param)
return (long) (Float.parseFloat(param) * 1000);
|
public void | resetPermanentError()Reset any permanent error status set by {@link #onPermanentError},
allowing operations to be scheduled as normal.
SharedPreferencesCompat.apply(mStorage.edit().remove(PREFIX + "permanentError"));
|
public void | resetTransientError()Reset all transient error counts, allowing the next operation to proceed
immediately without backoff. Commonly used on network state changes, when
partial progress occurs (some data received), and in other circumstances
where there is reason to hope things might start working better.
SharedPreferencesCompat.apply(mStorage.edit().remove(PREFIX + "errorCount"));
|
public void | setEnabledState(boolean enabled)Enable or disable all operations. When disabled, all calls to
{@link #getNextTimeMillis} return {@link Long#MAX_VALUE}.
Commonly used when data network availability goes up and down.
SharedPreferencesCompat.apply(
mStorage.edit().putBoolean(PREFIX + "enabledState", enabled));
|
public boolean | setMoratoriumTimeHttp(java.lang.String retryAfter)Forbid any operations until after a certain time, as specified in
the format used by the HTTP "Retry-After" header.
Limited by {@link Options#maxMoratoriumMillis}.
try {
long ms = Long.valueOf(retryAfter) * 1000;
setMoratoriumTimeMillis(ms + currentTimeMillis());
return true;
} catch (NumberFormatException nfe) {
try {
setMoratoriumTimeMillis(AndroidHttpClient.parseDate(retryAfter));
return true;
} catch (IllegalArgumentException iae) {
return false;
}
}
|
public void | setMoratoriumTimeMillis(long millis)Forbid any operations until after a certain (absolute) time.
Limited by {@link Options#maxMoratoriumMillis}.
SharedPreferencesCompat.apply(mStorage.edit()
.putLong(PREFIX + "moratoriumTimeMillis", millis)
.putLong(PREFIX + "moratoriumSetTimeMillis", currentTimeMillis()));
|
public void | setTriggerTimeMillis(long millis)Request an operation to be performed at a certain time. The actual
scheduled time may be affected by error backoff logic and defined
minimum intervals. Use {@link Long#MAX_VALUE} to disable triggering.
SharedPreferencesCompat.apply(
mStorage.edit().putLong(PREFIX + "triggerTimeMillis", millis));
|
public java.lang.String | toString()Return a string description of the scheduler state for debugging.
StringBuilder out = new StringBuilder("[OperationScheduler:");
TreeMap<String, Object> copy = new TreeMap<String, Object>(mStorage.getAll()); // Sort keys
for (Map.Entry<String, Object> e : copy.entrySet()) {
String key = e.getKey();
if (key.startsWith(PREFIX)) {
if (key.endsWith("TimeMillis")) {
Time time = new Time();
time.set((Long) e.getValue());
out.append(" ").append(key.substring(PREFIX.length(), key.length() - 10));
out.append("=").append(time.format("%Y-%m-%d/%H:%M:%S"));
} else {
out.append(" ").append(key.substring(PREFIX.length()));
Object v = e.getValue();
if (v == null) {
out.append("=(null)");
} else {
out.append("=").append(v.toString());
}
}
}
}
return out.append("]").toString();
|