Loading services/core/java/com/android/server/ConnectivityService.java +15 −0 Original line number Diff line number Diff line Loading @@ -140,6 +140,7 @@ import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.LingerMonitor; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.MultipathPolicyTracker; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkDiagnostics; import com.android.server.connectivity.NetworkMonitor; Loading Loading @@ -511,6 +512,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting final MultinetworkPolicyTracker mMultinetworkPolicyTracker; @VisibleForTesting final MultipathPolicyTracker mMultipathPolicyTracker; /** * Implements support for the legacy "one network per network type" model. * Loading Loading @@ -894,6 +898,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mContext, mHandler, () -> rematchForAvoidBadWifiUpdate()); mMultinetworkPolicyTracker.start(); mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler); mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties); registerPrivateDnsSettingsCallbacks(); } Loading Loading @@ -1974,6 +1980,9 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(); dumpAvoidBadWifiSettings(pw); pw.println(); mMultipathPolicyTracker.dump(pw); if (argsContain(args, SHORT_ARG) == false) { pw.println(); synchronized (mValidationLogs) { Loading Loading @@ -2891,6 +2900,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED; } Integer networkPreference = mMultipathPolicyTracker.getMultipathPreference(network); if (networkPreference != null) { return networkPreference; } return mMultinetworkPolicyTracker.getMeteredMultipathPreference(); } Loading Loading @@ -2984,6 +2998,7 @@ public class ConnectivityService extends IConnectivityManager.Stub for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkMonitor.systemReady = true; } mMultipathPolicyTracker.start(); break; } case EVENT_REVALIDATE_NETWORK: { Loading services/core/java/com/android/server/MultipathPolicyTracker.java 0 → 100644 +361 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.connectivity; import android.app.usage.NetworkStatsManager; import android.app.usage.NetworkStatsManager.UsageCallback; import android.content.Context; import android.net.INetworkStatsService; import android.net.INetworkPolicyManager; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; import android.net.NetworkRequest; import android.net.NetworkStats; import android.net.NetworkTemplate; import android.net.StringNetworkSpecifier; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.TelephonyManager; import android.util.DebugUtils; import android.util.Slog; import java.util.Calendar; import java.util.concurrent.ConcurrentHashMap; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; /** * Manages multipath data budgets. * * Informs the return value of ConnectivityManager#getMultipathPreference() based on: * - The user's data plan, as returned by getSubscriptionOpportunisticQuota(). * - The amount of data usage that occurs on mobile networks while they are not the system default * network (i.e., when the app explicitly selected such networks). * * Currently, quota is determined on a daily basis, from midnight to midnight local time. * * @hide */ public class MultipathPolicyTracker { private static String TAG = MultipathPolicyTracker.class.getSimpleName(); private static final boolean DBG = false; private final Context mContext; private final Handler mHandler; private ConnectivityManager mCM; private NetworkStatsManager mStatsManager; private NetworkPolicyManager mNPM; private TelephonyManager mTelephonyManager; private INetworkStatsService mStatsService; 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; private volatile int mMeteredMultipathPreference; public MultipathPolicyTracker(Context ctx, Handler handler) { mContext = ctx; mHandler = handler; // Because we are initialized by the ConnectivityService constructor, we can't touch any // connectivity APIs. Service initialization is done in start(). } public void start() { mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); mNPM = (NetworkPolicyManager) mContext.getSystemService(Context.NETWORK_POLICY_SERVICE); mStatsManager = (NetworkStatsManager) mContext.getSystemService( Context.NETWORK_STATS_SERVICE); mStatsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); registerTrackMobileCallback(); registerNetworkPolicyListener(); } public void shutdown() { maybeUnregisterTrackMobileCallback(); unregisterNetworkPolicyListener(); for (MultipathTracker t : mMultipathTrackers.values()) { t.shutdown(); } mMultipathTrackers.clear(); } // Called on an arbitrary binder thread. public Integer getMultipathPreference(Network network) { MultipathTracker t = mMultipathTrackers.get(network); if (t != null) { return t.getMultipathPreference(); } return null; } // Track information on mobile networks as they come and go. class MultipathTracker { final Network network; final int subId; final String subscriberId; private long mQuota; /** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */ private long mMultipathBudget; private final NetworkTemplate mNetworkTemplate; private final UsageCallback mUsageCallback; public MultipathTracker(Network network, NetworkCapabilities nc) { this.network = network; try { subId = Integer.parseInt( ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString()); } catch (ClassCastException | NullPointerException | NumberFormatException e) { throw new IllegalStateException(String.format( "Can't get subId from mobile network %s (%s): %s", network, nc, e.getMessage())); } TelephonyManager tele = (TelephonyManager) mContext.getSystemService( Context.TELEPHONY_SERVICE); if (tele == null) { throw new IllegalStateException(String.format("Missing TelephonyManager")); } tele = tele.createForSubscriptionId(subId); if (tele == null) { throw new IllegalStateException(String.format( "Can't get TelephonyManager for subId %d", subId)); } subscriberId = tele.getSubscriberId(); mNetworkTemplate = new NetworkTemplate( NetworkTemplate.MATCH_MOBILE_ALL, subscriberId, new String[] { subscriberId }, null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL, NetworkStats.DEFAULT_NETWORK_NO); mUsageCallback = new UsageCallback() { @Override public void onThresholdReached(int networkType, String subscriberId) { if (DBG) Slog.d(TAG, "onThresholdReached for network " + network); mMultipathBudget = 0; updateMultipathBudget(); } }; updateMultipathBudget(); } private long getDailyNonDefaultDataUsage() { Calendar start = Calendar.getInstance(); Calendar end = (Calendar) start.clone(); start.set(Calendar.HOUR_OF_DAY, 0); start.set(Calendar.MINUTE, 0); start.set(Calendar.SECOND, 0); start.set(Calendar.MILLISECOND, 0); long bytes; try { // TODO: Consider using NetworkStatsManager.getSummaryForDevice instead. bytes = mStatsService.getNetworkTotalBytes(mNetworkTemplate, start.getTimeInMillis(), end.getTimeInMillis()); if (DBG) Slog.w(TAG, "Non-default data usage: " + bytes); } catch (RemoteException e) { Slog.w(TAG, "Can't fetch daily data usage: " + e); bytes = -1; } catch (IllegalStateException e) { // Bandwidth control disabled? bytes = -1; } return bytes; } void updateMultipathBudget() { NetworkPolicyManagerInternal npms = LocalServices.getService( NetworkPolicyManagerInternal.class); long quota = npms.getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH); if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes"); if (quota == 0) { // STOPSHIP: replace this with a configurable mechanism. quota = DEFAULT_DAILY_MULTIPATH_QUOTA; if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes"); } if (haveMultipathBudget() && quota == mQuota) { // If we already have a usage callback pending , there's no need to re-register it // if the quota hasn't changed. The callback will simply fire as expected when the // budget is spent. Also: if we re-register the callback when we're below the // UsageCallback's minimum value of 2MB, we'll overshoot the budget. if (DBG) Slog.d(TAG, "Quota still " + quota + ", not updating."); return; } mQuota = quota; long usage = getDailyNonDefaultDataUsage(); long budget = Math.max(0, quota - usage); if (budget > 0) { if (DBG) Slog.d(TAG, "Setting callback for " + budget + " bytes on network " + network); registerUsageCallback(budget); } else { maybeUnregisterUsageCallback(); } } public int getMultipathPreference() { if (haveMultipathBudget()) { return MULTIPATH_PREFERENCE_HANDOVER | MULTIPATH_PREFERENCE_RELIABILITY; } return 0; } // For debugging only. public long getQuota() { return mQuota; } // For debugging only. public long getMultipathBudget() { return mMultipathBudget; } private boolean haveMultipathBudget() { return mMultipathBudget > 0; } private void registerUsageCallback(long budget) { maybeUnregisterUsageCallback(); mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget, mUsageCallback, mHandler); mMultipathBudget = budget; } private void maybeUnregisterUsageCallback() { if (haveMultipathBudget()) { if (DBG) Slog.d(TAG, "Unregistering callback, budget was " + mMultipathBudget); mStatsManager.unregisterUsageCallback(mUsageCallback); mMultipathBudget = 0; } } void shutdown() { maybeUnregisterUsageCallback(); } } // Only ever updated on the handler thread. Accessed from other binder threads to retrieve // the tracker for a specific network. private final ConcurrentHashMap <Network, MultipathTracker> mMultipathTrackers = new ConcurrentHashMap<>(); // 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 // handler thread. private void registerTrackMobileCallback() { final NetworkRequest request = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_INTERNET) .addTransportType(TRANSPORT_CELLULAR) .build(); mMobileNetworkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { MultipathTracker existing = mMultipathTrackers.get(network); if (existing != null) { existing.updateMultipathBudget(); return; } try { mMultipathTrackers.put(network, new MultipathTracker(network, nc)); } catch (IllegalStateException e) { Slog.e(TAG, "Can't track mobile network " + network + ": " + e.getMessage()); } if (DBG) Slog.d(TAG, "Tracking mobile network " + network); } @Override public void onLost(Network network) { MultipathTracker existing = mMultipathTrackers.get(network); if (existing != null) { existing.shutdown(); mMultipathTrackers.remove(network); } if (DBG) Slog.d(TAG, "No longer tracking mobile network " + network); } }; mCM.registerNetworkCallback(request, mMobileNetworkCallback, mHandler); } private void maybeUnregisterTrackMobileCallback() { if (mMobileNetworkCallback != null) { mCM.unregisterNetworkCallback(mMobileNetworkCallback); } mMobileNetworkCallback = null; } private void registerNetworkPolicyListener() { mPolicyListener = new NetworkPolicyManager.Listener() { @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { // Dispatched every time opportunistic quota is recalculated. mHandler.post(() -> { for (MultipathTracker t : mMultipathTrackers.values()) { t.updateMultipathBudget(); } }); } }; mNPM.registerListener(mPolicyListener); } private void unregisterNetworkPolicyListener() { mNPM.unregisterListener(mPolicyListener); } public void dump(IndentingPrintWriter pw) { // Do not use in production. Access to class data is only safe on the handler thrad. pw.println("MultipathPolicyTracker:"); pw.increaseIndent(); for (MultipathTracker t : mMultipathTrackers.values()) { pw.println(String.format("Network %s: quota %d, budget %d. Preference: %s", t.network, t.getQuota(), t.getMultipathBudget(), DebugUtils.flagsToString(ConnectivityManager.class, "MULTIPATH_PREFERENCE_", t.getMultipathPreference()))); } pw.decreaseIndent(); } } services/net/java/android/net/util/MultinetworkPolicyTracker.java +1 −0 Original line number Diff line number Diff line Loading @@ -122,6 +122,7 @@ public class MultinetworkPolicyTracker { return mAvoidBadWifi; } // TODO: move this to MultipathPolicyTracker. public int getMeteredMultipathPreference() { return mMeteredMultipathPreference; } Loading Loading
services/core/java/com/android/server/ConnectivityService.java +15 −0 Original line number Diff line number Diff line Loading @@ -140,6 +140,7 @@ import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.LingerMonitor; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.MultipathPolicyTracker; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkDiagnostics; import com.android.server.connectivity.NetworkMonitor; Loading Loading @@ -511,6 +512,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting final MultinetworkPolicyTracker mMultinetworkPolicyTracker; @VisibleForTesting final MultipathPolicyTracker mMultipathPolicyTracker; /** * Implements support for the legacy "one network per network type" model. * Loading Loading @@ -894,6 +898,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mContext, mHandler, () -> rematchForAvoidBadWifiUpdate()); mMultinetworkPolicyTracker.start(); mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler); mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties); registerPrivateDnsSettingsCallbacks(); } Loading Loading @@ -1974,6 +1980,9 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(); dumpAvoidBadWifiSettings(pw); pw.println(); mMultipathPolicyTracker.dump(pw); if (argsContain(args, SHORT_ARG) == false) { pw.println(); synchronized (mValidationLogs) { Loading Loading @@ -2891,6 +2900,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED; } Integer networkPreference = mMultipathPolicyTracker.getMultipathPreference(network); if (networkPreference != null) { return networkPreference; } return mMultinetworkPolicyTracker.getMeteredMultipathPreference(); } Loading Loading @@ -2984,6 +2998,7 @@ public class ConnectivityService extends IConnectivityManager.Stub for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkMonitor.systemReady = true; } mMultipathPolicyTracker.start(); break; } case EVENT_REVALIDATE_NETWORK: { Loading
services/core/java/com/android/server/MultipathPolicyTracker.java 0 → 100644 +361 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.connectivity; import android.app.usage.NetworkStatsManager; import android.app.usage.NetworkStatsManager.UsageCallback; import android.content.Context; import android.net.INetworkStatsService; import android.net.INetworkPolicyManager; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; import android.net.NetworkRequest; import android.net.NetworkStats; import android.net.NetworkTemplate; import android.net.StringNetworkSpecifier; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.TelephonyManager; import android.util.DebugUtils; import android.util.Slog; import java.util.Calendar; import java.util.concurrent.ConcurrentHashMap; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; /** * Manages multipath data budgets. * * Informs the return value of ConnectivityManager#getMultipathPreference() based on: * - The user's data plan, as returned by getSubscriptionOpportunisticQuota(). * - The amount of data usage that occurs on mobile networks while they are not the system default * network (i.e., when the app explicitly selected such networks). * * Currently, quota is determined on a daily basis, from midnight to midnight local time. * * @hide */ public class MultipathPolicyTracker { private static String TAG = MultipathPolicyTracker.class.getSimpleName(); private static final boolean DBG = false; private final Context mContext; private final Handler mHandler; private ConnectivityManager mCM; private NetworkStatsManager mStatsManager; private NetworkPolicyManager mNPM; private TelephonyManager mTelephonyManager; private INetworkStatsService mStatsService; 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; private volatile int mMeteredMultipathPreference; public MultipathPolicyTracker(Context ctx, Handler handler) { mContext = ctx; mHandler = handler; // Because we are initialized by the ConnectivityService constructor, we can't touch any // connectivity APIs. Service initialization is done in start(). } public void start() { mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); mNPM = (NetworkPolicyManager) mContext.getSystemService(Context.NETWORK_POLICY_SERVICE); mStatsManager = (NetworkStatsManager) mContext.getSystemService( Context.NETWORK_STATS_SERVICE); mStatsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); registerTrackMobileCallback(); registerNetworkPolicyListener(); } public void shutdown() { maybeUnregisterTrackMobileCallback(); unregisterNetworkPolicyListener(); for (MultipathTracker t : mMultipathTrackers.values()) { t.shutdown(); } mMultipathTrackers.clear(); } // Called on an arbitrary binder thread. public Integer getMultipathPreference(Network network) { MultipathTracker t = mMultipathTrackers.get(network); if (t != null) { return t.getMultipathPreference(); } return null; } // Track information on mobile networks as they come and go. class MultipathTracker { final Network network; final int subId; final String subscriberId; private long mQuota; /** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */ private long mMultipathBudget; private final NetworkTemplate mNetworkTemplate; private final UsageCallback mUsageCallback; public MultipathTracker(Network network, NetworkCapabilities nc) { this.network = network; try { subId = Integer.parseInt( ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString()); } catch (ClassCastException | NullPointerException | NumberFormatException e) { throw new IllegalStateException(String.format( "Can't get subId from mobile network %s (%s): %s", network, nc, e.getMessage())); } TelephonyManager tele = (TelephonyManager) mContext.getSystemService( Context.TELEPHONY_SERVICE); if (tele == null) { throw new IllegalStateException(String.format("Missing TelephonyManager")); } tele = tele.createForSubscriptionId(subId); if (tele == null) { throw new IllegalStateException(String.format( "Can't get TelephonyManager for subId %d", subId)); } subscriberId = tele.getSubscriberId(); mNetworkTemplate = new NetworkTemplate( NetworkTemplate.MATCH_MOBILE_ALL, subscriberId, new String[] { subscriberId }, null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL, NetworkStats.DEFAULT_NETWORK_NO); mUsageCallback = new UsageCallback() { @Override public void onThresholdReached(int networkType, String subscriberId) { if (DBG) Slog.d(TAG, "onThresholdReached for network " + network); mMultipathBudget = 0; updateMultipathBudget(); } }; updateMultipathBudget(); } private long getDailyNonDefaultDataUsage() { Calendar start = Calendar.getInstance(); Calendar end = (Calendar) start.clone(); start.set(Calendar.HOUR_OF_DAY, 0); start.set(Calendar.MINUTE, 0); start.set(Calendar.SECOND, 0); start.set(Calendar.MILLISECOND, 0); long bytes; try { // TODO: Consider using NetworkStatsManager.getSummaryForDevice instead. bytes = mStatsService.getNetworkTotalBytes(mNetworkTemplate, start.getTimeInMillis(), end.getTimeInMillis()); if (DBG) Slog.w(TAG, "Non-default data usage: " + bytes); } catch (RemoteException e) { Slog.w(TAG, "Can't fetch daily data usage: " + e); bytes = -1; } catch (IllegalStateException e) { // Bandwidth control disabled? bytes = -1; } return bytes; } void updateMultipathBudget() { NetworkPolicyManagerInternal npms = LocalServices.getService( NetworkPolicyManagerInternal.class); long quota = npms.getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH); if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes"); if (quota == 0) { // STOPSHIP: replace this with a configurable mechanism. quota = DEFAULT_DAILY_MULTIPATH_QUOTA; if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes"); } if (haveMultipathBudget() && quota == mQuota) { // If we already have a usage callback pending , there's no need to re-register it // if the quota hasn't changed. The callback will simply fire as expected when the // budget is spent. Also: if we re-register the callback when we're below the // UsageCallback's minimum value of 2MB, we'll overshoot the budget. if (DBG) Slog.d(TAG, "Quota still " + quota + ", not updating."); return; } mQuota = quota; long usage = getDailyNonDefaultDataUsage(); long budget = Math.max(0, quota - usage); if (budget > 0) { if (DBG) Slog.d(TAG, "Setting callback for " + budget + " bytes on network " + network); registerUsageCallback(budget); } else { maybeUnregisterUsageCallback(); } } public int getMultipathPreference() { if (haveMultipathBudget()) { return MULTIPATH_PREFERENCE_HANDOVER | MULTIPATH_PREFERENCE_RELIABILITY; } return 0; } // For debugging only. public long getQuota() { return mQuota; } // For debugging only. public long getMultipathBudget() { return mMultipathBudget; } private boolean haveMultipathBudget() { return mMultipathBudget > 0; } private void registerUsageCallback(long budget) { maybeUnregisterUsageCallback(); mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget, mUsageCallback, mHandler); mMultipathBudget = budget; } private void maybeUnregisterUsageCallback() { if (haveMultipathBudget()) { if (DBG) Slog.d(TAG, "Unregistering callback, budget was " + mMultipathBudget); mStatsManager.unregisterUsageCallback(mUsageCallback); mMultipathBudget = 0; } } void shutdown() { maybeUnregisterUsageCallback(); } } // Only ever updated on the handler thread. Accessed from other binder threads to retrieve // the tracker for a specific network. private final ConcurrentHashMap <Network, MultipathTracker> mMultipathTrackers = new ConcurrentHashMap<>(); // 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 // handler thread. private void registerTrackMobileCallback() { final NetworkRequest request = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_INTERNET) .addTransportType(TRANSPORT_CELLULAR) .build(); mMobileNetworkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { MultipathTracker existing = mMultipathTrackers.get(network); if (existing != null) { existing.updateMultipathBudget(); return; } try { mMultipathTrackers.put(network, new MultipathTracker(network, nc)); } catch (IllegalStateException e) { Slog.e(TAG, "Can't track mobile network " + network + ": " + e.getMessage()); } if (DBG) Slog.d(TAG, "Tracking mobile network " + network); } @Override public void onLost(Network network) { MultipathTracker existing = mMultipathTrackers.get(network); if (existing != null) { existing.shutdown(); mMultipathTrackers.remove(network); } if (DBG) Slog.d(TAG, "No longer tracking mobile network " + network); } }; mCM.registerNetworkCallback(request, mMobileNetworkCallback, mHandler); } private void maybeUnregisterTrackMobileCallback() { if (mMobileNetworkCallback != null) { mCM.unregisterNetworkCallback(mMobileNetworkCallback); } mMobileNetworkCallback = null; } private void registerNetworkPolicyListener() { mPolicyListener = new NetworkPolicyManager.Listener() { @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { // Dispatched every time opportunistic quota is recalculated. mHandler.post(() -> { for (MultipathTracker t : mMultipathTrackers.values()) { t.updateMultipathBudget(); } }); } }; mNPM.registerListener(mPolicyListener); } private void unregisterNetworkPolicyListener() { mNPM.unregisterListener(mPolicyListener); } public void dump(IndentingPrintWriter pw) { // Do not use in production. Access to class data is only safe on the handler thrad. pw.println("MultipathPolicyTracker:"); pw.increaseIndent(); for (MultipathTracker t : mMultipathTrackers.values()) { pw.println(String.format("Network %s: quota %d, budget %d. Preference: %s", t.network, t.getQuota(), t.getMultipathBudget(), DebugUtils.flagsToString(ConnectivityManager.class, "MULTIPATH_PREFERENCE_", t.getMultipathPreference()))); } pw.decreaseIndent(); } }
services/net/java/android/net/util/MultinetworkPolicyTracker.java +1 −0 Original line number Diff line number Diff line Loading @@ -122,6 +122,7 @@ public class MultinetworkPolicyTracker { return mAvoidBadWifi; } // TODO: move this to MultipathPolicyTracker. public int getMeteredMultipathPreference() { return mMeteredMultipathPreference; } Loading