summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
+ }
+ }
+}