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