diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 93e30ffabd9d6..b165d34b147b2 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -75,6 +75,9 @@ import static android.net.wifi.WifiManager.EXTRA_CHANGE_REASON; import static android.net.wifi.WifiManager.EXTRA_NETWORK_INFO; import static android.net.wifi.WifiManager.EXTRA_WIFI_CONFIGURATION; import static android.net.wifi.WifiManager.EXTRA_WIFI_INFO; +import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; +import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT; +import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static com.android.internal.util.ArrayUtils.appendInt; @@ -141,6 +144,7 @@ import android.os.IDeviceIdleController; import android.os.INetworkManagementService; import android.os.Message; import android.os.MessageQueue.IdleHandler; +import android.os.PersistableBundle; import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.Process; @@ -153,6 +157,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -174,6 +179,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; @@ -205,6 +211,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Calendar; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -321,6 +328,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private UsageStatsManagerInternal mUsageStats; private final TrustedTime mTime; private final UserManager mUserManager; + private final CarrierConfigManager mCarrierConfigManager; private IConnectivityManager mConnManager; private INotificationManager mNotifManager; @@ -472,6 +480,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { Context.DEVICE_IDLE_CONTROLLER)); mTime = checkNotNull(time, "missing TrustedTime"); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); mIPm = pm; HandlerThread thread = new HandlerThread(TAG); @@ -756,6 +765,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { WifiManager.NETWORK_STATE_CHANGED_ACTION); mContext.registerReceiver(mWifiStateReceiver, wifiStateFilter, null, mHandler); + // listen for carrier config changes to update data cycle information + final IntentFilter carrierConfigFilter = new IntentFilter( + ACTION_CARRIER_CONFIG_CHANGED); + mContext.registerReceiver(mCarrierConfigReceiver, carrierConfigFilter, null, mHandler); + mUsageStats.addAppIdleStateChangeListener(new AppIdleStateChangeListener()); // tell systemReady() that the service has been initialized initCompleteSignal.countDown(); @@ -1291,6 +1305,213 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } }; + /** + * Update mobile policies with data cycle information from {@link CarrierConfigManager} + * if necessary. + * + * @param subId that has its associated NetworkPolicy updated if necessary + * @return if any policies were updated + */ + private boolean maybeUpdateMobilePolicyCycleNL(int subId) { + if (LOGV) Slog.v(TAG, "maybeUpdateMobilePolicyCycleNL()"); + final PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId); + + if (config == null) { + return false; + } + + boolean policyUpdated = false; + final String subscriberId = TelephonyManager.from(mContext).getSubscriberId(subId); + + // find and update the mobile NetworkPolicy for this subscriber id + final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true); + for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) { + final NetworkTemplate template = mNetworkPolicy.keyAt(i); + if (template.matches(probeIdent)) { + NetworkPolicy policy = mNetworkPolicy.valueAt(i); + + // only update the policy if the user didn't change any of the defaults. + if (!policy.inferred) { + // TODO: inferred could be split, so that if a user changes their data limit or + // warning, it doesn't prevent their cycle date from being updated. + if (LOGD) Slog.v(TAG, "Didn't update NetworkPolicy because policy.inferred"); + continue; + } + + final int cycleDay = getCycleDayFromCarrierConfig(config, policy.cycleDay); + final long warningBytes = getWarningBytesFromCarrierConfig(config, + policy.warningBytes); + final long limitBytes = getLimitBytesFromCarrierConfig(config, + policy.limitBytes); + + if (policy.cycleDay == cycleDay && + policy.warningBytes == warningBytes && + policy.limitBytes == limitBytes) { + continue; + } + + policyUpdated = true; + policy.cycleDay = cycleDay; + policy.warningBytes = warningBytes; + policy.limitBytes = limitBytes; + + if (LOGD) { + Slog.d(TAG, "Updated NetworkPolicy " + policy + " which matches subscriber " + + NetworkIdentity.scrubSubscriberId(subscriberId) + + " from CarrierConfigManager"); + } + } + } + + return policyUpdated; + } + + /** + * Returns the cycle day that should be used for a mobile NetworkPolicy. + * + * It attempts to get an appropriate cycle day from the passed in CarrierConfig. If it's unable + * to do so, it returns the fallback value. + * + * @param config The CarrierConfig to read the value from. + * @param fallbackCycleDay to return if the CarrierConfig can't be read. + * @return cycleDay to use in the mobile NetworkPolicy. + */ + @VisibleForTesting + public int getCycleDayFromCarrierConfig(@Nullable PersistableBundle config, + int fallbackCycleDay) { + if (config == null) { + return fallbackCycleDay; + } + int cycleDay = + config.getInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT); + if (cycleDay == DATA_CYCLE_USE_PLATFORM_DEFAULT) { + return fallbackCycleDay; + } + // validate cycleDay value + final Calendar cal = Calendar.getInstance(); + if (cycleDay < cal.getMinimum(Calendar.DAY_OF_MONTH) || + cycleDay > cal.getMaximum(Calendar.DAY_OF_MONTH)) { + Slog.e(TAG, "Invalid date in " + + "CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT: " + cycleDay); + return fallbackCycleDay; + } + return cycleDay; + } + + /** + * Returns the warning bytes that should be used for a mobile NetworkPolicy. + * + * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable + * to do so, it returns the fallback value. + * + * @param config The CarrierConfig to read the value from. + * @param fallbackWarningBytes to return if the CarrierConfig can't be read. + * @return warningBytes to use in the mobile NetworkPolicy. + */ + @VisibleForTesting + public long getWarningBytesFromCarrierConfig(@Nullable PersistableBundle config, + long fallbackWarningBytes) { + if (config == null) { + return fallbackWarningBytes; + } + long warningBytes = + config.getLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG); + + if (warningBytes == DATA_CYCLE_THRESHOLD_DISABLED) { + return WARNING_DISABLED; + } else if (warningBytes == DATA_CYCLE_USE_PLATFORM_DEFAULT) { + return getPlatformDefaultWarningBytes(); + } else if (warningBytes < 0) { + Slog.e(TAG, "Invalid value in " + + "CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG; expected a " + + "non-negative value but got: " + warningBytes); + return fallbackWarningBytes; + } + + return warningBytes; + } + + /** + * Returns the limit bytes that should be used for a mobile NetworkPolicy. + * + * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable + * to do so, it returns the fallback value. + * + * @param config The CarrierConfig to read the value from. + * @param fallbackLimitBytes to return if the CarrierConfig can't be read. + * @return limitBytes to use in the mobile NetworkPolicy. + */ + @VisibleForTesting + public long getLimitBytesFromCarrierConfig(@Nullable PersistableBundle config, + long fallbackLimitBytes) { + if (config == null) { + return fallbackLimitBytes; + } + long limitBytes = + config.getLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG); + + if (limitBytes == DATA_CYCLE_THRESHOLD_DISABLED) { + return LIMIT_DISABLED; + } else if (limitBytes == DATA_CYCLE_USE_PLATFORM_DEFAULT) { + return getPlatformDefaultLimitBytes(); + } else if (limitBytes < 0) { + Slog.e(TAG, "Invalid value in " + + "CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG; expected a " + + "non-negative value but got: " + limitBytes); + return fallbackLimitBytes; + } + return limitBytes; + } + + /** + * Receiver that watches for {@link CarrierConfigManager} to be changed. + */ + private BroadcastReceiver mCarrierConfigReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // No need to do a permission check, because the ACTION_CARRIER_CONFIG_CHANGED + // broadcast is protected and can't be spoofed. Runs on a background handler thread. + + if (!intent.hasExtra(PhoneConstants.SUBSCRIPTION_KEY)) { + return; + } + final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, -1); + final TelephonyManager tele = TelephonyManager.from(mContext); + final String subscriberId = tele.getSubscriberId(subId); + + maybeRefreshTrustedTime(); + synchronized (mUidRulesFirstLock) { + synchronized (mNetworkPoliciesSecondLock) { + final boolean added = ensureActiveMobilePolicyNL(subId, subscriberId); + if (added) return; + final boolean updated = maybeUpdateMobilePolicyCycleNL(subId); + if (!updated) return; + // update network and notification rules, as the data cycle changed and it's + // possible that we should be triggering warnings/limits now + handleNetworkPoliciesUpdateAL(true); + } + } + } + }; + + /** + * Handles all tasks that need to be run after a new network policy has been set, or an existing + * one has been updated. + * + * @param shouldNormalizePolicies true iff network policies need to be normalized after the + * update. + */ + void handleNetworkPoliciesUpdateAL(boolean shouldNormalizePolicies) { + if (shouldNormalizePolicies) { + normalizePoliciesNL(); + } + updateNetworkEnabledNL(); + updateNetworkRulesNL(); + updateNotificationsNL(); + writePolicyAL(); + } + /** * Proactively control network data connections when they exceed * {@link NetworkPolicy#limitBytes}. @@ -1516,11 +1737,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int[] subIds = sub.getActiveSubscriptionIdList(); for (int subId : subIds) { final String subscriberId = tele.getSubscriberId(subId); - ensureActiveMobilePolicyNL(subscriberId); + ensureActiveMobilePolicyNL(subId, subscriberId); } } - private void ensureActiveMobilePolicyNL(String subscriberId) { + /** + * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we + * have at least a default mobile policy defined. + * + * @param subId to build a default policy for + * @param subscriberId that we check for an existing policy + * @return true if a mobile network policy was added, or false one already existed. + */ + private boolean ensureActiveMobilePolicyNL(int subId, String subscriberId) { // Poke around to see if we already have a policy final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true); @@ -1531,33 +1760,51 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { Slog.d(TAG, "Found template " + template + " which matches subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId)); } - return; + return false; } } Slog.i(TAG, "No policy for subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId) + "; generating default policy"); + final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId); + addNetworkPolicyNL(policy); + return true; + } - // Build default mobile policy, and assume usage cycle starts today + private long getPlatformDefaultWarningBytes() { final int dataWarningConfig = mContext.getResources().getInteger( com.android.internal.R.integer.config_networkPolicyDefaultWarning); - final long warningBytes; if (dataWarningConfig == WARNING_DISABLED) { - warningBytes = WARNING_DISABLED; + return WARNING_DISABLED; } else { - warningBytes = dataWarningConfig * MB_IN_BYTES; + return dataWarningConfig * MB_IN_BYTES; } + } + private long getPlatformDefaultLimitBytes() { + return LIMIT_DISABLED; + } + + @VisibleForTesting + public NetworkPolicy buildDefaultMobilePolicy(int subId, String subscriberId) { + PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId); + + // assume usage cycle starts today final Time time = new Time(); time.setToNow(); - final int cycleDay = time.monthDay; final String cycleTimezone = time.timezone; + final int cycleDay = getCycleDayFromCarrierConfig(config, time.monthDay); + final long warningBytes = getWarningBytesFromCarrierConfig(config, + getPlatformDefaultWarningBytes()); + final long limitBytes = getLimitBytesFromCarrierConfig(config, + getPlatformDefaultLimitBytes()); + final NetworkTemplate template = buildTemplateMobileAll(subscriberId); final NetworkPolicy policy = new NetworkPolicy(template, cycleDay, cycleTimezone, - warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, true); - addNetworkPolicyNL(policy); + warningBytes, limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, true); + return policy; } private void readPolicyAL() { @@ -2025,10 +2272,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { normalizePoliciesNL(policies); - updateNetworkEnabledNL(); - updateNetworkRulesNL(); - updateNotificationsNL(); - writePolicyAL(); + handleNetworkPoliciesUpdateAL(false); } } } finally { @@ -2125,11 +2369,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { throw new IllegalArgumentException("unexpected type"); } - normalizePoliciesNL(); - updateNetworkEnabledNL(); - updateNetworkRulesNL(); - updateNotificationsNL(); - writePolicyAL(); + handleNetworkPoliciesUpdateAL(true); } } } @@ -2360,11 +2600,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mNetworkPolicy.valueAt(i).clearSnooze(); } - normalizePoliciesNL(); - updateNetworkEnabledNL(); - updateNetworkRulesNL(); - updateNotificationsNL(); - writePolicyAL(); + handleNetworkPoliciesUpdateAL(true); fout.println("Cleared snooze timestamps"); return; diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 29c6f89e5fa07..dbba727706565 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -19,6 +19,7 @@ package com.android.server; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkPolicy.LIMIT_DISABLED; +import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; @@ -26,8 +27,15 @@ import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.computeNextCycleBoundary; import static android.net.NetworkPolicyManager.uidPoliciesToString; +import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; +import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; +import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT; +import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED; +import static android.telephony.CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG; +import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG; +import static android.telephony.CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.Time.TIMEZONE_UTC; @@ -94,18 +102,24 @@ import android.net.NetworkStats; import android.net.NetworkTemplate; import android.os.Binder; import android.os.INetworkManagementService; +import android.os.PersistableBundle; import android.os.PowerManagerInternal; import android.os.PowerSaveState; +import android.os.RemoteException; import android.os.UserHandle; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.format.Time; import android.util.Log; import android.util.TrustedTime; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent; import com.android.server.net.NetworkPolicyManagerInternal; @@ -143,6 +157,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Arrays; +import java.util.Calendar; import java.util.LinkedHashSet; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -200,6 +215,9 @@ public class NetworkPolicyManagerServiceTest { private @Mock INotificationManager mNotifManager; private @Mock PackageManager mPackageManager; private @Mock IPackageManager mIpm; + private @Mock SubscriptionManager mSubscriptionManager; + private @Mock CarrierConfigManager mCarrierConfigManager; + private @Mock TelephonyManager mTelephonyManager; private static ActivityManagerInternal mActivityManagerInternal; @@ -214,6 +232,12 @@ public class NetworkPolicyManagerServiceTest { private long mElapsedRealtime; private static final int USER_ID = 0; + private static final int FAKE_SUB_ID = 3737373; + private static final String FAKE_SUBSCRIBER_ID = "FAKE_SUB_ID"; + private static final int DEFAULT_CYCLE_DAY = 1; + private static final int INVALID_CARRIER_CONFIG_VALUE = -9999; + private long mDefaultWarningBytes; // filled in with the actual default before tests are run + private long mDefaultLimitBytes; // filled in with the actual default before tests are run private static final int APP_ID_A = android.os.Process.FIRST_APPLICATION_UID + 4; private static final int APP_ID_B = android.os.Process.FIRST_APPLICATION_UID + 8; @@ -235,6 +259,8 @@ public class NetworkPolicyManagerServiceTest { @BeforeClass public static void registerLocalServices() { + final PowerManagerInternal powerManager = addLocalServiceMock(PowerManagerInternal.class); + when(powerManager.getLowPowerState(anyInt())).thenReturn(mock(PowerSaveState.class)); addLocalServiceMock(DeviceIdleController.LocalService.class); final UsageStatsManagerInternal usageStats = addLocalServiceMock(UsageStatsManagerInternal.class); @@ -255,7 +281,8 @@ public class NetworkPolicyManagerServiceTest { setCurrentTimeMillis(TEST_START); - // intercept various broadcasts, and pretend that uids have packages + // Intercept various broadcasts, and pretend that uids have packages. + // Also return mock service instances for a few critical services. mServiceContext = new BroadcastInterceptingContext(context) { @Override public PackageManager getPackageManager() { @@ -266,6 +293,20 @@ public class NetworkPolicyManagerServiceTest { public void startActivity(Intent intent) { // ignored } + + @Override + public Object getSystemService(String name) { + switch (name) { + case Context.TELEPHONY_SUBSCRIPTION_SERVICE: + return mSubscriptionManager; + case Context.CARRIER_CONFIG_SERVICE: + return mCarrierConfigManager; + case Context.TELEPHONY_SERVICE: + return mTelephonyManager; + default: + return super.getSystemService(name); + } + } }; setNetpolicyXml(context); @@ -321,6 +362,10 @@ public class NetworkPolicyManagerServiceTest { ArgumentCaptor.forClass(INetworkManagementEventObserver.class); verify(mNetworkManager).registerObserver(networkObserver.capture()); mNetworkObserver = networkObserver.getValue(); + + NetworkPolicy defaultPolicy = mService.buildDefaultMobilePolicy(0, ""); + mDefaultWarningBytes = defaultPolicy.warningBytes; + mDefaultLimitBytes = defaultPolicy.limitBytes; } @After @@ -1132,6 +1177,269 @@ public class NetworkPolicyManagerServiceTest { } } + private void assertCycleDayAsExpected(PersistableBundle config, int carrierCycleDay, + boolean expectValid) { + config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, carrierCycleDay); + int actualCycleDay = mService.getCycleDayFromCarrierConfig(config, + INVALID_CARRIER_CONFIG_VALUE); + if (expectValid) { + assertEquals(carrierCycleDay, actualCycleDay); + } else { + // INVALID_CARRIER_CONFIG_VALUE is returned for invalid values + assertEquals(INVALID_CARRIER_CONFIG_VALUE, actualCycleDay); + } + } + + @Test + public void testGetCycleDayFromCarrierConfig() { + PersistableBundle config = CarrierConfigManager.getDefaultConfig(); + final Calendar cal = Calendar.getInstance(); + int actualCycleDay; + + config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, DATA_CYCLE_USE_PLATFORM_DEFAULT); + actualCycleDay = mService.getCycleDayFromCarrierConfig(config, DEFAULT_CYCLE_DAY); + assertEquals(DEFAULT_CYCLE_DAY, actualCycleDay); + + // null config returns a default value + actualCycleDay = mService.getCycleDayFromCarrierConfig(null, DEFAULT_CYCLE_DAY); + assertEquals(DEFAULT_CYCLE_DAY, actualCycleDay); + + // Sane, non-default values + assertCycleDayAsExpected(config, 1, true); + assertCycleDayAsExpected(config, cal.getMaximum(Calendar.DAY_OF_MONTH), true); + assertCycleDayAsExpected(config, cal.getMinimum(Calendar.DAY_OF_MONTH), true); + + // Invalid values + assertCycleDayAsExpected(config, 0, false); + assertCycleDayAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, false); + assertCycleDayAsExpected(config, cal.getMaximum(Calendar.DAY_OF_MONTH) + 1, false); + assertCycleDayAsExpected(config, cal.getMinimum(Calendar.DAY_OF_MONTH) - 5, false); + } + + private void assertWarningBytesAsExpected(PersistableBundle config, long carrierWarningBytes, + long expected) { + config.putLong(KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, carrierWarningBytes); + long actualWarning = mService.getWarningBytesFromCarrierConfig(config, + INVALID_CARRIER_CONFIG_VALUE); + assertEquals(expected, actualWarning); + } + + @Test + public void testGetWarningBytesFromCarrierConfig() { + PersistableBundle config = CarrierConfigManager.getDefaultConfig(); + long actualWarningBytes; + + assertWarningBytesAsExpected(config, DATA_CYCLE_USE_PLATFORM_DEFAULT, + mDefaultWarningBytes); + assertWarningBytesAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, WARNING_DISABLED); + assertWarningBytesAsExpected(config, 0, 0); + // not a valid value + assertWarningBytesAsExpected(config, -1000, INVALID_CARRIER_CONFIG_VALUE); + + // null config returns a default value + actualWarningBytes = mService.getWarningBytesFromCarrierConfig(null, mDefaultWarningBytes); + assertEquals(mDefaultWarningBytes, actualWarningBytes); + } + + private void assertLimitBytesAsExpected(PersistableBundle config, long carrierWarningBytes, + long expected) { + config.putLong(KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, carrierWarningBytes); + long actualWarning = mService.getLimitBytesFromCarrierConfig(config, + INVALID_CARRIER_CONFIG_VALUE); + assertEquals(expected, actualWarning); + } + + @Test + public void testGetLimitBytesFromCarrierConfig() { + PersistableBundle config = CarrierConfigManager.getDefaultConfig(); + long actualLimitBytes; + + assertLimitBytesAsExpected(config, DATA_CYCLE_USE_PLATFORM_DEFAULT, + mDefaultLimitBytes); + assertLimitBytesAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, LIMIT_DISABLED); + assertLimitBytesAsExpected(config, 0, 0); + // not a valid value + assertLimitBytesAsExpected(config, -1000, INVALID_CARRIER_CONFIG_VALUE); + + // null config returns a default value + actualLimitBytes = mService.getWarningBytesFromCarrierConfig(null, mDefaultLimitBytes); + assertEquals(mDefaultLimitBytes, actualLimitBytes); + } + + private PersistableBundle setupUpdateMobilePolicyCycleTests() throws RemoteException { + when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]); + when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID}); + when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID); + PersistableBundle bundle = CarrierConfigManager.getDefaultConfig(); + when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(bundle); + setNetworkPolicies(buildDefaultFakeMobilePolicy()); + return bundle; + } + + @Test + public void testUpdateMobilePolicyCycleWithNullConfig() throws RemoteException { + when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]); + when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID}); + when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID); + when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(null); + setNetworkPolicies(buildDefaultFakeMobilePolicy()); + // smoke test to make sure no errors are raised + mServiceContext.sendBroadcast( + new Intent(ACTION_CARRIER_CONFIG_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID) + ); + assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes, + true); + } + + @Test + public void testUpdateMobilePolicyCycleWithInvalidConfig() throws RemoteException { + PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + // Test with an invalid CarrierConfig, there should be no changes or crashes. + bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, -100); + bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, -100); + bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, -100); + mServiceContext.sendBroadcast( + new Intent(ACTION_CARRIER_CONFIG_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID) + ); + + assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes, + true); + } + + @Test + public void testUpdateMobilePolicyCycleWithDefaultConfig() throws RemoteException { + PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + // Test that we respect the platform values when told to + bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, + DATA_CYCLE_USE_PLATFORM_DEFAULT); + bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, + DATA_CYCLE_USE_PLATFORM_DEFAULT); + bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, + DATA_CYCLE_USE_PLATFORM_DEFAULT); + mServiceContext.sendBroadcast( + new Intent(ACTION_CARRIER_CONFIG_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID) + ); + + assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes, + true); + } + + @Test + public void testUpdateMobilePolicyCycleWithUserOverrides() throws RemoteException { + PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + + // inferred = false implies that a user manually modified this policy. + NetworkPolicy policy = buildDefaultFakeMobilePolicy(); + policy.inferred = false; + setNetworkPolicies(policy); + + bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31); + bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, 9999); + bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, + DATA_CYCLE_THRESHOLD_DISABLED); + mServiceContext.sendBroadcast( + new Intent(ACTION_CARRIER_CONFIG_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID) + ); + + // The policy still shouldn't change, because we don't want to overwrite user settings. + assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes, + false); + } + + @Test + public void testUpdateMobilePolicyCycleUpdatesDataCycle() throws RemoteException { + PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + + bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31); + bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, 9999); + bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, 9999); + mServiceContext.sendBroadcast( + new Intent(ACTION_CARRIER_CONFIG_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID) + ); + + assertNetworkPolicyEquals(31, 9999, 9999, true); + } + + @Test + public void testUpdateMobilePolicyCycleDisableThresholds() throws RemoteException { + PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + + bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31); + bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, + DATA_CYCLE_THRESHOLD_DISABLED); + bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, + DATA_CYCLE_THRESHOLD_DISABLED); + mServiceContext.sendBroadcast( + new Intent(ACTION_CARRIER_CONFIG_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID) + ); + + assertNetworkPolicyEquals(31, WARNING_DISABLED, LIMIT_DISABLED, true); + } + + @Test + public void testUpdateMobilePolicyCycleRevertsToDefault() throws RemoteException { + PersistableBundle bundle = setupUpdateMobilePolicyCycleTests(); + + bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31); + bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, + DATA_CYCLE_THRESHOLD_DISABLED); + bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, + DATA_CYCLE_THRESHOLD_DISABLED); + mServiceContext.sendBroadcast( + new Intent(ACTION_CARRIER_CONFIG_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID) + ); + assertNetworkPolicyEquals(31, WARNING_DISABLED, LIMIT_DISABLED, true); + + // If the user switches carriers to one that doesn't use a CarrierConfig, we should revert + // to the default data limit and warning. The cycle date doesn't need to revert as it's + // arbitrary anyways. + bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, + DATA_CYCLE_USE_PLATFORM_DEFAULT); + bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, + DATA_CYCLE_USE_PLATFORM_DEFAULT); + bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, + DATA_CYCLE_USE_PLATFORM_DEFAULT); + mServiceContext.sendBroadcast( + new Intent(ACTION_CARRIER_CONFIG_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID) + ); + + assertNetworkPolicyEquals(31, mDefaultWarningBytes, mDefaultLimitBytes, + true); + } + + private NetworkPolicy buildDefaultFakeMobilePolicy() { + NetworkPolicy p = mService.buildDefaultMobilePolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID); + // set a deterministic cycle date + p.cycleDay = DEFAULT_CYCLE_DAY; + return p; + } + + private static NetworkPolicy buildFakeMobilePolicy(int cycleDay, long warningBytes, + long limitBytes, boolean inferred){ + final NetworkTemplate template = buildTemplateMobileAll(FAKE_SUBSCRIBER_ID); + return new NetworkPolicy(template, cycleDay, "America/Los_Angeles", warningBytes, + limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred); + } + + private void assertNetworkPolicyEquals(int expectedCycleDay, long expectedWarningBytes, + long expectedLimitBytes, boolean expectedInferred) { + NetworkPolicy[] policies = mService.getNetworkPolicies( + mServiceContext.getOpPackageName()); + assertEquals("Unexpected number of network policies", 1, policies.length); + NetworkPolicy actualPolicy = policies[0]; + NetworkPolicy expectedPolicy = buildFakeMobilePolicy(expectedCycleDay, expectedWarningBytes, + expectedLimitBytes, expectedInferred); + assertEquals(expectedPolicy, actualPolicy); + } + private static long parseTime(String time) { final Time result = new Time(); result.parse3339(time);