diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 68fc6c16fe4f3..98d8666ecd476 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9308,6 +9308,15 @@ public final class Settings {
public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
"network_metered_multipath_preference";
+ /**
+ * Default daily multipath budget used by ConnectivityManager.getMultipathPreference()
+ * on metered networks. This default quota is only used if quota could not be determined
+ * from data plan or data limit/warning set by the user.
+ * @hide
+ */
+ public static final String NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES =
+ "network_default_daily_multipath_quota_bytes";
+
/**
* Network watchlist last report time.
* @hide
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6d07d572e230c..3cd80f2677e09 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -327,6 +327,13 @@
This is the default value of that setting. -->
0
+
+ 2500000
+
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 02d6c9fc1656e..78a7286bf7128 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1922,6 +1922,7 @@
+
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index dfc99f6fcb3ad..002c9e2257cb1 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -301,6 +301,7 @@ public class SettingsBackupTest {
Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
Settings.Global.NETWORK_AVOID_BAD_WIFI,
+ Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
Settings.Global.NETWORK_PREFERENCE,
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 53a954444ca4c..3868ea6a1056a 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -24,13 +24,19 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
import android.app.usage.NetworkStatsManager;
import android.app.usage.NetworkStatsManager.UsageCallback;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
@@ -45,11 +51,15 @@ import android.net.StringNetworkSpecifier;
import android.os.BestClock;
import android.os.Handler;
import android.os.SystemClock;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.DebugUtils;
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -60,7 +70,6 @@ import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
-import java.util.Calendar;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -85,6 +94,9 @@ public class MultipathPolicyTracker {
private final Handler mHandler;
private final Clock mClock;
private final Dependencies mDeps;
+ private final ContentResolver mResolver;
+ private final SettingsObserver mSettingsObserver;
+ private final ConfigChangeReceiver mConfigChangeReceiver;
private ConnectivityManager mCM;
private NetworkPolicyManager mNPM;
@@ -93,8 +105,6 @@ public class MultipathPolicyTracker {
private NetworkCallback mMobileNetworkCallback;
private NetworkPolicyManager.Listener mPolicyListener;
- // STOPSHIP: replace this with a configurable mechanism.
- private static final long DEFAULT_DAILY_MULTIPATH_QUOTA = 2_500_000;
/**
* Divider to calculate opportunistic quota from user-set data limit or warning: 5% of user-set
@@ -118,6 +128,9 @@ public class MultipathPolicyTracker {
mHandler = handler;
mClock = deps.getClock();
mDeps = deps;
+ mResolver = mContext.getContentResolver();
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mConfigChangeReceiver = new ConfigChangeReceiver();
// Because we are initialized by the ConnectivityService constructor, we can't touch any
// connectivity APIs. Service initialization is done in start().
}
@@ -129,6 +142,14 @@ public class MultipathPolicyTracker {
registerTrackMobileCallback();
registerNetworkPolicyListener();
+ final Uri defaultSettingUri =
+ Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES);
+ mResolver.registerContentObserver(defaultSettingUri, false, mSettingsObserver);
+
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ mContext.registerReceiverAsUser(
+ mConfigChangeReceiver, UserHandle.ALL, intentFilter, null, mHandler);
}
public void shutdown() {
@@ -138,6 +159,8 @@ public class MultipathPolicyTracker {
t.shutdown();
}
mMultipathTrackers.clear();
+ mResolver.unregisterContentObserver(mSettingsObserver);
+ mContext.unregisterReceiver(mConfigChangeReceiver);
}
// Called on an arbitrary binder thread.
@@ -292,11 +315,11 @@ public class MultipathPolicyTracker {
// Fallback to user settings-based quota if not available from phone plan
if (quota == OPPORTUNISTIC_QUOTA_UNKNOWN) {
quota = getUserPolicyOpportunisticQuotaBytes();
+ if (DBG) Slog.d(TAG, "Opportunistic quota from user policy: " + quota + " bytes");
}
if (quota == OPPORTUNISTIC_QUOTA_UNKNOWN) {
- // STOPSHIP: replace this with a configurable mechanism.
- quota = DEFAULT_DAILY_MULTIPATH_QUOTA;
+ quota = getDefaultDailyMultipathQuotaBytes();
if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes");
}
@@ -374,6 +397,21 @@ public class MultipathPolicyTracker {
private final ConcurrentHashMap mMultipathTrackers =
new ConcurrentHashMap<>();
+ private long getDefaultDailyMultipathQuotaBytes() {
+ final String setting = Settings.Global.getString(mContext.getContentResolver(),
+ NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES);
+ if (setting != null) {
+ try {
+ return Long.parseLong(setting);
+ } catch(NumberFormatException e) {
+ // fall through
+ }
+ }
+
+ return mContext.getResources().getInteger(
+ R.integer.config_networkDefaultDailyMultipathQuotaBytes);
+ }
+
// TODO: this races with app code that might respond to onAvailable() by immediately calling
// getMultipathPreference. Fix this by adding to ConnectivityService the ability to directly
// invoke NetworkCallbacks on tightly-coupled classes such as this one which run on its
@@ -415,6 +453,15 @@ public class MultipathPolicyTracker {
mCM.registerNetworkCallback(request, mMobileNetworkCallback, mHandler);
}
+ /**
+ * Update multipath budgets for all trackers. To be called on the mHandler thread.
+ */
+ private void updateAllMultipathBudgets() {
+ for (MultipathTracker t : mMultipathTrackers.values()) {
+ t.updateMultipathBudget();
+ }
+ }
+
private void maybeUnregisterTrackMobileCallback() {
if (mMobileNetworkCallback != null) {
mCM.unregisterNetworkCallback(mMobileNetworkCallback);
@@ -427,11 +474,7 @@ public class MultipathPolicyTracker {
@Override
public void onMeteredIfacesChanged(String[] meteredIfaces) {
// Dispatched every time opportunistic quota is recalculated.
- mHandler.post(() -> {
- for (MultipathTracker t : mMultipathTrackers.values()) {
- t.updateMultipathBudget();
- }
- });
+ mHandler.post(() -> updateAllMultipathBudgets());
}
};
mNPM.registerListener(mPolicyListener);
@@ -441,6 +484,35 @@ public class MultipathPolicyTracker {
mNPM.unregisterListener(mPolicyListener);
}
+ private final class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ Slog.wtf(TAG, "Should never be reached.");
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (!Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES)
+ .equals(uri)) {
+ Slog.wtf(TAG, "Unexpected settings observation: " + uri);
+ }
+ if (DBG) Slog.d(TAG, "Settings change: updating budgets.");
+ updateAllMultipathBudgets();
+ }
+ }
+
+ private final class ConfigChangeReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DBG) Slog.d(TAG, "Configuration change: updating budgets.");
+ updateAllMultipathBudgets();
+ }
+ }
+
public void dump(IndentingPrintWriter pw) {
// Do not use in production. Access to class data is only safe on the handler thrad.
pw.println("MultipathPolicyTracker:");