summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Sharkey <jsharkey@android.com> 2011-04-25 23:44:11 -0700
committer Jeff Sharkey <jsharkey@android.com> 2011-04-28 12:28:36 -0700
commitb7342acebcb7e5dc7da0cda77fbddf50e7dfdd7c (patch)
tree40e6e8fe8708c087640f8a26bffdf20eac8e779e
parentcb95894bc7ed2f5f4fc541be62631e5456f442d3 (diff)
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
-rw-r--r--core/java/android/util/NtpTrustedTime.java96
-rw-r--r--core/java/android/util/TrustedTime.java55
-rw-r--r--services/java/com/android/server/ThrottleService.java183
-rw-r--r--services/tests/servicestests/Android.mk4
-rw-r--r--services/tests/servicestests/AndroidManifest.xml4
-rw-r--r--services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java400
6 files changed, 662 insertions, 80 deletions
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
new file mode 100644
index 000000000000..729c5064204e
--- /dev/null
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -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();
+ }
+}
diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java
new file mode 100644
index 000000000000..263d7821a085
--- /dev/null
+++ b/core/java/android/util/TrustedTime.java
@@ -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();
+}
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index d841cb3a7766..02332b7aa918 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -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:" +
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 186b3490e502..295f324f9582 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -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
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 2fcce785178c..f8d14265217c 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -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" />
diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
new file mode 100644
index 000000000000..6f55f4600dcd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
@@ -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
+ }
+ }
+}