blob: 04c792ae482b38b9a7a19ad25f714d713507ef4d [file] [log] [blame]
Lorenzo Colittid260ef22018-01-24 17:35:07 +09001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity;
18
Jeff Sharkeye0c29952018-02-20 17:24:55 -070019import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
20import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
21import static android.net.ConnectivityManager.TYPE_MOBILE;
22import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090023import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
24import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
Jeff Sharkeye0c29952018-02-20 17:24:55 -070025import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090026import static android.net.NetworkPolicy.LIMIT_DISABLED;
Remi NGUYEN VAN6a7a5a12018-04-02 21:18:52 +090027import static android.net.NetworkPolicy.WARNING_DISABLED;
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +090028import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
Rambo Wang818cf712019-12-12 18:16:10 -080029import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
Jeff Sharkeye0c29952018-02-20 17:24:55 -070030
31import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090032import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
Jeff Sharkeye0c29952018-02-20 17:24:55 -070033
Lorenzo Colittid260ef22018-01-24 17:35:07 +090034import android.app.usage.NetworkStatsManager;
35import android.app.usage.NetworkStatsManager.UsageCallback;
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +090036import android.content.BroadcastReceiver;
37import android.content.ContentResolver;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090038import android.content.Context;
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +090039import android.content.Intent;
40import android.content.IntentFilter;
41import android.database.ContentObserver;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090042import android.net.ConnectivityManager;
43import android.net.ConnectivityManager.NetworkCallback;
44import android.net.Network;
45import android.net.NetworkCapabilities;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090046import android.net.NetworkIdentity;
47import android.net.NetworkPolicy;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090048import android.net.NetworkPolicyManager;
49import android.net.NetworkRequest;
Rambo Wang818cf712019-12-12 18:16:10 -080050import android.net.NetworkSpecifier;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090051import android.net.NetworkStats;
52import android.net.NetworkTemplate;
Rambo Wang818cf712019-12-12 18:16:10 -080053import android.net.TelephonyNetworkSpecifier;
54import android.net.Uri;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090055import android.os.BestClock;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090056import android.os.Handler;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090057import android.os.SystemClock;
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +090058import android.os.UserHandle;
59import android.provider.Settings;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090060import android.telephony.TelephonyManager;
61import android.util.DebugUtils;
Jeff Sharkey0fc6d032018-03-30 16:25:11 -060062import android.util.Range;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090063import android.util.Slog;
64
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +090065import com.android.internal.R;
Remi NGUYEN VAN6a7a5a12018-04-02 21:18:52 +090066import com.android.internal.annotations.VisibleForTesting;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090067import com.android.internal.util.IndentingPrintWriter;
68import com.android.server.LocalServices;
69import com.android.server.net.NetworkPolicyManagerInternal;
Jeff Sharkeye0c29952018-02-20 17:24:55 -070070import com.android.server.net.NetworkStatsManagerInternal;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090071
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090072import java.time.Clock;
73import java.time.ZoneId;
74import java.time.ZoneOffset;
75import java.time.ZonedDateTime;
76import java.time.temporal.ChronoUnit;
Jeff Sharkeye0c29952018-02-20 17:24:55 -070077import java.util.concurrent.ConcurrentHashMap;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090078import java.util.concurrent.TimeUnit;
Lorenzo Colittid260ef22018-01-24 17:35:07 +090079
80/**
81 * Manages multipath data budgets.
82 *
83 * Informs the return value of ConnectivityManager#getMultipathPreference() based on:
84 * - The user's data plan, as returned by getSubscriptionOpportunisticQuota().
85 * - The amount of data usage that occurs on mobile networks while they are not the system default
86 * network (i.e., when the app explicitly selected such networks).
87 *
88 * Currently, quota is determined on a daily basis, from midnight to midnight local time.
89 *
90 * @hide
91 */
92public class MultipathPolicyTracker {
93 private static String TAG = MultipathPolicyTracker.class.getSimpleName();
94
95 private static final boolean DBG = false;
96
97 private final Context mContext;
98 private final Handler mHandler;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +090099 private final Clock mClock;
100 private final Dependencies mDeps;
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900101 private final ContentResolver mResolver;
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900102 private final ConfigChangeReceiver mConfigChangeReceiver;
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900103
Remi NGUYEN VAN6a7a5a12018-04-02 21:18:52 +0900104 @VisibleForTesting
105 final ContentObserver mSettingsObserver;
106
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900107 private ConnectivityManager mCM;
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900108 private NetworkPolicyManager mNPM;
Jeff Sharkeye0c29952018-02-20 17:24:55 -0700109 private NetworkStatsManager mStatsManager;
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900110
111 private NetworkCallback mMobileNetworkCallback;
112 private NetworkPolicyManager.Listener mPolicyListener;
113
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900114
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900115 /**
116 * Divider to calculate opportunistic quota from user-set data limit or warning: 5% of user-set
117 * limit.
118 */
119 private static final int OPQUOTA_USER_SETTING_DIVIDER = 20;
120
121 public static class Dependencies {
122 public Clock getClock() {
123 return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
124 Clock.systemUTC());
125 }
126 }
127
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900128 public MultipathPolicyTracker(Context ctx, Handler handler) {
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900129 this(ctx, handler, new Dependencies());
130 }
131
132 public MultipathPolicyTracker(Context ctx, Handler handler, Dependencies deps) {
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900133 mContext = ctx;
134 mHandler = handler;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900135 mClock = deps.getClock();
136 mDeps = deps;
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900137 mResolver = mContext.getContentResolver();
138 mSettingsObserver = new SettingsObserver(mHandler);
139 mConfigChangeReceiver = new ConfigChangeReceiver();
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900140 // Because we are initialized by the ConnectivityService constructor, we can't touch any
141 // connectivity APIs. Service initialization is done in start().
142 }
143
144 public void start() {
Jeff Sharkeye0c29952018-02-20 17:24:55 -0700145 mCM = mContext.getSystemService(ConnectivityManager.class);
146 mNPM = mContext.getSystemService(NetworkPolicyManager.class);
147 mStatsManager = mContext.getSystemService(NetworkStatsManager.class);
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900148
149 registerTrackMobileCallback();
150 registerNetworkPolicyListener();
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900151 final Uri defaultSettingUri =
152 Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES);
153 mResolver.registerContentObserver(defaultSettingUri, false, mSettingsObserver);
154
155 final IntentFilter intentFilter = new IntentFilter();
156 intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
157 mContext.registerReceiverAsUser(
158 mConfigChangeReceiver, UserHandle.ALL, intentFilter, null, mHandler);
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900159 }
160
161 public void shutdown() {
162 maybeUnregisterTrackMobileCallback();
163 unregisterNetworkPolicyListener();
164 for (MultipathTracker t : mMultipathTrackers.values()) {
165 t.shutdown();
166 }
167 mMultipathTrackers.clear();
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900168 mResolver.unregisterContentObserver(mSettingsObserver);
169 mContext.unregisterReceiver(mConfigChangeReceiver);
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900170 }
171
172 // Called on an arbitrary binder thread.
173 public Integer getMultipathPreference(Network network) {
Jeff Sharkeye0c29952018-02-20 17:24:55 -0700174 if (network == null) {
175 return null;
176 }
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900177 MultipathTracker t = mMultipathTrackers.get(network);
178 if (t != null) {
179 return t.getMultipathPreference();
180 }
181 return null;
182 }
183
184 // Track information on mobile networks as they come and go.
185 class MultipathTracker {
186 final Network network;
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900187 final String subscriberId;
188
189 private long mQuota;
190 /** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */
191 private long mMultipathBudget;
192 private final NetworkTemplate mNetworkTemplate;
193 private final UsageCallback mUsageCallback;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900194 private NetworkCapabilities mNetworkCapabilities;
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900195
196 public MultipathTracker(Network network, NetworkCapabilities nc) {
197 this.network = network;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900198 this.mNetworkCapabilities = new NetworkCapabilities(nc);
Rambo Wang818cf712019-12-12 18:16:10 -0800199 NetworkSpecifier specifier = nc.getNetworkSpecifier();
200 int subId = INVALID_SUBSCRIPTION_ID;
201 if (specifier instanceof TelephonyNetworkSpecifier) {
202 subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
203 } else {
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900204 throw new IllegalStateException(String.format(
Rambo Wang818cf712019-12-12 18:16:10 -0800205 "Can't get subId from mobile network %s (%s)",
206 network, nc));
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900207 }
208
Jeff Sharkeye0c29952018-02-20 17:24:55 -0700209 TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900210 if (tele == null) {
211 throw new IllegalStateException(String.format("Missing TelephonyManager"));
212 }
213 tele = tele.createForSubscriptionId(subId);
214 if (tele == null) {
215 throw new IllegalStateException(String.format(
216 "Can't get TelephonyManager for subId %d", subId));
217 }
218
219 subscriberId = tele.getSubscriberId();
220 mNetworkTemplate = new NetworkTemplate(
Jeff Sharkeye0c29952018-02-20 17:24:55 -0700221 NetworkTemplate.MATCH_MOBILE, subscriberId, new String[] { subscriberId },
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900222 null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
223 NetworkStats.DEFAULT_NETWORK_NO);
224 mUsageCallback = new UsageCallback() {
225 @Override
226 public void onThresholdReached(int networkType, String subscriberId) {
227 if (DBG) Slog.d(TAG, "onThresholdReached for network " + network);
228 mMultipathBudget = 0;
229 updateMultipathBudget();
230 }
231 };
232
233 updateMultipathBudget();
234 }
235
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900236 public void setNetworkCapabilities(NetworkCapabilities nc) {
237 mNetworkCapabilities = new NetworkCapabilities(nc);
238 }
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900239
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900240 // TODO: calculate with proper timezone information
241 private long getDailyNonDefaultDataUsage() {
242 final ZonedDateTime end =
243 ZonedDateTime.ofInstant(mClock.instant(), ZoneId.systemDefault());
244 final ZonedDateTime start = end.truncatedTo(ChronoUnit.DAYS);
245
246 final long bytes = getNetworkTotalBytes(
247 start.toInstant().toEpochMilli(),
248 end.toInstant().toEpochMilli());
249 if (DBG) Slog.d(TAG, "Non-default data usage: " + bytes);
250 return bytes;
251 }
252
253 private long getNetworkTotalBytes(long start, long end) {
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900254 try {
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900255 return LocalServices.getService(NetworkStatsManagerInternal.class)
256 .getNetworkTotalBytes(mNetworkTemplate, start, end);
Jeff Sharkeye0c29952018-02-20 17:24:55 -0700257 } catch (RuntimeException e) {
258 Slog.w(TAG, "Failed to get data usage: " + e);
259 return -1;
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900260 }
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900261 }
262
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900263 private NetworkIdentity getTemplateMatchingNetworkIdentity(NetworkCapabilities nc) {
264 return new NetworkIdentity(
265 ConnectivityManager.TYPE_MOBILE,
266 0 /* subType, unused for template matching */,
267 subscriberId,
268 null /* networkId, unused for matching mobile networks */,
269 !nc.hasCapability(NET_CAPABILITY_NOT_ROAMING),
270 !nc.hasCapability(NET_CAPABILITY_NOT_METERED),
271 false /* defaultNetwork, templates should have DEFAULT_NETWORK_ALL */);
272 }
273
274 private long getRemainingDailyBudget(long limitBytes,
Jeff Sharkey0fc6d032018-03-30 16:25:11 -0600275 Range<ZonedDateTime> cycle) {
276 final long start = cycle.getLower().toInstant().toEpochMilli();
277 final long end = cycle.getUpper().toInstant().toEpochMilli();
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900278 final long totalBytes = getNetworkTotalBytes(start, end);
279 final long remainingBytes = totalBytes == -1 ? 0 : Math.max(0, limitBytes - totalBytes);
280 // 1 + ((end - now - 1) / millisInDay with integers is equivalent to:
281 // ceil((double)(end - now) / millisInDay)
282 final long remainingDays =
283 1 + ((end - mClock.millis() - 1) / TimeUnit.DAYS.toMillis(1));
284
285 return remainingBytes / Math.max(1, remainingDays);
286 }
287
288 private long getUserPolicyOpportunisticQuotaBytes() {
289 // Keep the most restrictive applicable policy
290 long minQuota = Long.MAX_VALUE;
291 final NetworkIdentity identity = getTemplateMatchingNetworkIdentity(
292 mNetworkCapabilities);
293
294 final NetworkPolicy[] policies = mNPM.getNetworkPolicies();
295 for (NetworkPolicy policy : policies) {
Remi NGUYEN VAN6a7a5a12018-04-02 21:18:52 +0900296 if (policy.hasCycle() && policy.template.matches(identity)) {
297 final long cycleStart = policy.cycleIterator().next().getLower()
298 .toInstant().toEpochMilli();
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900299 // Prefer user-defined warning, otherwise use hard limit
Remi NGUYEN VAN6a7a5a12018-04-02 21:18:52 +0900300 final long activeWarning = getActiveWarning(policy, cycleStart);
301 final long policyBytes = (activeWarning == WARNING_DISABLED)
302 ? getActiveLimit(policy, cycleStart)
303 : activeWarning;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900304
Remi NGUYEN VAN6a7a5a12018-04-02 21:18:52 +0900305 if (policyBytes != LIMIT_DISABLED && policyBytes != WARNING_DISABLED) {
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900306 final long policyBudget = getRemainingDailyBudget(policyBytes,
307 policy.cycleIterator().next());
308 minQuota = Math.min(minQuota, policyBudget);
309 }
310 }
311 }
312
313 if (minQuota == Long.MAX_VALUE) {
314 return OPPORTUNISTIC_QUOTA_UNKNOWN;
315 }
316
317 return minQuota / OPQUOTA_USER_SETTING_DIVIDER;
318 }
319
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900320 void updateMultipathBudget() {
Jeff Sharkeye0c29952018-02-20 17:24:55 -0700321 long quota = LocalServices.getService(NetworkPolicyManagerInternal.class)
322 .getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH);
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900323 if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes");
324
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900325 // Fallback to user settings-based quota if not available from phone plan
326 if (quota == OPPORTUNISTIC_QUOTA_UNKNOWN) {
327 quota = getUserPolicyOpportunisticQuotaBytes();
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900328 if (DBG) Slog.d(TAG, "Opportunistic quota from user policy: " + quota + " bytes");
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900329 }
330
331 if (quota == OPPORTUNISTIC_QUOTA_UNKNOWN) {
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900332 quota = getDefaultDailyMultipathQuotaBytes();
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900333 if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes");
334 }
335
Remi NGUYEN VANd06a9002018-04-04 14:51:26 +0900336 // TODO: re-register if day changed: budget may have run out but should be refreshed.
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900337 if (haveMultipathBudget() && quota == mQuota) {
Remi NGUYEN VANd06a9002018-04-04 14:51:26 +0900338 // If there is already a usage callback pending , there's no need to re-register it
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900339 // if the quota hasn't changed. The callback will simply fire as expected when the
Remi NGUYEN VANd06a9002018-04-04 14:51:26 +0900340 // budget is spent.
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900341 if (DBG) Slog.d(TAG, "Quota still " + quota + ", not updating.");
342 return;
343 }
344 mQuota = quota;
345
Jeff Sharkeye0c29952018-02-20 17:24:55 -0700346 // If we can't get current usage, assume the worst and don't give
347 // ourselves any budget to work with.
348 final long usage = getDailyNonDefaultDataUsage();
349 final long budget = (usage == -1) ? 0 : Math.max(0, quota - usage);
Remi NGUYEN VANd06a9002018-04-04 14:51:26 +0900350
351 // Only consider budgets greater than MIN_THRESHOLD_BYTES, otherwise the callback will
352 // fire late, after data usage went over budget. Also budget should be 0 if remaining
353 // data is close to 0.
354 // This is necessary because the usage callback does not accept smaller thresholds.
355 // Because it snaps everything to MIN_THRESHOLD_BYTES, the lesser of the two evils is
356 // to snap to 0 here.
357 // This will only be called if the total quota for the day changed, not if usage changed
358 // since last time, so even if this is called very often the budget will not snap to 0
359 // as soon as there are less than 2MB left for today.
360 if (budget > NetworkStatsManager.MIN_THRESHOLD_BYTES) {
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900361 if (DBG) Slog.d(TAG, "Setting callback for " + budget +
362 " bytes on network " + network);
363 registerUsageCallback(budget);
364 } else {
365 maybeUnregisterUsageCallback();
366 }
367 }
368
369 public int getMultipathPreference() {
370 if (haveMultipathBudget()) {
371 return MULTIPATH_PREFERENCE_HANDOVER | MULTIPATH_PREFERENCE_RELIABILITY;
372 }
373 return 0;
374 }
375
376 // For debugging only.
377 public long getQuota() {
378 return mQuota;
379 }
380
381 // For debugging only.
382 public long getMultipathBudget() {
383 return mMultipathBudget;
384 }
385
386 private boolean haveMultipathBudget() {
387 return mMultipathBudget > 0;
388 }
389
390 private void registerUsageCallback(long budget) {
391 maybeUnregisterUsageCallback();
392 mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget,
393 mUsageCallback, mHandler);
394 mMultipathBudget = budget;
395 }
396
397 private void maybeUnregisterUsageCallback() {
398 if (haveMultipathBudget()) {
399 if (DBG) Slog.d(TAG, "Unregistering callback, budget was " + mMultipathBudget);
400 mStatsManager.unregisterUsageCallback(mUsageCallback);
401 mMultipathBudget = 0;
402 }
403 }
404
405 void shutdown() {
406 maybeUnregisterUsageCallback();
407 }
408 }
409
Remi NGUYEN VAN6a7a5a12018-04-02 21:18:52 +0900410 private static long getActiveWarning(NetworkPolicy policy, long cycleStart) {
411 return policy.lastWarningSnooze < cycleStart
412 ? policy.warningBytes
413 : WARNING_DISABLED;
414 }
415
416 private static long getActiveLimit(NetworkPolicy policy, long cycleStart) {
417 return policy.lastLimitSnooze < cycleStart
418 ? policy.limitBytes
419 : LIMIT_DISABLED;
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900420 }
421
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900422 // Only ever updated on the handler thread. Accessed from other binder threads to retrieve
423 // the tracker for a specific network.
424 private final ConcurrentHashMap <Network, MultipathTracker> mMultipathTrackers =
425 new ConcurrentHashMap<>();
426
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900427 private long getDefaultDailyMultipathQuotaBytes() {
428 final String setting = Settings.Global.getString(mContext.getContentResolver(),
429 NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES);
430 if (setting != null) {
431 try {
432 return Long.parseLong(setting);
433 } catch(NumberFormatException e) {
434 // fall through
435 }
436 }
437
438 return mContext.getResources().getInteger(
439 R.integer.config_networkDefaultDailyMultipathQuotaBytes);
440 }
441
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900442 // TODO: this races with app code that might respond to onAvailable() by immediately calling
443 // getMultipathPreference. Fix this by adding to ConnectivityService the ability to directly
444 // invoke NetworkCallbacks on tightly-coupled classes such as this one which run on its
445 // handler thread.
446 private void registerTrackMobileCallback() {
447 final NetworkRequest request = new NetworkRequest.Builder()
448 .addCapability(NET_CAPABILITY_INTERNET)
449 .addTransportType(TRANSPORT_CELLULAR)
450 .build();
451 mMobileNetworkCallback = new ConnectivityManager.NetworkCallback() {
452 @Override
453 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
454 MultipathTracker existing = mMultipathTrackers.get(network);
455 if (existing != null) {
Remi NGUYEN VAN05cfad72018-04-02 10:16:50 +0900456 existing.setNetworkCapabilities(nc);
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900457 existing.updateMultipathBudget();
458 return;
459 }
460
461 try {
462 mMultipathTrackers.put(network, new MultipathTracker(network, nc));
463 } catch (IllegalStateException e) {
464 Slog.e(TAG, "Can't track mobile network " + network + ": " + e.getMessage());
465 }
466 if (DBG) Slog.d(TAG, "Tracking mobile network " + network);
467 }
468
469 @Override
470 public void onLost(Network network) {
471 MultipathTracker existing = mMultipathTrackers.get(network);
472 if (existing != null) {
473 existing.shutdown();
474 mMultipathTrackers.remove(network);
475 }
476 if (DBG) Slog.d(TAG, "No longer tracking mobile network " + network);
477 }
478 };
479
480 mCM.registerNetworkCallback(request, mMobileNetworkCallback, mHandler);
481 }
482
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900483 /**
484 * Update multipath budgets for all trackers. To be called on the mHandler thread.
485 */
486 private void updateAllMultipathBudgets() {
487 for (MultipathTracker t : mMultipathTrackers.values()) {
488 t.updateMultipathBudget();
489 }
490 }
491
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900492 private void maybeUnregisterTrackMobileCallback() {
493 if (mMobileNetworkCallback != null) {
494 mCM.unregisterNetworkCallback(mMobileNetworkCallback);
495 }
496 mMobileNetworkCallback = null;
497 }
498
499 private void registerNetworkPolicyListener() {
500 mPolicyListener = new NetworkPolicyManager.Listener() {
501 @Override
502 public void onMeteredIfacesChanged(String[] meteredIfaces) {
503 // Dispatched every time opportunistic quota is recalculated.
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900504 mHandler.post(() -> updateAllMultipathBudgets());
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900505 }
506 };
507 mNPM.registerListener(mPolicyListener);
508 }
509
510 private void unregisterNetworkPolicyListener() {
511 mNPM.unregisterListener(mPolicyListener);
512 }
513
Remi NGUYEN VANe0ec9922018-03-29 16:17:19 +0900514 private final class SettingsObserver extends ContentObserver {
515 public SettingsObserver(Handler handler) {
516 super(handler);
517 }
518
519 @Override
520 public void onChange(boolean selfChange) {
521 Slog.wtf(TAG, "Should never be reached.");
522 }
523
524 @Override
525 public void onChange(boolean selfChange, Uri uri) {
526 if (!Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES)
527 .equals(uri)) {
528 Slog.wtf(TAG, "Unexpected settings observation: " + uri);
529 }
530 if (DBG) Slog.d(TAG, "Settings change: updating budgets.");
531 updateAllMultipathBudgets();
532 }
533 }
534
535 private final class ConfigChangeReceiver extends BroadcastReceiver {
536 @Override
537 public void onReceive(Context context, Intent intent) {
538 if (DBG) Slog.d(TAG, "Configuration change: updating budgets.");
539 updateAllMultipathBudgets();
540 }
541 }
542
Lorenzo Colittid260ef22018-01-24 17:35:07 +0900543 public void dump(IndentingPrintWriter pw) {
544 // Do not use in production. Access to class data is only safe on the handler thrad.
545 pw.println("MultipathPolicyTracker:");
546 pw.increaseIndent();
547 for (MultipathTracker t : mMultipathTrackers.values()) {
548 pw.println(String.format("Network %s: quota %d, budget %d. Preference: %s",
549 t.network, t.getQuota(), t.getMultipathBudget(),
550 DebugUtils.flagsToString(ConnectivityManager.class, "MULTIPATH_PREFERENCE_",
551 t.getMultipathPreference())));
552 }
553 pw.decreaseIndent();
554 }
555}