diff options
| -rw-r--r-- | core/java/android/util/NtpTrustedTime.java | 96 | ||||
| -rw-r--r-- | core/java/android/util/TrustedTime.java | 55 | ||||
| -rw-r--r-- | services/java/com/android/server/ThrottleService.java | 183 | ||||
| -rw-r--r-- | services/tests/servicestests/Android.mk | 4 | ||||
| -rw-r--r-- | services/tests/servicestests/AndroidManifest.xml | 4 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java | 400 |
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 + } + } +} |