Tests for ThrottleService, NTP into TrustedTime.

Wrote initial suite of tests for ThrottleService, checking a variety
of edge cases.  Checks going over limits, updating policies, and reset
after cycle elapses.

Moved NTP code in ThrottleService into new TrustedTime interface,
which makes it easier to understand, and allows tests to provide custom
clocks.

Change-Id: I0d62b8b3a169516a2ab2d33025f6fe30dc792be8
This commit is contained in:
Jeff Sharkey
2011-04-25 23:44:11 -07:00
parent cb95894bc7
commit b7342acebc
6 changed files with 662 additions and 80 deletions

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.util;
import android.net.SntpClient;
import android.os.SystemClock;
/**
* {@link TrustedTime} that connects with a remote NTP server as its remote
* trusted time source.
*
* @hide
*/
public class NtpTrustedTime implements TrustedTime {
private String mNtpServer;
private long mNtpTimeout;
private boolean mHasCache;
private long mCachedNtpTime;
private long mCachedNtpElapsedRealtime;
private long mCachedNtpCertainty;
public NtpTrustedTime() {
}
public void setNtpServer(String server, long timeout) {
mNtpServer = server;
mNtpTimeout = timeout;
}
/** {@inheritDoc} */
public boolean forceRefresh() {
if (mNtpServer == null) {
throw new IllegalStateException("Missing NTP server");
}
final SntpClient client = new SntpClient();
if (client.requestTime(mNtpServer, (int) mNtpTimeout)) {
mHasCache = true;
mCachedNtpTime = client.getNtpTime();
mCachedNtpElapsedRealtime = client.getNtpTimeReference();
mCachedNtpCertainty = client.getRoundTripTime() / 2;
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
public boolean hasCache() {
return mHasCache;
}
/** {@inheritDoc} */
public long getCacheAge() {
if (mHasCache) {
return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
} else {
return Long.MAX_VALUE;
}
}
/** {@inheritDoc} */
public long getCacheCertainty() {
if (mHasCache) {
return mCachedNtpCertainty;
} else {
return Long.MAX_VALUE;
}
}
/** {@inheritDoc} */
public long currentTimeMillis() {
if (!mHasCache) {
throw new IllegalStateException("Missing authoritative time source");
}
// current time is age after the last ntp cache; callers who
// want fresh values will hit makeAuthoritative() first.
return mCachedNtpTime + getCacheAge();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.util;
/**
* Interface that provides trusted time information, possibly coming from an NTP
* server. Implementations may cache answers until {@link #forceRefresh()}.
*
* @hide
*/
public interface TrustedTime {
/**
* Force update with an external trusted time source, returning {@code true}
* when successful.
*/
public boolean forceRefresh();
/**
* Check if this instance has cached a response from a trusted time source.
*/
public boolean hasCache();
/**
* Return time since last trusted time source contact, or
* {@link Long#MAX_VALUE} if never contacted.
*/
public long getCacheAge();
/**
* Return certainty of cached trusted time in milliseconds, or
* {@link Long#MAX_VALUE} if never contacted. Smaller values are more
* precise.
*/
public long getCacheCertainty();
/**
* Return current time similar to {@link System#currentTimeMillis()},
* possibly using a cached authoritative time source.
*/
public long currentTimeMillis();
}

View File

@@ -16,6 +16,9 @@
package com.android.server;
import com.android.internal.R;
import com.android.internal.telephony.TelephonyProperties;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -30,7 +33,6 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.INetworkManagementEventObserver;
import android.net.IThrottleManager;
import android.net.SntpClient;
import android.net.ThrottleManager;
import android.os.Binder;
import android.os.Environment;
@@ -47,10 +49,9 @@ import android.os.SystemProperties;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.NtpTrustedTime;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.telephony.TelephonyProperties;
import android.util.TrustedTime;
import java.io.BufferedWriter;
import java.io.File;
@@ -60,11 +61,11 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.GregorianCalendar;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
// TODO - add comments - reference the ThrottleManager for public API
public class ThrottleService extends IThrottleManager.Stub {
@@ -84,6 +85,11 @@ public class ThrottleService extends IThrottleManager.Stub {
private static final int TESTING_RESET_PERIOD_SEC = 60 * 10;
private static final long TESTING_THRESHOLD = 1 * 1024 * 1024;
private static final long MAX_NTP_CACHE_AGE = 24 * 60 * 60 * 1000;
private static final long MAX_NTP_FETCH_WAIT = 20 * 1000;
private long mMaxNtpCacheAge = MAX_NTP_CACHE_AGE;
private int mPolicyPollPeriodSec;
private AtomicLong mPolicyThreshold;
private AtomicInteger mPolicyThrottleValue;
@@ -121,10 +127,24 @@ public class ThrottleService extends IThrottleManager.Stub {
private static final int THROTTLE_INDEX_UNTHROTTLED = 0;
private static final String PROPERTIES_FILE = "/etc/gps.conf";
private String mNtpServer;
private boolean mNtpActive;
private Intent mPollStickyBroadcast;
private TrustedTime mTime;
private static INetworkManagementService getNetworkManagementService() {
final IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
return INetworkManagementService.Stub.asInterface(b);
}
public ThrottleService(Context context) {
// TODO: move to using cached NtpTrustedTime
this(context, getNetworkManagementService(), new NtpTrustedTime(),
context.getResources().getString(R.string.config_datause_iface));
}
public ThrottleService(Context context, INetworkManagementService nmService, TrustedTime time,
String iface) {
if (VDBG) Slog.v(TAG, "Starting ThrottleService");
mContext = context;
@@ -132,17 +152,15 @@ public class ThrottleService extends IThrottleManager.Stub {
mPolicyThrottleValue = new AtomicInteger();
mThrottleIndex = new AtomicInteger();
mNtpActive = false;
mIface = mContext.getResources().getString(R.string.config_datause_iface);
mIface = iface;
mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
Intent pollIntent = new Intent(ACTION_POLL, null);
mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
Intent resetIntent = new Intent(ACTION_RESET, null);
mPendingResetIntent = PendingIntent.getBroadcast(mContext, RESET_REQUEST, resetIntent, 0);
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
mNMService = INetworkManagementService.Stub.asInterface(b);
mNMService = nmService;
mTime = time;
mNotificationManager = (NotificationManager)mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
@@ -189,7 +207,7 @@ public class ThrottleService extends IThrottleManager.Stub {
mMsg = msg;
}
void observe(Context context) {
void register(Context context) {
ContentResolver resolver = context.getContentResolver();
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.THROTTLE_POLLING_SEC), false, this);
@@ -207,6 +225,11 @@ public class ThrottleService extends IThrottleManager.Stub {
Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC), false, this);
}
void unregister(Context context) {
final ContentResolver resolver = context.getContentResolver();
resolver.unregisterContentObserver(this);
}
@Override
public void onChange(boolean selfChange) {
mHandler.obtainMessage(mMsg).sendToTarget();
@@ -220,7 +243,9 @@ public class ThrottleService extends IThrottleManager.Stub {
}
private long ntpToWallTime(long ntpTime) {
long bestNow = getBestTime(true); // do it quickly
// get time quickly without worrying about trusted state
long bestNow = mTime.hasCache() ? mTime.currentTimeMillis()
: System.currentTimeMillis();
long localNow = System.currentTimeMillis();
return localNow + (ntpTime - bestNow);
}
@@ -300,7 +325,7 @@ public class ThrottleService extends IThrottleManager.Stub {
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
dispatchPoll();
}
}, new IntentFilter(ACTION_POLL));
@@ -308,7 +333,7 @@ public class ThrottleService extends IThrottleManager.Stub {
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
dispatchReset();
}
}, new IntentFilter(ACTION_RESET));
@@ -318,7 +343,10 @@ public class ThrottleService extends IThrottleManager.Stub {
File file = new File(PROPERTIES_FILE);
stream = new FileInputStream(file);
properties.load(stream);
mNtpServer = properties.getProperty("NTP_SERVER", null);
final String ntpServer = properties.getProperty("NTP_SERVER", null);
if (mTime instanceof NtpTrustedTime) {
((NtpTrustedTime) mTime).setNtpServer(ntpServer, MAX_NTP_FETCH_WAIT);
}
} catch (IOException e) {
Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
} finally {
@@ -343,9 +371,33 @@ public class ThrottleService extends IThrottleManager.Stub {
}
mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED);
mSettingsObserver.observe(mContext);
mSettingsObserver.register(mContext);
}
void shutdown() {
// TODO: eventually connect with ShutdownThread to persist stats during
// graceful shutdown.
if (mThread != null) {
mThread.quit();
}
if (mSettingsObserver != null) {
mSettingsObserver.unregister(mContext);
}
if (mPollStickyBroadcast != null) {
mContext.removeStickyBroadcast(mPollStickyBroadcast);
}
}
void dispatchPoll() {
mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
}
void dispatchReset() {
mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
}
private static final int EVENT_REBOOT_RECOVERY = 0;
private static final int EVENT_POLICY_CHANGED = 1;
@@ -440,15 +492,17 @@ public class ThrottleService extends IThrottleManager.Stub {
mPolicyNotificationsAllowedMask = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.THROTTLE_NOTIFICATION_TYPE, defaultNotificationType);
mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC);
final int maxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC,
(int) (MAX_NTP_CACHE_AGE / 1000));
mMaxNtpCacheAge = maxNtpCacheAgeSec * 1000;
if (VDBG || (mPolicyThreshold.get() != 0)) {
Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" +
mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() +
", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay +
", noteType=" + mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" +
mMaxNtpCacheAgeSec);
", noteType=" + mPolicyNotificationsAllowedMask + ", mMaxNtpCacheAge=" +
mMaxNtpCacheAge);
}
// force updates
@@ -464,9 +518,15 @@ public class ThrottleService extends IThrottleManager.Stub {
private void onPollAlarm() {
long now = SystemClock.elapsedRealtime();
long next = now + mPolicyPollPeriodSec*1000;
long next = now + mPolicyPollPeriodSec * 1000;
checkForAuthoritativeTime();
// when trusted cache outdated, try refreshing
if (mTime.getCacheAge() > mMaxNtpCacheAge) {
if (mTime.forceRefresh()) {
if (VDBG) Slog.d(TAG, "updated trusted time, reseting alarm");
dispatchReset();
}
}
long incRead = 0;
long incWrite = 0;
@@ -509,6 +569,7 @@ public class ThrottleService extends IThrottleManager.Stub {
broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, getPeriodStartTime(mIface));
broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, getResetTime(mIface));
mContext.sendStickyBroadcast(broadcast);
mPollStickyBroadcast = broadcast;
mAlarmManager.cancel(mPendingPollIntent);
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
@@ -538,7 +599,8 @@ public class ThrottleService extends IThrottleManager.Stub {
// have we spoken with an ntp server yet?
// this is controversial, but we'd rather err towards not throttling
if ((mNtpServer != null) && !mNtpActive) {
if (!mTime.hasCache()) {
Slog.w(TAG, "missing trusted time, skipping throttle check");
return;
}
@@ -697,9 +759,15 @@ public class ThrottleService extends IThrottleManager.Stub {
" bytes read and " + mRecorder.getPeriodTx(0) + " written");
}
long now = getBestTime(false);
// when trusted cache outdated, try refreshing
if (mTime.getCacheAge() > mMaxNtpCacheAge) {
mTime.forceRefresh();
}
if (mNtpActive || (mNtpServer == null)) {
// as long as we have a trusted time cache, we always reset alarms,
// even if the refresh above failed.
if (mTime.hasCache()) {
final long now = mTime.currentTimeMillis();
Calendar end = calculatePeriodEnd(now);
Calendar start = calculatePeriodStart(end);
@@ -714,56 +782,11 @@ public class ThrottleService extends IThrottleManager.Stub {
SystemClock.elapsedRealtime() + offset,
mPendingResetIntent);
} else {
if (VDBG) Slog.d(TAG, "no authoritative time - not resetting period");
if (VDBG) Slog.d(TAG, "no trusted time, not resetting period");
}
}
}
private void checkForAuthoritativeTime() {
if (mNtpActive || (mNtpServer == null)) return;
// will try to get the ntp time and switch to it if found.
// will also cache the time so we don't fetch it repeatedly.
getBestTime(false);
}
private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day
private static final int MAX_NTP_FETCH_WAIT = 20 * 1000;
private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC;
private long cachedNtp;
private long cachedNtpTimestamp;
// if the request is tied to UI and ANR's are a danger, request a fast result
// the regular polling should have updated the cached time recently using the
// slower method (!fast)
private long getBestTime(boolean fast) {
if (mNtpServer != null) {
if (mNtpActive) {
long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp;
if (ntpAge < mMaxNtpCacheAgeSec * 1000 || fast) {
if (VDBG) Slog.v(TAG, "using cached time");
return cachedNtp + ntpAge;
}
}
SntpClient client = new SntpClient();
if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT)) {
cachedNtp = client.getNtpTime();
cachedNtpTimestamp = SystemClock.elapsedRealtime();
if (!mNtpActive) {
mNtpActive = true;
if (VDBG) Slog.d(TAG, "found Authoritative time - reseting alarm");
mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
}
if (VDBG) Slog.v(TAG, "using Authoritative time: " + cachedNtp);
return cachedNtp;
}
}
long time = System.currentTimeMillis();
if (VDBG) Slog.v(TAG, "using User time: " + time);
mNtpActive = false;
return time;
}
// records bytecount data for a given time and accumulates it into larger time windows
// for logging and other purposes
//
@@ -929,7 +952,7 @@ public class ThrottleService extends IThrottleManager.Stub {
private void checkAndDeleteLRUDataFile(File dir) {
File[] files = dir.listFiles();
if (files.length <= MAX_SIMS_SUPPORTED) return;
if (files == null || files.length <= MAX_SIMS_SUPPORTED) return;
if (DBG) Slog.d(TAG, "Too many data files");
do {
File oldest = null;
@@ -949,9 +972,11 @@ public class ThrottleService extends IThrottleManager.Stub {
File newest = null;
File[] files = dir.listFiles();
for (File f : files) {
if ((newest == null) || (newest.lastModified() < f.lastModified())) {
newest = f;
if (files != null) {
for (File f : files) {
if ((newest == null) || (newest.lastModified() < f.lastModified())) {
newest = f;
}
}
}
if (newest == null) {
@@ -1126,7 +1151,7 @@ public class ThrottleService extends IThrottleManager.Stub {
" seconds.");
pw.println("Polling every " + mPolicyPollPeriodSec + " seconds");
pw.println("Current Throttle Index is " + mThrottleIndex.get());
pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec);
pw.println("mMaxNtpCacheAge=" + mMaxNtpCacheAge);
for (int i = 0; i < mRecorder.getPeriodCount(); i++) {
pw.println(" Period[" + i + "] - read:" + mRecorder.getPeriodRx(i) + ", written:" +

View File

@@ -7,7 +7,9 @@ LOCAL_MODULE_TAGS := tests
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := easymocklib
LOCAL_STATIC_JAVA_LIBRARIES := \
easymocklib \
guava
LOCAL_JAVA_LIBRARIES := android.test.runner services

View File

@@ -20,6 +20,10 @@
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application>
<uses-library android:name="android.test.runner" />

View File

@@ -0,0 +1,400 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.AbstractFuture;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.INetworkManagementEventObserver;
import android.net.ThrottleManager;
import android.os.INetworkManagementService;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.text.format.DateUtils;
import android.util.TrustedTime;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Future;
/**
* Tests for {@link ThrottleService}.
*/
public class ThrottleServiceTest extends AndroidTestCase {
private static final String TAG = "ThrottleServiceTest";
private static final long MB_IN_BYTES = 1024 * 1024;
private static final int TEST_KBITPS = 222;
private static final int TEST_RESET_DAY = 11;
private static final String TEST_IFACE = "test0";
private WatchingContext mWatchingContext;
private INetworkManagementService mMockNMService;
private TrustedTime mMockTime;
private ThrottleService mThrottleService;
@Override
public void setUp() throws Exception {
super.setUp();
mWatchingContext = new WatchingContext(getContext());
mMockNMService = createMock(INetworkManagementService.class);
mMockTime = createMock(TrustedTime.class);
mThrottleService = new ThrottleService(
mWatchingContext, mMockNMService, mMockTime, TEST_IFACE);
}
@Override
public void tearDown() throws Exception {
mWatchingContext = null;
mMockNMService = null;
mThrottleService.shutdown();
mThrottleService = null;
clearThrottlePolicy();
super.tearDown();
}
public void testNoPolicyNotThrottled() throws Exception {
expectTimeCurrent();
expectSystemReady();
// provide stats without policy, verify not throttled
expectGetInterfaceCounter(1 * MB_IN_BYTES, 2 * MB_IN_BYTES);
expectSetInterfaceThrottle(-1, -1);
replay(mMockTime, mMockNMService);
systemReady();
verify(mMockTime, mMockNMService);
}
public void testUnderLimitNotThrottled() throws Exception {
setThrottlePolicy(200 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
expectTimeCurrent();
expectSystemReady();
// provide stats under limits, and verify not throttled
expectGetInterfaceCounter(1 * MB_IN_BYTES, 2 * MB_IN_BYTES);
expectSetInterfaceThrottle(-1, -1);
replay(mMockTime, mMockNMService);
systemReady();
verify(mMockTime, mMockNMService);
}
public void testOverLimitThrottled() throws Exception {
setThrottlePolicy(200 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
expectTimeCurrent();
expectSystemReady();
// provide stats over limits, and verify throttled
expectGetInterfaceCounter(500 * MB_IN_BYTES, 600 * MB_IN_BYTES);
expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS);
replay(mMockTime, mMockNMService);
systemReady();
verify(mMockTime, mMockNMService);
}
public void testUnderThenOverLimitThrottled() throws Exception {
setThrottlePolicy(201 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
expectTimeCurrent();
expectSystemReady();
// provide stats right under 201MB limit, verify not throttled
expectGetInterfaceCounter(100 * MB_IN_BYTES, 100 * MB_IN_BYTES);
expectSetInterfaceThrottle(-1, -1);
replay(mMockTime, mMockNMService);
systemReady();
verify(mMockTime, mMockNMService);
reset(mMockTime, mMockNMService);
expectTimeCurrent();
// adjust usage to bump over limit, verify throttle kicks in
expectGetInterfaceCounter(105 * MB_IN_BYTES, 100 * MB_IN_BYTES);
expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS);
// and kick poll event which should throttle
replay(mMockTime, mMockNMService);
forceServicePoll();
verify(mMockTime, mMockNMService);
}
public void testUpdatedPolicyThrottled() throws Exception {
setThrottlePolicy(500 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
expectTimeCurrent();
expectSystemReady();
// provide stats under limit, verify not throttled
expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES);
expectSetInterfaceThrottle(-1, -1);
replay(mMockTime, mMockNMService);
systemReady();
verify(mMockTime, mMockNMService);
reset(mMockTime, mMockNMService);
expectTimeCurrent();
// provide same stats, but verify that modified policy will throttle
expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES);
expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS);
replay(mMockTime, mMockNMService);
// now adjust policy to bump usage over limit
setThrottlePolicy(5 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
// and wait for policy updated broadcast
mWatchingContext.nextBroadcastIntent(ThrottleManager.POLICY_CHANGED_ACTION).get();
verify(mMockTime, mMockNMService);
}
public void testWithPolicyOverLimitThrottledAndRemovedAfterCycle() throws Exception {
setThrottlePolicy(90 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
final long baseTime = System.currentTimeMillis();
expectTime(baseTime);
expectSystemReady();
// provide stats over limit, verify throttle kicks in
expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES);
expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS);
replay(mMockTime, mMockNMService);
systemReady();
verify(mMockTime, mMockNMService);
reset(mMockTime, mMockNMService);
// pretend that time has jumped forward two months
expectTime(baseTime + DateUtils.WEEK_IN_MILLIS * 8);
// provide slightly updated stats, but verify throttle is removed
expectGetInterfaceCounter(60 * MB_IN_BYTES, 60 * MB_IN_BYTES);
expectSetInterfaceThrottle(-1, -1);
// and kick poll event which should throttle
replay(mMockTime, mMockNMService);
forceServiceReset();
verify(mMockTime, mMockNMService);
}
/**
* Persist the given {@link ThrottleService} policy into {@link Settings}.
*/
public void setThrottlePolicy(long thresholdBytes, int valueKbitps, int resetDay) {
final ContentResolver resolver = getContext().getContentResolver();
Settings.Secure.putLong(resolver, Settings.Secure.THROTTLE_THRESHOLD_BYTES, thresholdBytes);
Settings.Secure.putInt(resolver, Settings.Secure.THROTTLE_VALUE_KBITSPS, valueKbitps);
Settings.Secure.putInt(resolver, Settings.Secure.THROTTLE_RESET_DAY, resetDay);
}
/**
* Clear any {@link ThrottleService} policy from {@link Settings}.
*/
public void clearThrottlePolicy() {
final ContentResolver resolver = getContext().getContentResolver();
Settings.Secure.putString(resolver, Settings.Secure.THROTTLE_THRESHOLD_BYTES, null);
Settings.Secure.putString(resolver, Settings.Secure.THROTTLE_VALUE_KBITSPS, null);
Settings.Secure.putString(resolver, Settings.Secure.THROTTLE_RESET_DAY, null);
}
/**
* Expect any {@link TrustedTime} mock calls, and respond with
* {@link System#currentTimeMillis()}.
*/
public void expectTimeCurrent() throws Exception {
expectTime(System.currentTimeMillis());
}
/**
* Expect any {@link TrustedTime} mock calls, and respond with the given
* time in response to {@link TrustedTime#currentTimeMillis()}.
*/
public void expectTime(long currentTime) throws Exception {
expect(mMockTime.forceRefresh()).andReturn(false).anyTimes();
expect(mMockTime.hasCache()).andReturn(true).anyTimes();
expect(mMockTime.currentTimeMillis()).andReturn(currentTime).anyTimes();
expect(mMockTime.getCacheAge()).andReturn(0L).anyTimes();
expect(mMockTime.getCacheCertainty()).andReturn(0L).anyTimes();
}
/**
* Expect {@link ThrottleService#systemReady()} generated calls, such as
* connecting with {@link NetworkManagementService} mock.
*/
public void expectSystemReady() throws Exception {
mMockNMService.registerObserver(isA(INetworkManagementEventObserver.class));
expectLastCall().atLeastOnce();
}
/**
* Expect {@link NetworkManagementService#getInterfaceRxCounter} mock calls,
* responding with the given counter values.
*/
public void expectGetInterfaceCounter(long rx, long tx) throws Exception {
expect(mMockNMService.getInterfaceRxCounter(isA(String.class))).andReturn(rx).atLeastOnce();
expect(mMockNMService.getInterfaceTxCounter(isA(String.class))).andReturn(tx).atLeastOnce();
}
/**
* Expect {@link NetworkManagementService#setInterfaceThrottle} mock call
* with the specified parameters.
*/
public void expectSetInterfaceThrottle(int rx, int tx) throws Exception {
mMockNMService.setInterfaceThrottle(isA(String.class), eq(rx), eq(tx));
expectLastCall().atLeastOnce();
}
/**
* Dispatch {@link ThrottleService#systemReady()} and block until finished.
*/
public void systemReady() throws Exception {
final Future<Intent> policyChanged = mWatchingContext.nextBroadcastIntent(
ThrottleManager.POLICY_CHANGED_ACTION);
final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent(
ThrottleManager.THROTTLE_POLL_ACTION);
mThrottleService.systemReady();
// wait for everything to settle; for policy to update and for first poll
policyChanged.get();
pollAction.get();
}
/**
* Dispatch {@link ThrottleService#dispatchPoll()} and block until finished.
*/
public void forceServicePoll() throws Exception {
// during systemReady() service already pushed a sticky broadcast, so we
// need to skip the immediate and wait for the updated sticky.
final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent(
ThrottleManager.THROTTLE_POLL_ACTION);
mThrottleService.dispatchPoll();
pollAction.get();
}
/**
* Dispatch {@link ThrottleService#dispatchReset()} and block until finished.
*/
public void forceServiceReset() throws Exception {
// during systemReady() service already pushed a sticky broadcast, so we
// need to skip the immediate and wait for the updated sticky.
final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent(
ThrottleManager.THROTTLE_POLL_ACTION);
mThrottleService.dispatchReset();
pollAction.get();
}
/**
* {@link ContextWrapper} that can attach listeners for upcoming
* {@link Context#sendBroadcast(Intent)}.
*/
private static class WatchingContext extends ContextWrapper {
private List<LocalBroadcastReceiver> mReceivers = Lists.newArrayList();
public class LocalBroadcastReceiver extends AbstractFuture<Intent> {
private IntentFilter mFilter;
public LocalBroadcastReceiver(IntentFilter filter) {
mFilter = filter;
}
public boolean dispatchBroadcast(Intent intent) {
if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
set(intent);
return true;
} else {
return false;
}
}
}
public WatchingContext(Context base) {
super(base);
}
public Future<Intent> nextBroadcastIntent(String action) {
return nextBroadcastIntent(new IntentFilter(action));
}
public Future<Intent> nextBroadcastIntent(IntentFilter filter) {
final LocalBroadcastReceiver receiver = new LocalBroadcastReceiver(filter);
synchronized (mReceivers) {
mReceivers.add(receiver);
}
return receiver;
}
@Override
public void sendBroadcast(Intent intent) {
synchronized (mReceivers) {
final Iterator<LocalBroadcastReceiver> i = mReceivers.iterator();
while (i.hasNext()) {
final LocalBroadcastReceiver receiver = i.next();
if (receiver.dispatchBroadcast(intent)) {
i.remove();
}
}
}
}
@Override
public void sendStickyBroadcast(Intent intent) {
sendBroadcast(intent);
}
@Override
public void removeStickyBroadcast(Intent intent) {
// ignored
}
}
}