1 /*
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 
17 package com.android.server.connectivity;
18 
19 import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_MAX_SAMPLES;
20 import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_MIN_SAMPLES;
21 import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
22 import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
23 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
24 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
25 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
26 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
27 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER;
28 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
29 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
30 
31 import android.annotation.NonNull;
32 import android.content.ContentResolver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.net.ConnectivityManager;
36 import android.net.ConnectivitySettingsManager;
37 import android.net.IDnsResolver;
38 import android.net.InetAddresses;
39 import android.net.LinkProperties;
40 import android.net.Network;
41 import android.net.NetworkCapabilities;
42 import android.net.ResolverParamsParcel;
43 import android.net.Uri;
44 import android.net.shared.PrivateDnsConfig;
45 import android.os.Binder;
46 import android.os.RemoteException;
47 import android.os.ServiceSpecificException;
48 import android.os.UserHandle;
49 import android.provider.Settings;
50 import android.text.TextUtils;
51 import android.util.Log;
52 import android.util.Pair;
53 
54 import java.net.InetAddress;
55 import java.util.Arrays;
56 import java.util.Collection;
57 import java.util.Collections;
58 import java.util.HashMap;
59 import java.util.HashSet;
60 import java.util.Iterator;
61 import java.util.Map;
62 import java.util.Set;
63 import java.util.concurrent.ConcurrentHashMap;
64 import java.util.stream.Collectors;
65 
66 /**
67  * Encapsulate the management of DNS settings for networks.
68  *
69  * This class it NOT designed for concurrent access. Furthermore, all non-static
70  * methods MUST be called from ConnectivityService's thread. However, an exceptional
71  * case is getPrivateDnsConfig(Network) which is exclusively for
72  * ConnectivityService#dumpNetworkDiagnostics() on a random binder thread.
73  *
74  * [ Private DNS ]
75  * The code handling Private DNS is spread across several components, but this
76  * seems like the least bad place to collect all the observations.
77  *
78  * Private DNS handling and updating occurs in response to several different
79  * events. Each is described here with its corresponding intended handling.
80  *
81  * [A] Event: A new network comes up.
82  * Mechanics:
83  *     [1] ConnectivityService gets notifications from NetworkAgents.
84  *     [2] in updateNetworkInfo(), the first time the NetworkAgent goes into
85  *         into CONNECTED state, the Private DNS configuration is retrieved,
86  *         programmed, and strict mode hostname resolution (if applicable) is
87  *         enqueued in NetworkAgent's NetworkMonitor, via a call to
88  *         handlePerNetworkPrivateDnsConfig().
89  *     [3] Re-resolution of strict mode hostnames that fail to return any
90  *         IP addresses happens inside NetworkMonitor; it sends itself a
91  *         delayed CMD_EVALUATE_PRIVATE_DNS message in a simple backoff
92  *         schedule.
93  *     [4] Successfully resolved hostnames are sent to ConnectivityService
94  *         inside an EVENT_PRIVATE_DNS_CONFIG_RESOLVED message. The resolved
95  *         IP addresses are programmed into netd via:
96  *
97  *             updatePrivateDns() -> updateDnses()
98  *
99  *         both of which make calls into DnsManager.
100  *     [5] Upon a successful hostname resolution NetworkMonitor initiates a
101  *         validation attempt in the form of a lookup for a one-time hostname
102  *         that uses Private DNS.
103  *
104  * [B] Event: Private DNS settings are changed.
105  * Mechanics:
106  *     [1] ConnectivityService gets notifications from its SettingsObserver.
107  *     [2] handlePrivateDnsSettingsChanged() is called, which calls
108  *         handlePerNetworkPrivateDnsConfig() and the process proceeds
109  *         as if from A.3 above.
110  *
111  * [C] Event: An application calls ConnectivityManager#reportBadNetwork().
112  * Mechanics:
113  *     [1] NetworkMonitor is notified and initiates a reevaluation, which
114  *         always bypasses Private DNS.
115  *     [2] Once completed, NetworkMonitor checks if strict mode is in operation
116  *         and if so enqueues another evaluation of Private DNS, as if from
117  *         step A.5 above.
118  *
119  * @hide
120  */
121 public class DnsManager {
122     private static final String TAG = DnsManager.class.getSimpleName();
123     private static final PrivateDnsConfig PRIVATE_DNS_OFF = new PrivateDnsConfig();
124 
125     /* Defaults for resolver parameters. */
126     private static final int DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800;
127     private static final int DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25;
128     private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
129     private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
130 
131     /**
132      * Get PrivateDnsConfig.
133      */
getPrivateDnsConfig(Context context)134     public static PrivateDnsConfig getPrivateDnsConfig(Context context) {
135         final int mode = ConnectivitySettingsManager.getPrivateDnsMode(context);
136 
137         final boolean useTls = mode != PRIVATE_DNS_MODE_OFF;
138 
139         if (PRIVATE_DNS_MODE_PROVIDER_HOSTNAME == mode) {
140             final String specifier = getStringSetting(context.getContentResolver(),
141                     PRIVATE_DNS_SPECIFIER);
142             return new PrivateDnsConfig(specifier, null);
143         }
144 
145         return new PrivateDnsConfig(useTls);
146     }
147 
getPrivateDnsSettingsUris()148     public static Uri[] getPrivateDnsSettingsUris() {
149         return new Uri[]{
150             Settings.Global.getUriFor(PRIVATE_DNS_DEFAULT_MODE),
151             Settings.Global.getUriFor(PRIVATE_DNS_MODE),
152             Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER),
153         };
154     }
155 
156     public static class PrivateDnsValidationUpdate {
157         public final int netId;
158         public final InetAddress ipAddress;
159         public final String hostname;
160         // Refer to IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_*.
161         public final int validationResult;
162 
PrivateDnsValidationUpdate(int netId, InetAddress ipAddress, String hostname, int validationResult)163         public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress,
164                 String hostname, int validationResult) {
165             this.netId = netId;
166             this.ipAddress = ipAddress;
167             this.hostname = hostname;
168             this.validationResult = validationResult;
169         }
170     }
171 
172     private static class PrivateDnsValidationStatuses {
173         enum ValidationStatus {
174             IN_PROGRESS,
175             FAILED,
176             SUCCEEDED
177         }
178 
179         // Validation statuses of <hostname, ipAddress> pairs for a single netId
180         // Caution : not thread-safe. As mentioned in the top file comment, all
181         // methods of this class must only be called on ConnectivityService's thread.
182         private Map<Pair<String, InetAddress>, ValidationStatus> mValidationMap;
183 
PrivateDnsValidationStatuses()184         private PrivateDnsValidationStatuses() {
185             mValidationMap = new HashMap<>();
186         }
187 
hasValidatedServer()188         private boolean hasValidatedServer() {
189             for (ValidationStatus status : mValidationMap.values()) {
190                 if (status == ValidationStatus.SUCCEEDED) {
191                     return true;
192                 }
193             }
194             return false;
195         }
196 
updateTrackedDnses(String[] ipAddresses, String hostname)197         private void updateTrackedDnses(String[] ipAddresses, String hostname) {
198             Set<Pair<String, InetAddress>> latestDnses = new HashSet<>();
199             for (String ipAddress : ipAddresses) {
200                 try {
201                     latestDnses.add(new Pair(hostname,
202                             InetAddresses.parseNumericAddress(ipAddress)));
203                 } catch (IllegalArgumentException e) {}
204             }
205             // Remove <hostname, ipAddress> pairs that should not be tracked.
206             for (Iterator<Map.Entry<Pair<String, InetAddress>, ValidationStatus>> it =
207                     mValidationMap.entrySet().iterator(); it.hasNext(); ) {
208                 Map.Entry<Pair<String, InetAddress>, ValidationStatus> entry = it.next();
209                 if (!latestDnses.contains(entry.getKey())) {
210                     it.remove();
211                 }
212             }
213             // Add new <hostname, ipAddress> pairs that should be tracked.
214             for (Pair<String, InetAddress> p : latestDnses) {
215                 if (!mValidationMap.containsKey(p)) {
216                     mValidationMap.put(p, ValidationStatus.IN_PROGRESS);
217                 }
218             }
219         }
220 
updateStatus(PrivateDnsValidationUpdate update)221         private void updateStatus(PrivateDnsValidationUpdate update) {
222             Pair<String, InetAddress> p = new Pair(update.hostname,
223                     update.ipAddress);
224             if (!mValidationMap.containsKey(p)) {
225                 return;
226             }
227             if (update.validationResult == VALIDATION_RESULT_SUCCESS) {
228                 mValidationMap.put(p, ValidationStatus.SUCCEEDED);
229             } else if (update.validationResult == VALIDATION_RESULT_FAILURE) {
230                 mValidationMap.put(p, ValidationStatus.FAILED);
231             } else {
232                 Log.e(TAG, "Unknown private dns validation operation="
233                         + update.validationResult);
234             }
235         }
236 
fillInValidatedPrivateDns(LinkProperties lp)237         private LinkProperties fillInValidatedPrivateDns(LinkProperties lp) {
238             lp.setValidatedPrivateDnsServers(Collections.EMPTY_LIST);
239             mValidationMap.forEach((key, value) -> {
240                     if (value == ValidationStatus.SUCCEEDED) {
241                         lp.addValidatedPrivateDnsServer(key.second);
242                     }
243                 });
244             return lp;
245         }
246     }
247 
248     private final Context mContext;
249     private final ContentResolver mContentResolver;
250     private final IDnsResolver mDnsResolver;
251     private final ConcurrentHashMap<Integer, PrivateDnsConfig> mPrivateDnsMap;
252     // TODO: Replace the Map with SparseArrays.
253     private final Map<Integer, PrivateDnsValidationStatuses> mPrivateDnsValidationMap;
254     private final Map<Integer, LinkProperties> mLinkPropertiesMap;
255     private final Map<Integer, NetworkCapabilities> mNetworkCapabilitiesMap;
256 
257     private int mSampleValidity;
258     private int mSuccessThreshold;
259     private int mMinSamples;
260     private int mMaxSamples;
261 
DnsManager(Context ctx, IDnsResolver dnsResolver)262     public DnsManager(Context ctx, IDnsResolver dnsResolver) {
263         mContext = ctx;
264         mContentResolver = mContext.getContentResolver();
265         mDnsResolver = dnsResolver;
266         mPrivateDnsMap = new ConcurrentHashMap<>();
267         mPrivateDnsValidationMap = new HashMap<>();
268         mLinkPropertiesMap = new HashMap<>();
269         mNetworkCapabilitiesMap = new HashMap<>();
270 
271         // TODO: Create and register ContentObservers to track every setting
272         // used herein, posting messages to respond to changes.
273     }
274 
getPrivateDnsConfig()275     public PrivateDnsConfig getPrivateDnsConfig() {
276         return getPrivateDnsConfig(mContext);
277     }
278 
removeNetwork(Network network)279     public void removeNetwork(Network network) {
280         mPrivateDnsMap.remove(network.getNetId());
281         mPrivateDnsValidationMap.remove(network.getNetId());
282         mNetworkCapabilitiesMap.remove(network.getNetId());
283         mLinkPropertiesMap.remove(network.getNetId());
284     }
285 
286     // This is exclusively called by ConnectivityService#dumpNetworkDiagnostics() which
287     // is not on the ConnectivityService handler thread.
getPrivateDnsConfig(@onNull Network network)288     public PrivateDnsConfig getPrivateDnsConfig(@NonNull Network network) {
289         return mPrivateDnsMap.getOrDefault(network.getNetId(), PRIVATE_DNS_OFF);
290     }
291 
updatePrivateDns(Network network, PrivateDnsConfig cfg)292     public PrivateDnsConfig updatePrivateDns(Network network, PrivateDnsConfig cfg) {
293         Log.w(TAG, "updatePrivateDns(" + network + ", " + cfg + ")");
294         return (cfg != null)
295                 ? mPrivateDnsMap.put(network.getNetId(), cfg)
296                 : mPrivateDnsMap.remove(network.getNetId());
297     }
298 
updatePrivateDnsStatus(int netId, LinkProperties lp)299     public void updatePrivateDnsStatus(int netId, LinkProperties lp) {
300         // Use the PrivateDnsConfig data pushed to this class instance
301         // from ConnectivityService.
302         final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
303                 PRIVATE_DNS_OFF);
304 
305         final boolean useTls = privateDnsCfg.mode != PRIVATE_DNS_MODE_OFF;
306         final PrivateDnsValidationStatuses statuses =
307                 useTls ? mPrivateDnsValidationMap.get(netId) : null;
308         final boolean validated = (null != statuses) && statuses.hasValidatedServer();
309         final boolean strictMode = privateDnsCfg.inStrictMode();
310         final String tlsHostname = strictMode ? privateDnsCfg.hostname : null;
311         final boolean usingPrivateDns = strictMode || validated;
312 
313         lp.setUsePrivateDns(usingPrivateDns);
314         lp.setPrivateDnsServerName(tlsHostname);
315         if (usingPrivateDns && null != statuses) {
316             statuses.fillInValidatedPrivateDns(lp);
317         } else {
318             lp.setValidatedPrivateDnsServers(Collections.EMPTY_LIST);
319         }
320     }
321 
updatePrivateDnsValidation(PrivateDnsValidationUpdate update)322     public void updatePrivateDnsValidation(PrivateDnsValidationUpdate update) {
323         final PrivateDnsValidationStatuses statuses = mPrivateDnsValidationMap.get(update.netId);
324         if (statuses == null) return;
325         statuses.updateStatus(update);
326     }
327 
328     /**
329      * Update {@link NetworkCapabilities} stored in this instance.
330      *
331      * In order to ensure that the resolver has access to necessary information when other events
332      * occur, capabilities are always saved to a hashMap before updating the DNS configuration
333      * whenever a new network is created, transport types are modified, or metered capabilities are
334      * altered for a network. When a network is destroyed, the corresponding entry is removed from
335      * the hashMap. To prevent concurrency issues, the hashMap should always be accessed from the
336      * same thread.
337      */
updateCapabilitiesForNetwork(int netId, @NonNull final NetworkCapabilities nc)338     public void updateCapabilitiesForNetwork(int netId, @NonNull final NetworkCapabilities nc) {
339         mNetworkCapabilitiesMap.put(netId, nc);
340         sendDnsConfigurationForNetwork(netId);
341     }
342 
343     /**
344      * When {@link LinkProperties} are changed in a specific network, they are
345      * always saved to a hashMap before update dns config.
346      * When destroying network, the specific network will be removed from the hashMap.
347      * The hashMap is always accessed on the same thread.
348      */
noteDnsServersForNetwork(int netId, @NonNull LinkProperties lp)349     public void noteDnsServersForNetwork(int netId, @NonNull LinkProperties lp) {
350         mLinkPropertiesMap.put(netId, lp);
351         sendDnsConfigurationForNetwork(netId);
352     }
353 
354     /**
355      * Send dns configuration parameters to resolver for a given network.
356      */
sendDnsConfigurationForNetwork(int netId)357     public void sendDnsConfigurationForNetwork(int netId) {
358         final LinkProperties lp = mLinkPropertiesMap.get(netId);
359         final NetworkCapabilities nc = mNetworkCapabilitiesMap.get(netId);
360         if (lp == null || nc == null) return;
361         updateParametersSettings();
362         final ResolverParamsParcel paramsParcel = new ResolverParamsParcel();
363 
364         // We only use the PrivateDnsConfig data pushed to this class instance
365         // from ConnectivityService because it works in coordination with
366         // NetworkMonitor to decide which networks need validation and runs the
367         // blocking calls to resolve Private DNS strict mode hostnames.
368         //
369         // At this time we do not attempt to enable Private DNS on non-Internet
370         // networks like IMS.
371         final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
372                 PRIVATE_DNS_OFF);
373         final boolean useTls = privateDnsCfg.mode != PRIVATE_DNS_MODE_OFF;
374         final boolean strictMode = privateDnsCfg.inStrictMode();
375 
376         paramsParcel.netId = netId;
377         paramsParcel.sampleValiditySeconds = mSampleValidity;
378         paramsParcel.successThreshold = mSuccessThreshold;
379         paramsParcel.minSamples = mMinSamples;
380         paramsParcel.maxSamples = mMaxSamples;
381         paramsParcel.servers = makeStrings(lp.getDnsServers());
382         paramsParcel.domains = getDomainStrings(lp.getDomains());
383         paramsParcel.tlsName = strictMode ? privateDnsCfg.hostname : "";
384         paramsParcel.tlsServers =
385                 strictMode ? makeStrings(
386                         Arrays.stream(privateDnsCfg.ips)
387                               .filter((ip) -> lp.isReachable(ip))
388                               .collect(Collectors.toList()))
389                 : useTls ? paramsParcel.servers  // Opportunistic
390                 : new String[0];            // Off
391         paramsParcel.transportTypes = nc.getTransportTypes();
392         paramsParcel.meteredNetwork = nc.isMetered();
393         paramsParcel.interfaceNames = lp.getAllInterfaceNames().toArray(new String[0]);
394         // Prepare to track the validation status of the DNS servers in the
395         // resolver config when private DNS is in opportunistic or strict mode.
396         if (useTls) {
397             if (!mPrivateDnsValidationMap.containsKey(netId)) {
398                 mPrivateDnsValidationMap.put(netId, new PrivateDnsValidationStatuses());
399             }
400             mPrivateDnsValidationMap.get(netId).updateTrackedDnses(paramsParcel.tlsServers,
401                     paramsParcel.tlsName);
402         } else {
403             mPrivateDnsValidationMap.remove(netId);
404         }
405 
406         Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
407                 + "%d, %d, %s, %s, %s, %b, %s)", paramsParcel.netId,
408                 Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
409                 paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
410                 paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
411                 paramsParcel.retryCount, paramsParcel.tlsName,
412                 Arrays.toString(paramsParcel.tlsServers),
413                 Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork,
414                 Arrays.toString(paramsParcel.interfaceNames)));
415 
416         try {
417             mDnsResolver.setResolverConfiguration(paramsParcel);
418         } catch (RemoteException | ServiceSpecificException e) {
419             Log.e(TAG, "Error setting DNS configuration: " + e);
420             return;
421         }
422     }
423 
424     /**
425      * Flush DNS caches and events work before boot has completed.
426      */
flushVmDnsCache()427     public void flushVmDnsCache() {
428         /*
429          * Tell the VMs to toss their DNS caches
430          */
431         final Intent intent = new Intent(ConnectivityManager.ACTION_CLEAR_DNS_CACHE);
432         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
433         /*
434          * Connectivity events can happen before boot has completed ...
435          */
436         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
437         final long ident = Binder.clearCallingIdentity();
438         try {
439             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
440         } finally {
441             Binder.restoreCallingIdentity(ident);
442         }
443     }
444 
updateParametersSettings()445     private void updateParametersSettings() {
446         mSampleValidity = getIntSetting(
447                 DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
448                 DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
449         if (mSampleValidity < 0 || mSampleValidity > 65535) {
450             Log.w(TAG, "Invalid sampleValidity=" + mSampleValidity + ", using default="
451                     + DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
452             mSampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
453         }
454 
455         mSuccessThreshold = getIntSetting(
456                 DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
457                 DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
458         if (mSuccessThreshold < 0 || mSuccessThreshold > 100) {
459             Log.w(TAG, "Invalid successThreshold=" + mSuccessThreshold + ", using default="
460                     + DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
461             mSuccessThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
462         }
463 
464         mMinSamples = getIntSetting(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
465         mMaxSamples = getIntSetting(DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
466         if (mMinSamples < 0 || mMinSamples > mMaxSamples || mMaxSamples > 64) {
467             Log.w(TAG, "Invalid sample count (min, max)=(" + mMinSamples + ", " + mMaxSamples
468                     + "), using default=(" + DNS_RESOLVER_DEFAULT_MIN_SAMPLES + ", "
469                     + DNS_RESOLVER_DEFAULT_MAX_SAMPLES + ")");
470             mMinSamples = DNS_RESOLVER_DEFAULT_MIN_SAMPLES;
471             mMaxSamples = DNS_RESOLVER_DEFAULT_MAX_SAMPLES;
472         }
473     }
474 
getIntSetting(String which, int dflt)475     private int getIntSetting(String which, int dflt) {
476         return Settings.Global.getInt(mContentResolver, which, dflt);
477     }
478 
479     /**
480      * Create a string array of host addresses from a collection of InetAddresses
481      *
482      * @param addrs a Collection of InetAddresses
483      * @return an array of Strings containing their host addresses
484      */
makeStrings(Collection<InetAddress> addrs)485     private String[] makeStrings(Collection<InetAddress> addrs) {
486         String[] result = new String[addrs.size()];
487         int i = 0;
488         for (InetAddress addr : addrs) {
489             result[i++] = addr.getHostAddress();
490         }
491         return result;
492     }
493 
getStringSetting(ContentResolver cr, String which)494     private static String getStringSetting(ContentResolver cr, String which) {
495         return Settings.Global.getString(cr, which);
496     }
497 
getDomainStrings(String domains)498     private static String[] getDomainStrings(String domains) {
499         return (TextUtils.isEmpty(domains)) ? new String[0] : domains.split(" ");
500     }
501 }
502