• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 
17 package android.net;
18 
19 import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
20 import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE;
21 import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
22 
23 import static com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString;
24 
25 import android.annotation.IntDef;
26 import android.annotation.IntRange;
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.annotation.SystemApi;
30 import android.content.Context;
31 import android.net.ConnectivityManager.MultipathPreference;
32 import android.os.Binder;
33 import android.os.Build;
34 import android.os.Process;
35 import android.os.UserHandle;
36 import android.provider.Settings;
37 import android.text.TextUtils;
38 import android.util.ArraySet;
39 import android.util.Range;
40 
41 import com.android.net.module.util.ConnectivitySettingsUtils;
42 import com.android.net.module.util.ProxyUtils;
43 
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.time.Duration;
47 import java.util.List;
48 import java.util.Set;
49 import java.util.StringJoiner;
50 
51 /**
52  * A manager class for connectivity module settings.
53  *
54  * @hide
55  */
56 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
57 public class ConnectivitySettingsManager {
58 
ConnectivitySettingsManager()59     private ConnectivitySettingsManager() {}
60 
61     /** Data activity timeout settings */
62 
63     /**
64      * Inactivity timeout to track mobile data activity.
65      *
66      * If set to a positive integer, it indicates the inactivity timeout value in seconds to
67      * infer the data activity of mobile network. After a period of no activity on mobile
68      * networks with length specified by the timeout, an {@code ACTION_DATA_ACTIVITY_CHANGE}
69      * intent is fired to indicate a transition of network status from "active" to "idle". Any
70      * subsequent activity on mobile networks triggers the firing of {@code
71      * ACTION_DATA_ACTIVITY_CHANGE} intent indicating transition from "idle" to "active".
72      *
73      * Network activity refers to transmitting or receiving data on the network interfaces.
74      *
75      * Tracking is disabled if set to zero or negative value.
76      *
77      * @hide
78      */
79     public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile";
80 
81     /**
82      * Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE}
83      * but for Wifi network.
84      *
85      * @hide
86      */
87     public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi";
88 
89     /** Dns resolver settings */
90 
91     /**
92      * Sample validity in seconds to configure for the system DNS resolver.
93      *
94      * @hide
95      */
96     public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS =
97             "dns_resolver_sample_validity_seconds";
98 
99     /**
100      * Success threshold in percent for use with the system DNS resolver.
101      *
102      * @hide
103      */
104     public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT =
105             "dns_resolver_success_threshold_percent";
106 
107     /**
108      * Minimum number of samples needed for statistics to be considered meaningful in the
109      * system DNS resolver.
110      *
111      * @hide
112      */
113     public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples";
114 
115     /**
116      * Maximum number taken into account for statistics purposes in the system DNS resolver.
117      *
118      * @hide
119      */
120     public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples";
121 
122     private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
123     private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
124 
125     /** Network switch notification settings */
126 
127     /**
128      * The maximum number of notifications shown in 24 hours when switching networks.
129      *
130      * @hide
131      */
132     public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT =
133             "network_switch_notification_daily_limit";
134 
135     /**
136      * The minimum time in milliseconds between notifications when switching networks.
137      *
138      * @hide
139      */
140     public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS =
141             "network_switch_notification_rate_limit_millis";
142 
143     /** Captive portal settings */
144 
145     /**
146      * The URL used for HTTP captive portal detection upon a new connection.
147      * A 204 response code from the server is used for validation.
148      *
149      * @hide
150      */
151     public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
152 
153     /**
154      * What to do when connecting a network that presents a captive portal.
155      * Must be one of the CAPTIVE_PORTAL_MODE_* constants below.
156      *
157      * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
158      *
159      * @hide
160      */
161     public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
162 
163     /**
164      * Don't attempt to detect captive portals.
165      */
166     public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
167 
168     /**
169      * When detecting a captive portal, display a notification that
170      * prompts the user to sign in.
171      */
172     public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
173 
174     /**
175      * When detecting a captive portal, immediately disconnect from the
176      * network and do not reconnect to that network in the future.
177      */
178     public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
179 
180     /** @hide */
181     @Retention(RetentionPolicy.SOURCE)
182     @IntDef(value = {
183             CAPTIVE_PORTAL_MODE_IGNORE,
184             CAPTIVE_PORTAL_MODE_PROMPT,
185             CAPTIVE_PORTAL_MODE_AVOID,
186     })
187     public @interface CaptivePortalMode {}
188 
189     /** Global http proxy settings */
190 
191     /**
192      * Host name for global http proxy. Set via ConnectivityManager.
193      *
194      * @hide
195      */
196     public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host";
197 
198     /**
199      * Integer host port for global http proxy. Set via ConnectivityManager.
200      *
201      * @hide
202      */
203     public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port";
204 
205     /**
206      * Exclusion list for global proxy. This string contains a list of
207      * comma-separated domains where the global proxy does not apply.
208      * Domains should be listed in a comma- separated list. Example of
209      * acceptable formats: ".domain1.com,my.domain2.com" Use
210      * ConnectivityManager to set/get.
211      *
212      * @hide
213      */
214     public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST =
215             "global_http_proxy_exclusion_list";
216 
217     /**
218      * The location PAC File for the proxy.
219      *
220      * @hide
221      */
222     public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url";
223 
224     /** Private dns settings */
225 
226     /**
227      * The requested Private DNS mode (string), and an accompanying specifier (string).
228      *
229      * Currently, the specifier holds the chosen provider name when the mode requests
230      * a specific provider. It may be used to store the provider name even when the
231      * mode changes so that temporarily disabling and re-enabling the specific
232      * provider mode does not necessitate retyping the provider hostname.
233      *
234      * @hide
235      */
236     public static final String PRIVATE_DNS_MODE = "private_dns_mode";
237 
238     /**
239      * The specific Private DNS provider name.
240      *
241      * @hide
242      */
243     public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
244 
245     /**
246      * Forced override of the default mode (hardcoded as "automatic", nee "opportunistic").
247      * This allows changing the default mode without effectively disabling other modes,
248      * all of which require explicit user action to enable/configure. See also b/79719289.
249      *
250      * Value is a string, suitable for assignment to PRIVATE_DNS_MODE above.
251      *
252      * @hide
253      */
254     public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
255 
256     /** Other settings */
257 
258     /**
259      * The number of milliseconds to hold on to a PendingIntent based request. This delay gives
260      * the receivers of the PendingIntent an opportunity to make a new network request before
261      * the Network satisfying the request is potentially removed.
262      *
263      * @hide
264      */
265     public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS =
266             "connectivity_release_pending_intent_delay_ms";
267 
268     /**
269      * Whether the mobile data connection should remain active even when higher
270      * priority networks like WiFi are active, to help make network switching faster.
271      *
272      * See ConnectivityService for more info.
273      *
274      * (0 = disabled, 1 = enabled)
275      *
276      * @hide
277      */
278     public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
279 
280     /**
281      * Whether the wifi data connection should remain active even when higher
282      * priority networks like Ethernet are active, to keep both networks.
283      * In the case where higher priority networks are connected, wifi will be
284      * unused unless an application explicitly requests to use it.
285      *
286      * See ConnectivityService for more info.
287      *
288      * (0 = disabled, 1 = enabled)
289      *
290      * @hide
291      */
292     public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested";
293 
294     /**
295      * Whether to automatically switch away from wifi networks that lose Internet access.
296      * Only meaningful if config_networkAvoidBadWifi is set to 0, otherwise the system always
297      * avoids such networks. Valid values are:
298      *
299      * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
300      * null: Ask the user whether to switch away from bad wifi.
301      * 1: Avoid bad wifi.
302      *
303      * @hide
304      */
305     public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi";
306 
307     /**
308      * Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
309      */
310     public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0;
311 
312     /**
313      * Ask the user whether to switch away from bad wifi.
314      */
315     public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1;
316 
317     /**
318      * Avoid bad wifi.
319      */
320     public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2;
321 
322     /** @hide */
323     @Retention(RetentionPolicy.SOURCE)
324     @IntDef(value = {
325             NETWORK_AVOID_BAD_WIFI_IGNORE,
326             NETWORK_AVOID_BAD_WIFI_PROMPT,
327             NETWORK_AVOID_BAD_WIFI_AVOID,
328     })
329     public @interface NetworkAvoidBadWifi {}
330 
331     /**
332      * User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be
333      * overridden by the system based on device or application state. If null, the value
334      * specified by config_networkMeteredMultipathPreference is used.
335      *
336      * @hide
337      */
338     public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
339             "network_metered_multipath_preference";
340 
341     /**
342      * A list of uids that should go on cellular networks in preference even when higher-priority
343      * networks are connected.
344      *
345      * @hide
346      */
347     public static final String MOBILE_DATA_PREFERRED_UIDS = "mobile_data_preferred_uids";
348 
349     /**
350      * One of the private DNS modes that indicates the private DNS mode is off.
351      */
352     public static final int PRIVATE_DNS_MODE_OFF = ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OFF;
353 
354     /**
355      * One of the private DNS modes that indicates the private DNS mode is automatic, which
356      * will try to use the current DNS as private DNS.
357      */
358     public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC =
359             ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC;
360 
361     /**
362      * One of the private DNS modes that indicates the private DNS mode is strict and the
363      * {@link #PRIVATE_DNS_SPECIFIER} is required, which will try to use the value of
364      * {@link #PRIVATE_DNS_SPECIFIER} as private DNS.
365      */
366     public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME =
367             ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
368 
369     /** @hide */
370     @Retention(RetentionPolicy.SOURCE)
371     @IntDef(value = {
372             PRIVATE_DNS_MODE_OFF,
373             PRIVATE_DNS_MODE_OPPORTUNISTIC,
374             PRIVATE_DNS_MODE_PROVIDER_HOSTNAME,
375     })
376     public @interface PrivateDnsMode {}
377 
378     /**
379      * A list of uids that is allowed to use restricted networks.
380      *
381      * @hide
382      */
383     public static final String UIDS_ALLOWED_ON_RESTRICTED_NETWORKS =
384             "uids_allowed_on_restricted_networks";
385 
386     /**
387      * A global rate limit that applies to all networks with NET_CAPABILITY_INTERNET when enabled.
388      *
389      * @hide
390      */
391     public static final String INGRESS_RATE_LIMIT_BYTES_PER_SECOND =
392             "ingress_rate_limit_bytes_per_second";
393 
394     /**
395      * Get mobile data activity timeout from {@link Settings}.
396      *
397      * @param context The {@link Context} to query the setting.
398      * @param def The default timeout if no setting value.
399      * @return The {@link Duration} of timeout to track mobile data activity.
400      */
401     @NonNull
getMobileDataActivityTimeout(@onNull Context context, @NonNull Duration def)402     public static Duration getMobileDataActivityTimeout(@NonNull Context context,
403             @NonNull Duration def) {
404         final int timeout = Settings.Global.getInt(
405                 context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE, (int) def.getSeconds());
406         return Duration.ofSeconds(timeout);
407     }
408 
409     /**
410      * Set mobile data activity timeout to {@link Settings}.
411      * Tracking is disabled if set to zero or negative value.
412      *
413      * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be
414      * ignored.
415      *
416      * @param context The {@link Context} to set the setting.
417      * @param timeout The mobile data activity timeout.
418      */
setMobileDataActivityTimeout(@onNull Context context, @NonNull Duration timeout)419     public static void setMobileDataActivityTimeout(@NonNull Context context,
420             @NonNull Duration timeout) {
421         Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE,
422                 (int) timeout.getSeconds());
423     }
424 
425     /**
426      * Get wifi data activity timeout from {@link Settings}.
427      *
428      * @param context The {@link Context} to query the setting.
429      * @param def The default timeout if no setting value.
430      * @return The {@link Duration} of timeout to track wifi data activity.
431      */
432     @NonNull
getWifiDataActivityTimeout(@onNull Context context, @NonNull Duration def)433     public static Duration getWifiDataActivityTimeout(@NonNull Context context,
434             @NonNull Duration def) {
435         final int timeout = Settings.Global.getInt(
436                 context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI, (int) def.getSeconds());
437         return Duration.ofSeconds(timeout);
438     }
439 
440     /**
441      * Set wifi data activity timeout to {@link Settings}.
442      * Tracking is disabled if set to zero or negative value.
443      *
444      * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be
445      * ignored.
446      *
447      * @param context The {@link Context} to set the setting.
448      * @param timeout The wifi data activity timeout.
449      */
setWifiDataActivityTimeout(@onNull Context context, @NonNull Duration timeout)450     public static void setWifiDataActivityTimeout(@NonNull Context context,
451             @NonNull Duration timeout) {
452         Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI,
453                 (int) timeout.getSeconds());
454     }
455 
456     /**
457      * Get dns resolver sample validity duration from {@link Settings}.
458      *
459      * @param context The {@link Context} to query the setting.
460      * @param def The default duration if no setting value.
461      * @return The {@link Duration} of sample validity duration to configure for the system DNS
462      *         resolver.
463      */
464     @NonNull
getDnsResolverSampleValidityDuration(@onNull Context context, @NonNull Duration def)465     public static Duration getDnsResolverSampleValidityDuration(@NonNull Context context,
466             @NonNull Duration def) {
467         final int duration = Settings.Global.getInt(context.getContentResolver(),
468                 DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, (int) def.getSeconds());
469         return Duration.ofSeconds(duration);
470     }
471 
472     /**
473      * Set dns resolver sample validity duration to {@link Settings}. The duration must be a
474      * positive number of seconds.
475      *
476      * @param context The {@link Context} to set the setting.
477      * @param duration The sample validity duration.
478      */
setDnsResolverSampleValidityDuration(@onNull Context context, @NonNull Duration duration)479     public static void setDnsResolverSampleValidityDuration(@NonNull Context context,
480             @NonNull Duration duration) {
481         final int time = (int) duration.getSeconds();
482         if (time <= 0) {
483             throw new IllegalArgumentException("Invalid duration");
484         }
485         Settings.Global.putInt(
486                 context.getContentResolver(), DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, time);
487     }
488 
489     /**
490      * Get dns resolver success threshold percent from {@link Settings}.
491      *
492      * @param context The {@link Context} to query the setting.
493      * @param def The default value if no setting value.
494      * @return The success threshold in percent for use with the system DNS resolver.
495      */
getDnsResolverSuccessThresholdPercent(@onNull Context context, int def)496     public static int getDnsResolverSuccessThresholdPercent(@NonNull Context context, int def) {
497         return Settings.Global.getInt(
498                 context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, def);
499     }
500 
501     /**
502      * Set dns resolver success threshold percent to {@link Settings}. The threshold percent must
503      * be 0~100.
504      *
505      * @param context The {@link Context} to set the setting.
506      * @param percent The success threshold percent.
507      */
setDnsResolverSuccessThresholdPercent(@onNull Context context, @IntRange(from = 0, to = 100) int percent)508     public static void setDnsResolverSuccessThresholdPercent(@NonNull Context context,
509             @IntRange(from = 0, to = 100) int percent) {
510         if (percent < 0 || percent > 100) {
511             throw new IllegalArgumentException("Percent must be 0~100");
512         }
513         Settings.Global.putInt(
514                 context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, percent);
515     }
516 
517     /**
518      * Get dns resolver samples range from {@link Settings}.
519      *
520      * @param context The {@link Context} to query the setting.
521      * @return The {@link Range<Integer>} of samples needed for statistics to be considered
522      *         meaningful in the system DNS resolver.
523      */
524     @NonNull
getDnsResolverSampleRanges(@onNull Context context)525     public static Range<Integer> getDnsResolverSampleRanges(@NonNull Context context) {
526         final int minSamples = Settings.Global.getInt(context.getContentResolver(),
527                 DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
528         final int maxSamples = Settings.Global.getInt(context.getContentResolver(),
529                 DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
530         return new Range<>(minSamples, maxSamples);
531     }
532 
533     /**
534      * Set dns resolver samples range to {@link Settings}.
535      *
536      * @param context The {@link Context} to set the setting.
537      * @param range The samples range. The minimum number should be more than 0 and the maximum
538      *              number should be less that 64.
539      */
setDnsResolverSampleRanges(@onNull Context context, @NonNull Range<Integer> range)540     public static void setDnsResolverSampleRanges(@NonNull Context context,
541             @NonNull Range<Integer> range) {
542         if (range.getLower() < 0 || range.getUpper() > 64) {
543             throw new IllegalArgumentException("Argument must be 0~64");
544         }
545         Settings.Global.putInt(
546                 context.getContentResolver(), DNS_RESOLVER_MIN_SAMPLES, range.getLower());
547         Settings.Global.putInt(
548                 context.getContentResolver(), DNS_RESOLVER_MAX_SAMPLES, range.getUpper());
549     }
550 
551     /**
552      * Get maximum count (from {@link Settings}) of switching network notifications shown in 24
553      * hours.
554      *
555      * @param context The {@link Context} to query the setting.
556      * @param def The default value if no setting value.
557      * @return The maximum count of notifications shown in 24 hours when switching networks.
558      */
getNetworkSwitchNotificationMaximumDailyCount(@onNull Context context, int def)559     public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
560             int def) {
561         return Settings.Global.getInt(
562                 context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, def);
563     }
564 
565     /**
566      * Set maximum count (to {@link Settings}) of switching network notifications shown in 24 hours.
567      * The count must be at least 0.
568      *
569      * @param context The {@link Context} to set the setting.
570      * @param count The maximum count of switching network notifications shown in 24 hours.
571      */
setNetworkSwitchNotificationMaximumDailyCount(@onNull Context context, @IntRange(from = 0) int count)572     public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
573             @IntRange(from = 0) int count) {
574         if (count < 0) {
575             throw new IllegalArgumentException("Count must be more than 0.");
576         }
577         Settings.Global.putInt(
578                 context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, count);
579     }
580 
581     /**
582      * Get minimum duration (from {@link Settings}) between each switching network notifications.
583      *
584      * @param context The {@link Context} to query the setting.
585      * @param def The default time if no setting value.
586      * @return The minimum duration between notifications when switching networks.
587      */
588     @NonNull
getNetworkSwitchNotificationRateDuration(@onNull Context context, @NonNull Duration def)589     public static Duration getNetworkSwitchNotificationRateDuration(@NonNull Context context,
590             @NonNull Duration def) {
591         final int duration = Settings.Global.getInt(context.getContentResolver(),
592                 NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, (int) def.toMillis());
593         return Duration.ofMillis(duration);
594     }
595 
596     /**
597      * Set minimum duration (to {@link Settings}) between each switching network notifications.
598      * The duration will be rounded down to the next millisecond, and must be positive.
599      *
600      * @param context The {@link Context} to set the setting.
601      * @param duration The minimum duration between notifications when switching networks.
602      */
setNetworkSwitchNotificationRateDuration(@onNull Context context, @NonNull Duration duration)603     public static void setNetworkSwitchNotificationRateDuration(@NonNull Context context,
604             @NonNull Duration duration) {
605         final int time = (int) duration.toMillis();
606         if (time < 0) {
607             throw new IllegalArgumentException("Invalid duration.");
608         }
609         Settings.Global.putInt(context.getContentResolver(),
610                 NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, time);
611     }
612 
613     /**
614      * Get URL (from {@link Settings}) used for HTTP captive portal detection upon a new connection.
615      *
616      * @param context The {@link Context} to query the setting.
617      * @return The URL used for HTTP captive portal detection upon a new connection.
618      */
619     @Nullable
getCaptivePortalHttpUrl(@onNull Context context)620     public static String getCaptivePortalHttpUrl(@NonNull Context context) {
621         return Settings.Global.getString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL);
622     }
623 
624     /**
625      * Set URL (to {@link Settings}) used for HTTP captive portal detection upon a new connection.
626      * The URL is accessed to check for connectivity and presence of a captive portal on a network.
627      * The URL should respond with HTTP status 204 to a GET request, and the stack will use
628      * redirection status as a signal for captive portal detection.
629      * If the URL is set to null or is otherwise incorrect or inaccessible, the stack will fail to
630      * detect connectivity and portals. This will often result in loss of connectivity.
631      *
632      * @param context The {@link Context} to set the setting.
633      * @param url The URL used for HTTP captive portal detection upon a new connection.
634      */
setCaptivePortalHttpUrl(@onNull Context context, @Nullable String url)635     public static void setCaptivePortalHttpUrl(@NonNull Context context, @Nullable String url) {
636         Settings.Global.putString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL, url);
637     }
638 
639     /**
640      * Get mode (from {@link Settings}) when connecting a network that presents a captive portal.
641      *
642      * @param context The {@link Context} to query the setting.
643      * @param def The default mode if no setting value.
644      * @return The mode when connecting a network that presents a captive portal.
645      */
646     @CaptivePortalMode
getCaptivePortalMode(@onNull Context context, @CaptivePortalMode int def)647     public static int getCaptivePortalMode(@NonNull Context context,
648             @CaptivePortalMode int def) {
649         return Settings.Global.getInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, def);
650     }
651 
652     /**
653      * Set mode (to {@link Settings}) when connecting a network that presents a captive portal.
654      *
655      * @param context The {@link Context} to set the setting.
656      * @param mode The mode when connecting a network that presents a captive portal.
657      */
setCaptivePortalMode(@onNull Context context, @CaptivePortalMode int mode)658     public static void setCaptivePortalMode(@NonNull Context context, @CaptivePortalMode int mode) {
659         if (!(mode == CAPTIVE_PORTAL_MODE_IGNORE
660                 || mode == CAPTIVE_PORTAL_MODE_PROMPT
661                 || mode == CAPTIVE_PORTAL_MODE_AVOID)) {
662             throw new IllegalArgumentException("Invalid captive portal mode");
663         }
664         Settings.Global.putInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, mode);
665     }
666 
667     /**
668      * Get the global HTTP proxy applied to the device, or null if none.
669      *
670      * @param context The {@link Context} to query the setting.
671      * @return The {@link ProxyInfo} which build from global http proxy settings.
672      */
673     @Nullable
getGlobalProxy(@onNull Context context)674     public static ProxyInfo getGlobalProxy(@NonNull Context context) {
675         final String host = Settings.Global.getString(
676                 context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST);
677         final int port = Settings.Global.getInt(
678                 context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* def */);
679         final String exclusionList = Settings.Global.getString(
680                 context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
681         final String pacFileUrl = Settings.Global.getString(
682                 context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC);
683 
684         if (TextUtils.isEmpty(host) && TextUtils.isEmpty(pacFileUrl)) {
685             return null; // No global proxy.
686         }
687 
688         if (TextUtils.isEmpty(pacFileUrl)) {
689             return ProxyInfo.buildDirectProxy(
690                     host, port, ProxyUtils.exclusionStringAsList(exclusionList));
691         } else {
692             return ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
693         }
694     }
695 
696     /**
697      * Set global http proxy settings from given {@link ProxyInfo}.
698      *
699      * @param context The {@link Context} to set the setting.
700      * @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from
701      *                    {@link ProxyInfo#buildPacProxy(Uri)} or
702      *                    {@link ProxyInfo#buildDirectProxy(String, int, List)}
703      */
setGlobalProxy(@onNull Context context, @NonNull ProxyInfo proxyInfo)704     public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) {
705         final String host = proxyInfo.getHost();
706         final int port = proxyInfo.getPort();
707         final String exclusionList = proxyInfo.getExclusionListAsString();
708         final String pacFileUrl = proxyInfo.getPacFileUrl().toString();
709 
710         if (TextUtils.isEmpty(pacFileUrl)) {
711             Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host);
712             Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port);
713             Settings.Global.putString(
714                     context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclusionList);
715             Settings.Global.putString(
716                     context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
717         } else {
718             Settings.Global.putString(
719                     context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
720             Settings.Global.putString(
721                     context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */);
722             Settings.Global.putInt(
723                     context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */);
724             Settings.Global.putString(
725                     context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */);
726         }
727     }
728 
729     /**
730      * Clear all global http proxy settings.
731      *
732      * @param context The {@link Context} to set the setting.
733      */
clearGlobalProxy(@onNull Context context)734     public static void clearGlobalProxy(@NonNull Context context) {
735         Settings.Global.putString(
736                 context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */);
737         Settings.Global.putInt(
738                 context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */);
739         Settings.Global.putString(
740                 context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */);
741         Settings.Global.putString(
742                 context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
743     }
744 
745     /**
746      * Get private DNS mode from settings.
747      *
748      * @param context The Context to query the private DNS mode from settings.
749      * @return A string of private DNS mode.
750      */
751     @PrivateDnsMode
getPrivateDnsMode(@onNull Context context)752     public static int getPrivateDnsMode(@NonNull Context context) {
753         return ConnectivitySettingsUtils.getPrivateDnsMode(context);
754     }
755 
756     /**
757      * Set private DNS mode to settings.
758      *
759      * @param context The {@link Context} to set the private DNS mode.
760      * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants.
761      */
setPrivateDnsMode(@onNull Context context, @PrivateDnsMode int mode)762     public static void setPrivateDnsMode(@NonNull Context context, @PrivateDnsMode int mode) {
763         ConnectivitySettingsUtils.setPrivateDnsMode(context, mode);
764     }
765 
766     /**
767      * Get specific private dns provider name from {@link Settings}.
768      *
769      * @param context The {@link Context} to query the setting.
770      * @return The specific private dns provider name, or null if no setting value.
771      */
772     @Nullable
getPrivateDnsHostname(@onNull Context context)773     public static String getPrivateDnsHostname(@NonNull Context context) {
774         return ConnectivitySettingsUtils.getPrivateDnsHostname(context);
775     }
776 
777     /**
778      * Set specific private dns provider name to {@link Settings}.
779      *
780      * @param context The {@link Context} to set the setting.
781      * @param specifier The specific private dns provider name.
782      */
setPrivateDnsHostname(@onNull Context context, @Nullable String specifier)783     public static void setPrivateDnsHostname(@NonNull Context context, @Nullable String specifier) {
784         ConnectivitySettingsUtils.setPrivateDnsHostname(context, specifier);
785     }
786 
787     /**
788      * Get default private dns mode from {@link Settings}.
789      *
790      * @param context The {@link Context} to query the setting.
791      * @return The default private dns mode.
792      */
793     @PrivateDnsMode
794     @NonNull
getPrivateDnsDefaultMode(@onNull Context context)795     public static String getPrivateDnsDefaultMode(@NonNull Context context) {
796         return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE);
797     }
798 
799     /**
800      * Set default private dns mode to {@link Settings}.
801      *
802      * @param context The {@link Context} to set the setting.
803      * @param mode The default private dns mode. This should be one of the PRIVATE_DNS_MODE_*
804      *             constants.
805      */
setPrivateDnsDefaultMode(@onNull Context context, @NonNull @PrivateDnsMode int mode)806     public static void setPrivateDnsDefaultMode(@NonNull Context context,
807             @NonNull @PrivateDnsMode int mode) {
808         if (!(mode == PRIVATE_DNS_MODE_OFF
809                 || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
810                 || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
811             throw new IllegalArgumentException("Invalid private dns mode");
812         }
813         Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE,
814                 getPrivateDnsModeAsString(mode));
815     }
816 
817     /**
818      * Get duration (from {@link Settings}) to keep a PendingIntent-based request.
819      *
820      * @param context The {@link Context} to query the setting.
821      * @param def The default duration if no setting value.
822      * @return The duration to keep a PendingIntent-based request.
823      */
824     @NonNull
getConnectivityKeepPendingIntentDuration(@onNull Context context, @NonNull Duration def)825     public static Duration getConnectivityKeepPendingIntentDuration(@NonNull Context context,
826             @NonNull Duration def) {
827         final int duration = Settings.Secure.getInt(context.getContentResolver(),
828                 CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, (int) def.toMillis());
829         return Duration.ofMillis(duration);
830     }
831 
832     /**
833      * Set duration (to {@link Settings}) to keep a PendingIntent-based request.
834      * The duration will be rounded down to the next millisecond, and must be positive.
835      *
836      * @param context The {@link Context} to set the setting.
837      * @param duration The duration to keep a PendingIntent-based request.
838      */
setConnectivityKeepPendingIntentDuration(@onNull Context context, @NonNull Duration duration)839     public static void setConnectivityKeepPendingIntentDuration(@NonNull Context context,
840             @NonNull Duration duration) {
841         final int time = (int) duration.toMillis();
842         if (time < 0) {
843             throw new IllegalArgumentException("Invalid duration.");
844         }
845         Settings.Secure.putInt(
846                 context.getContentResolver(), CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, time);
847     }
848 
849     /**
850      * Read from {@link Settings} whether the mobile data connection should remain active
851      * even when higher priority networks are active.
852      *
853      * @param context The {@link Context} to query the setting.
854      * @param def The default value if no setting value.
855      * @return Whether the mobile data connection should remain active even when higher
856      *         priority networks are active.
857      */
getMobileDataAlwaysOn(@onNull Context context, boolean def)858     public static boolean getMobileDataAlwaysOn(@NonNull Context context, boolean def) {
859         final int enable = Settings.Global.getInt(
860                 context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (def ? 1 : 0));
861         return (enable != 0) ? true : false;
862     }
863 
864     /**
865      * Write into {@link Settings} whether the mobile data connection should remain active
866      * even when higher priority networks are active.
867      *
868      * @param context The {@link Context} to set the setting.
869      * @param enable Whether the mobile data connection should remain active even when higher
870      *               priority networks are active.
871      */
setMobileDataAlwaysOn(@onNull Context context, boolean enable)872     public static void setMobileDataAlwaysOn(@NonNull Context context, boolean enable) {
873         Settings.Global.putInt(
874                 context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (enable ? 1 : 0));
875     }
876 
877     /**
878      * Read from {@link Settings} whether the wifi data connection should remain active
879      * even when higher priority networks are active.
880      *
881      * @param context The {@link Context} to query the setting.
882      * @param def The default value if no setting value.
883      * @return Whether the wifi data connection should remain active even when higher
884      *         priority networks are active.
885      */
getWifiAlwaysRequested(@onNull Context context, boolean def)886     public static boolean getWifiAlwaysRequested(@NonNull Context context, boolean def) {
887         final int enable = Settings.Global.getInt(
888                 context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (def ? 1 : 0));
889         return (enable != 0) ? true : false;
890     }
891 
892     /**
893      * Write into {@link Settings} whether the wifi data connection should remain active
894      * even when higher priority networks are active.
895      *
896      * @param context The {@link Context} to set the setting.
897      * @param enable Whether the wifi data connection should remain active even when higher
898      *               priority networks are active
899      */
setWifiAlwaysRequested(@onNull Context context, boolean enable)900     public static void setWifiAlwaysRequested(@NonNull Context context, boolean enable) {
901         Settings.Global.putInt(
902                 context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (enable ? 1 : 0));
903     }
904 
905     /**
906      * Get avoid bad wifi setting from {@link Settings}.
907      *
908      * @param context The {@link Context} to query the setting.
909      * @return The setting whether to automatically switch away from wifi networks that lose
910      *         internet access.
911      */
912     @NetworkAvoidBadWifi
getNetworkAvoidBadWifi(@onNull Context context)913     public static int getNetworkAvoidBadWifi(@NonNull Context context) {
914         final String setting =
915                 Settings.Global.getString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI);
916         if ("0".equals(setting)) {
917             return NETWORK_AVOID_BAD_WIFI_IGNORE;
918         } else if ("1".equals(setting)) {
919             return NETWORK_AVOID_BAD_WIFI_AVOID;
920         } else {
921             return NETWORK_AVOID_BAD_WIFI_PROMPT;
922         }
923     }
924 
925     /**
926      * Set avoid bad wifi setting to {@link Settings}.
927      *
928      * @param context The {@link Context} to set the setting.
929      * @param value Whether to automatically switch away from wifi networks that lose internet
930      *              access.
931      */
setNetworkAvoidBadWifi(@onNull Context context, @NetworkAvoidBadWifi int value)932     public static void setNetworkAvoidBadWifi(@NonNull Context context,
933             @NetworkAvoidBadWifi int value) {
934         final String setting;
935         if (value == NETWORK_AVOID_BAD_WIFI_IGNORE) {
936             setting = "0";
937         } else if (value == NETWORK_AVOID_BAD_WIFI_AVOID) {
938             setting = "1";
939         } else if (value == NETWORK_AVOID_BAD_WIFI_PROMPT) {
940             setting = null;
941         } else {
942             throw new IllegalArgumentException("Invalid avoid bad wifi setting");
943         }
944         Settings.Global.putString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI, setting);
945     }
946 
947     /**
948      * Get network metered multipath preference from {@link Settings}.
949      *
950      * @param context The {@link Context} to query the setting.
951      * @return The network metered multipath preference which should be one of
952      *         ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value specified
953      *         by config_networkMeteredMultipathPreference is used.
954      */
955     @Nullable
getNetworkMeteredMultipathPreference(@onNull Context context)956     public static String getNetworkMeteredMultipathPreference(@NonNull Context context) {
957         return Settings.Global.getString(
958                 context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE);
959     }
960 
961     /**
962      * Set network metered multipath preference to {@link Settings}.
963      *
964      * @param context The {@link Context} to set the setting.
965      * @param preference The network metered multipath preference which should be one of
966      *                   ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value
967      *                   specified by config_networkMeteredMultipathPreference is used.
968      */
setNetworkMeteredMultipathPreference(@onNull Context context, @NonNull @MultipathPreference String preference)969     public static void setNetworkMeteredMultipathPreference(@NonNull Context context,
970             @NonNull @MultipathPreference String preference) {
971         if (!(Integer.valueOf(preference) == MULTIPATH_PREFERENCE_HANDOVER
972                 || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_RELIABILITY
973                 || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_PERFORMANCE)) {
974             throw new IllegalArgumentException("Invalid private dns mode");
975         }
976         Settings.Global.putString(
977                 context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference);
978     }
979 
getUidSetFromString(@ullable String uidList)980     private static Set<Integer> getUidSetFromString(@Nullable String uidList) {
981         final Set<Integer> uids = new ArraySet<>();
982         if (TextUtils.isEmpty(uidList)) {
983             return uids;
984         }
985         for (String uid : uidList.split(";")) {
986             uids.add(Integer.valueOf(uid));
987         }
988         return uids;
989     }
990 
getUidStringFromSet(@onNull Set<Integer> uidList)991     private static String getUidStringFromSet(@NonNull Set<Integer> uidList) {
992         final StringJoiner joiner = new StringJoiner(";");
993         for (Integer uid : uidList) {
994             if (uid < 0 || UserHandle.getAppId(uid) > Process.LAST_APPLICATION_UID) {
995                 throw new IllegalArgumentException("Invalid uid");
996             }
997             joiner.add(uid.toString());
998         }
999         return joiner.toString();
1000     }
1001 
1002     /**
1003      * Get the list of uids(from {@link Settings}) that should go on cellular networks in preference
1004      * even when higher-priority networks are connected.
1005      *
1006      * @param context The {@link Context} to query the setting.
1007      * @return A list of uids that should go on cellular networks in preference even when
1008      *         higher-priority networks are connected or null if no setting value.
1009      */
1010     @NonNull
getMobileDataPreferredUids(@onNull Context context)1011     public static Set<Integer> getMobileDataPreferredUids(@NonNull Context context) {
1012         final String uidList = Settings.Secure.getString(
1013                 context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS);
1014         return getUidSetFromString(uidList);
1015     }
1016 
1017     /**
1018      * Set the list of uids(to {@link Settings}) that should go on cellular networks in preference
1019      * even when higher-priority networks are connected.
1020      *
1021      * @param context The {@link Context} to set the setting.
1022      * @param uidList A list of uids that should go on cellular networks in preference even when
1023      *             higher-priority networks are connected.
1024      */
setMobileDataPreferredUids(@onNull Context context, @NonNull Set<Integer> uidList)1025     public static void setMobileDataPreferredUids(@NonNull Context context,
1026             @NonNull Set<Integer> uidList) {
1027         final String uids = getUidStringFromSet(uidList);
1028         Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS, uids);
1029     }
1030 
1031     /**
1032      * Get the list of uids (from {@link Settings}) allowed to use restricted networks.
1033      *
1034      * Access to restricted networks is controlled by the (preinstalled-only)
1035      * CONNECTIVITY_USE_RESTRICTED_NETWORKS permission, but highly privileged
1036      * callers can also set a list of uids that can access restricted networks.
1037      *
1038      * This is useful for example in some jurisdictions where government apps,
1039      * that can't be preinstalled, must still have access to emergency services.
1040      *
1041      * @param context The {@link Context} to query the setting.
1042      * @return A list of uids that is allowed to use restricted networks or null if no setting
1043      *         value.
1044      */
1045     @NonNull
getUidsAllowedOnRestrictedNetworks(@onNull Context context)1046     public static Set<Integer> getUidsAllowedOnRestrictedNetworks(@NonNull Context context) {
1047         final String uidList = Settings.Global.getString(
1048                 context.getContentResolver(), UIDS_ALLOWED_ON_RESTRICTED_NETWORKS);
1049         return getUidSetFromString(uidList);
1050     }
1051 
isCallingFromSystem()1052     private static boolean isCallingFromSystem() {
1053         final int uid = Binder.getCallingUid();
1054         final int pid = Binder.getCallingPid();
1055         if (uid == Process.SYSTEM_UID && pid == Process.myPid()) {
1056             return true;
1057         }
1058         return false;
1059     }
1060 
1061     /**
1062      * Set the list of uids(from {@link Settings}) that is allowed to use restricted networks.
1063      *
1064      * @param context The {@link Context} to set the setting.
1065      * @param uidList A list of uids that is allowed to use restricted networks.
1066      */
setUidsAllowedOnRestrictedNetworks(@onNull Context context, @NonNull Set<Integer> uidList)1067     public static void setUidsAllowedOnRestrictedNetworks(@NonNull Context context,
1068             @NonNull Set<Integer> uidList) {
1069         final boolean calledFromSystem = isCallingFromSystem();
1070         if (!calledFromSystem) {
1071             // Enforce NETWORK_SETTINGS check if it's debug build. This is for MTS test only.
1072             if (!Build.isDebuggable()) {
1073                 throw new SecurityException("Only system can set this setting.");
1074             }
1075             context.enforceCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS,
1076                     "Requires NETWORK_SETTINGS permission");
1077         }
1078         final String uids = getUidStringFromSet(uidList);
1079         Settings.Global.putString(context.getContentResolver(), UIDS_ALLOWED_ON_RESTRICTED_NETWORKS,
1080                 uids);
1081     }
1082 
1083     /**
1084      * Get the network bandwidth ingress rate limit.
1085      *
1086      * The limit is only applicable to networks that provide internet connectivity. -1 codes for no
1087      * bandwidth limitation.
1088      *
1089      * @param context The {@link Context} to query the setting.
1090      * @return The rate limit in number of bytes per second or -1 if disabled.
1091      */
getIngressRateLimitInBytesPerSecond(@onNull Context context)1092     public static long getIngressRateLimitInBytesPerSecond(@NonNull Context context) {
1093         return Settings.Global.getLong(context.getContentResolver(),
1094                 INGRESS_RATE_LIMIT_BYTES_PER_SECOND, -1);
1095     }
1096 
1097     /**
1098      * Set the network bandwidth ingress rate limit.
1099      *
1100      * The limit is applied to all networks that provide internet connectivity. It is applied on a
1101      * per-network basis, meaning that global ingress rate could exceed the limit when communicating
1102      * on multiple networks simultaneously.
1103      *
1104      * @param context The {@link Context} to set the setting.
1105      * @param rateLimitInBytesPerSec The rate limit in number of bytes per second or -1 to disable.
1106      */
setIngressRateLimitInBytesPerSecond(@onNull Context context, @IntRange(from = -1L, to = 0xFFFFFFFFL) long rateLimitInBytesPerSec)1107     public static void setIngressRateLimitInBytesPerSecond(@NonNull Context context,
1108             @IntRange(from = -1L, to = 0xFFFFFFFFL) long rateLimitInBytesPerSec) {
1109         if (rateLimitInBytesPerSec < -1) {
1110             throw new IllegalArgumentException(
1111                     "Rate limit must be within the range [-1, Integer.MAX_VALUE]");
1112         }
1113         Settings.Global.putLong(context.getContentResolver(),
1114                 INGRESS_RATE_LIMIT_BYTES_PER_SECOND,
1115                 rateLimitInBytesPerSec);
1116     }
1117 }
1118