WifiManager.java revision e746f03c6c8b8d0897d322ab524d545ace200fcd
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.net.wifi; 18 19import android.annotation.SdkConstant; 20import android.annotation.SdkConstant.SdkConstantType; 21import android.net.DhcpInfo; 22import android.os.Binder; 23import android.os.IBinder; 24import android.os.Handler; 25import android.os.RemoteException; 26import android.os.WorkSource; 27 28import java.util.List; 29 30/** 31 * This class provides the primary API for managing all aspects of Wi-Fi 32 * connectivity. Get an instance of this class by calling 33 * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context.WIFI_SERVICE)}. 34 35 * It deals with several categories of items: 36 * <ul> 37 * <li>The list of configured networks. The list can be viewed and updated, 38 * and attributes of individual entries can be modified.</li> 39 * <li>The currently active Wi-Fi network, if any. Connectivity can be 40 * established or torn down, and dynamic information about the state of 41 * the network can be queried.</li> 42 * <li>Results of access point scans, containing enough information to 43 * make decisions about what access point to connect to.</li> 44 * <li>It defines the names of various Intent actions that are broadcast 45 * upon any sort of change in Wi-Fi state. 46 * </ul> 47 * This is the API to use when performing Wi-Fi specific operations. To 48 * perform operations that pertain to network connectivity at an abstract 49 * level, use {@link android.net.ConnectivityManager}. 50 */ 51public class WifiManager { 52 53 // Supplicant error codes: 54 /** 55 * The error code if there was a problem authenticating. 56 */ 57 public static final int ERROR_AUTHENTICATING = 1; 58 59 /** 60 * Broadcast intent action indicating that Wi-Fi has been enabled, disabled, 61 * enabling, disabling, or unknown. One extra provides this state as an int. 62 * Another extra provides the previous state, if available. 63 * 64 * @see #EXTRA_WIFI_STATE 65 * @see #EXTRA_PREVIOUS_WIFI_STATE 66 */ 67 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 68 public static final String WIFI_STATE_CHANGED_ACTION = 69 "android.net.wifi.WIFI_STATE_CHANGED"; 70 /** 71 * The lookup key for an int that indicates whether Wi-Fi is enabled, 72 * disabled, enabling, disabling, or unknown. Retrieve it with 73 * {@link android.content.Intent#getIntExtra(String,int)}. 74 * 75 * @see #WIFI_STATE_DISABLED 76 * @see #WIFI_STATE_DISABLING 77 * @see #WIFI_STATE_ENABLED 78 * @see #WIFI_STATE_ENABLING 79 * @see #WIFI_STATE_UNKNOWN 80 */ 81 public static final String EXTRA_WIFI_STATE = "wifi_state"; 82 /** 83 * The previous Wi-Fi state. 84 * 85 * @see #EXTRA_WIFI_STATE 86 */ 87 public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state"; 88 89 /** 90 * Wi-Fi is currently being disabled. The state will change to {@link #WIFI_STATE_DISABLED} if 91 * it finishes successfully. 92 * 93 * @see #WIFI_STATE_CHANGED_ACTION 94 * @see #getWifiState() 95 */ 96 public static final int WIFI_STATE_DISABLING = 0; 97 /** 98 * Wi-Fi is disabled. 99 * 100 * @see #WIFI_STATE_CHANGED_ACTION 101 * @see #getWifiState() 102 */ 103 public static final int WIFI_STATE_DISABLED = 1; 104 /** 105 * Wi-Fi is currently being enabled. The state will change to {@link #WIFI_STATE_ENABLED} if 106 * it finishes successfully. 107 * 108 * @see #WIFI_STATE_CHANGED_ACTION 109 * @see #getWifiState() 110 */ 111 public static final int WIFI_STATE_ENABLING = 2; 112 /** 113 * Wi-Fi is enabled. 114 * 115 * @see #WIFI_STATE_CHANGED_ACTION 116 * @see #getWifiState() 117 */ 118 public static final int WIFI_STATE_ENABLED = 3; 119 /** 120 * Wi-Fi is in an unknown state. This state will occur when an error happens while enabling 121 * or disabling. 122 * 123 * @see #WIFI_STATE_CHANGED_ACTION 124 * @see #getWifiState() 125 */ 126 public static final int WIFI_STATE_UNKNOWN = 4; 127 128 /** 129 * Broadcast intent action indicating that Wi-Fi AP has been enabled, disabled, 130 * enabling, disabling, or failed. 131 * 132 * @hide 133 */ 134 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 135 public static final String WIFI_AP_STATE_CHANGED_ACTION = 136 "android.net.wifi.WIFI_AP_STATE_CHANGED"; 137 138 /** 139 * The lookup key for an int that indicates whether Wi-Fi AP is enabled, 140 * disabled, enabling, disabling, or failed. Retrieve it with 141 * {@link android.content.Intent#getIntExtra(String,int)}. 142 * 143 * @see #WIFI_AP_STATE_DISABLED 144 * @see #WIFI_AP_STATE_DISABLING 145 * @see #WIFI_AP_STATE_ENABLED 146 * @see #WIFI_AP_STATE_ENABLING 147 * @see #WIFI_AP_STATE_FAILED 148 * 149 * @hide 150 */ 151 public static final String EXTRA_WIFI_AP_STATE = "wifi_state"; 152 /** 153 * The previous Wi-Fi state. 154 * 155 * @see #EXTRA_WIFI_AP_STATE 156 * 157 * @hide 158 */ 159 public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state"; 160 /** 161 * Wi-Fi AP is currently being disabled. The state will change to 162 * {@link #WIFI_AP_STATE_DISABLED} if it finishes successfully. 163 * 164 * @see #WIFI_AP_STATE_CHANGED_ACTION 165 * @see #getWifiApState() 166 * 167 * @hide 168 */ 169 public static final int WIFI_AP_STATE_DISABLING = 10; 170 /** 171 * Wi-Fi AP is disabled. 172 * 173 * @see #WIFI_AP_STATE_CHANGED_ACTION 174 * @see #getWifiState() 175 * 176 * @hide 177 */ 178 public static final int WIFI_AP_STATE_DISABLED = 11; 179 /** 180 * Wi-Fi AP is currently being enabled. The state will change to 181 * {@link #WIFI_AP_STATE_ENABLED} if it finishes successfully. 182 * 183 * @see #WIFI_AP_STATE_CHANGED_ACTION 184 * @see #getWifiApState() 185 * 186 * @hide 187 */ 188 public static final int WIFI_AP_STATE_ENABLING = 12; 189 /** 190 * Wi-Fi AP is enabled. 191 * 192 * @see #WIFI_AP_STATE_CHANGED_ACTION 193 * @see #getWifiApState() 194 * 195 * @hide 196 */ 197 public static final int WIFI_AP_STATE_ENABLED = 13; 198 /** 199 * Wi-Fi AP is in a failed state. This state will occur when an error occurs during 200 * enabling or disabling 201 * 202 * @see #WIFI_AP_STATE_CHANGED_ACTION 203 * @see #getWifiApState() 204 * 205 * @hide 206 */ 207 public static final int WIFI_AP_STATE_FAILED = 14; 208 209 /** 210 * Broadcast intent action indicating that a connection to the supplicant has 211 * been established (and it is now possible 212 * to perform Wi-Fi operations) or the connection to the supplicant has been 213 * lost. One extra provides the connection state as a boolean, where {@code true} 214 * means CONNECTED. 215 * @see #EXTRA_SUPPLICANT_CONNECTED 216 */ 217 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 218 public static final String SUPPLICANT_CONNECTION_CHANGE_ACTION = 219 "android.net.wifi.supplicant.CONNECTION_CHANGE"; 220 /** 221 * The lookup key for a boolean that indicates whether a connection to 222 * the supplicant daemon has been gained or lost. {@code true} means 223 * a connection now exists. 224 * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. 225 */ 226 public static final String EXTRA_SUPPLICANT_CONNECTED = "connected"; 227 /** 228 * Broadcast intent action indicating that the state of Wi-Fi connectivity 229 * has changed. One extra provides the new state 230 * in the form of a {@link android.net.NetworkInfo} object. If the new state is 231 * CONNECTED, a second extra may provide the BSSID of the access point, 232 * as a {@code String}. 233 * @see #EXTRA_NETWORK_INFO 234 * @see #EXTRA_BSSID 235 */ 236 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 237 public static final String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE"; 238 /** 239 * The lookup key for a {@link android.net.NetworkInfo} object associated with the 240 * Wi-Fi network. Retrieve with 241 * {@link android.content.Intent#getParcelableExtra(String)}. 242 */ 243 public static final String EXTRA_NETWORK_INFO = "networkInfo"; 244 /** 245 * The lookup key for a String giving the BSSID of the access point to which 246 * we are connected. Only present when the new state is CONNECTED. 247 * Retrieve with 248 * {@link android.content.Intent#getStringExtra(String)}. 249 */ 250 public static final String EXTRA_BSSID = "bssid"; 251 /** 252 * Broadcast intent action indicating that the state of establishing a connection to 253 * an access point has changed.One extra provides the new 254 * {@link SupplicantState}. Note that the supplicant state is Wi-Fi specific, and 255 * is not generally the most useful thing to look at if you are just interested in 256 * the overall state of connectivity. 257 * @see #EXTRA_NEW_STATE 258 * @see #EXTRA_SUPPLICANT_ERROR 259 */ 260 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 261 public static final String SUPPLICANT_STATE_CHANGED_ACTION = 262 "android.net.wifi.supplicant.STATE_CHANGE"; 263 /** 264 * The lookup key for a {@link SupplicantState} describing the new state 265 * Retrieve with 266 * {@link android.content.Intent#getParcelableExtra(String)}. 267 */ 268 public static final String EXTRA_NEW_STATE = "newState"; 269 270 /** 271 * The lookup key for a {@link SupplicantState} describing the supplicant 272 * error code if any 273 * Retrieve with 274 * {@link android.content.Intent#getIntExtra(String, int)}. 275 * @see #ERROR_AUTHENTICATING 276 */ 277 public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError"; 278 /** 279 * Broadcast intent action indicating that the supplicant configuration changed. 280 * This can be as a result of adding/updating/deleting a network 281 * @hide 282 */ 283 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 284 public static final String SUPPLICANT_CONFIG_CHANGED_ACTION = 285 "android.net.wifi.supplicant.CONFIG_CHANGE"; 286 /** 287 * An access point scan has completed, and results are available from the supplicant. 288 * Call {@link #getScanResults()} to obtain the results. 289 */ 290 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 291 public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS"; 292 /** 293 * The RSSI (signal strength) has changed. 294 * @see #EXTRA_NEW_RSSI 295 */ 296 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 297 public static final String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED"; 298 /** 299 * The lookup key for an {@code int} giving the new RSSI in dBm. 300 */ 301 public static final String EXTRA_NEW_RSSI = "newRssi"; 302 303 /** 304 * Broadcast intent action indicating that the IP configuration 305 * changed on wifi. 306 * @hide 307 */ 308 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 309 public static final String CONFIG_CHANGED_ACTION = "android.net.wifi.CONFIG_CHANGED"; 310 /** 311 * The lookup key for a {@link android.net.LinkProperties} object associated with the 312 * Wi-Fi network. Retrieve with 313 * {@link android.content.Intent#getParcelableExtra(String)}. 314 * @hide 315 */ 316 public static final String EXTRA_LINK_PROPERTIES = "linkProperties"; 317 318 /** 319 * The network IDs of the configured networks could have changed. 320 */ 321 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 322 public static final String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED"; 323 324 /** 325 * Activity Action: Pick a Wi-Fi network to connect to. 326 * <p>Input: Nothing. 327 * <p>Output: Nothing. 328 */ 329 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 330 public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; 331 332 /** 333 * In this Wi-Fi lock mode, Wi-Fi will be kept active, 334 * and will behave normally, i.e., it will attempt to automatically 335 * establish a connection to a remembered access point that is 336 * within range, and will do periodic scans if there are remembered 337 * access points but none are in range. 338 */ 339 public static final int WIFI_MODE_FULL = 1; 340 /** 341 * In this Wi-Fi lock mode, Wi-Fi will be kept active, 342 * but the only operation that will be supported is initiation of 343 * scans, and the subsequent reporting of scan results. No attempts 344 * will be made to automatically connect to remembered access points, 345 * nor will periodic scans be automatically performed looking for 346 * remembered access points. Scans must be explicitly requested by 347 * an application in this mode. 348 */ 349 public static final int WIFI_MODE_SCAN_ONLY = 2; 350 351 /** Anything worse than or equal to this will show 0 bars. */ 352 private static final int MIN_RSSI = -100; 353 354 /** Anything better than or equal to this will show the max bars. */ 355 private static final int MAX_RSSI = -55; 356 357 IWifiManager mService; 358 Handler mHandler; 359 360 /* Maximum number of active locks we allow. 361 * This limit was added to prevent apps from creating a ridiculous number 362 * of locks and crashing the system by overflowing the global ref table. 363 */ 364 private static final int MAX_ACTIVE_LOCKS = 50; 365 366 /* Number of currently active WifiLocks and MulticastLocks */ 367 private int mActiveLockCount; 368 369 /** 370 * Create a new WifiManager instance. 371 * Applications will almost always want to use 372 * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve 373 * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. 374 * @param service the Binder interface 375 * @param handler target for messages 376 * @hide - hide this because it takes in a parameter of type IWifiManager, which 377 * is a system private class. 378 */ 379 public WifiManager(IWifiManager service, Handler handler) { 380 mService = service; 381 mHandler = handler; 382 } 383 384 /** 385 * Return a list of all the networks configured in the supplicant. 386 * Not all fields of WifiConfiguration are returned. Only the following 387 * fields are filled in: 388 * <ul> 389 * <li>networkId</li> 390 * <li>SSID</li> 391 * <li>BSSID</li> 392 * <li>priority</li> 393 * <li>allowedProtocols</li> 394 * <li>allowedKeyManagement</li> 395 * <li>allowedAuthAlgorithms</li> 396 * <li>allowedPairwiseCiphers</li> 397 * <li>allowedGroupCiphers</li> 398 * </ul> 399 * @return a list of network configurations in the form of a list 400 * of {@link WifiConfiguration} objects. 401 */ 402 public List<WifiConfiguration> getConfiguredNetworks() { 403 try { 404 return mService.getConfiguredNetworks(); 405 } catch (RemoteException e) { 406 return null; 407 } 408 } 409 410 /** 411 * Add a new network description to the set of configured networks. 412 * The {@code networkId} field of the supplied configuration object 413 * is ignored. 414 * <p/> 415 * The new network will be marked DISABLED by default. To enable it, 416 * called {@link #enableNetwork}. 417 * 418 * @param config the set of variables that describe the configuration, 419 * contained in a {@link WifiConfiguration} object. 420 * @return the ID of the newly created network description. This is used in 421 * other operations to specified the network to be acted upon. 422 * Returns {@code -1} on failure. 423 */ 424 public int addNetwork(WifiConfiguration config) { 425 if (config == null) { 426 return -1; 427 } 428 config.networkId = -1; 429 return addOrUpdateNetwork(config); 430 } 431 432 /** 433 * Update the network description of an existing configured network. 434 * 435 * @param config the set of variables that describe the configuration, 436 * contained in a {@link WifiConfiguration} object. It may 437 * be sparse, so that only the items that are being changed 438 * are non-<code>null</code>. The {@code networkId} field 439 * must be set to the ID of the existing network being updated. 440 * @return Returns the {@code networkId} of the supplied 441 * {@code WifiConfiguration} on success. 442 * <br/> 443 * Returns {@code -1} on failure, including when the {@code networkId} 444 * field of the {@code WifiConfiguration} does not refer to an 445 * existing network. 446 */ 447 public int updateNetwork(WifiConfiguration config) { 448 if (config == null || config.networkId < 0) { 449 return -1; 450 } 451 return addOrUpdateNetwork(config); 452 } 453 454 /** 455 * Internal method for doing the RPC that creates a new network description 456 * or updates an existing one. 457 * 458 * @param config The possibly sparse object containing the variables that 459 * are to set or updated in the network description. 460 * @return the ID of the network on success, {@code -1} on failure. 461 */ 462 private int addOrUpdateNetwork(WifiConfiguration config) { 463 try { 464 return mService.addOrUpdateNetwork(config); 465 } catch (RemoteException e) { 466 return -1; 467 } 468 } 469 470 /** 471 * Remove the specified network from the list of configured networks. 472 * This may result in the asynchronous delivery of state change 473 * events. 474 * @param netId the integer that identifies the network configuration 475 * to the supplicant 476 * @return {@code true} if the operation succeeded 477 */ 478 public boolean removeNetwork(int netId) { 479 try { 480 return mService.removeNetwork(netId); 481 } catch (RemoteException e) { 482 return false; 483 } 484 } 485 486 /** 487 * Allow a previously configured network to be associated with. If 488 * <code>disableOthers</code> is true, then all other configured 489 * networks are disabled, and an attempt to connect to the selected 490 * network is initiated. This may result in the asynchronous delivery 491 * of state change events. 492 * @param netId the ID of the network in the list of configured networks 493 * @param disableOthers if true, disable all other networks. The way to 494 * select a particular network to connect to is specify {@code true} 495 * for this parameter. 496 * @return {@code true} if the operation succeeded 497 */ 498 public boolean enableNetwork(int netId, boolean disableOthers) { 499 try { 500 return mService.enableNetwork(netId, disableOthers); 501 } catch (RemoteException e) { 502 return false; 503 } 504 } 505 506 /** 507 * Disable a configured network. The specified network will not be 508 * a candidate for associating. This may result in the asynchronous 509 * delivery of state change events. 510 * @param netId the ID of the network as returned by {@link #addNetwork}. 511 * @return {@code true} if the operation succeeded 512 */ 513 public boolean disableNetwork(int netId) { 514 try { 515 return mService.disableNetwork(netId); 516 } catch (RemoteException e) { 517 return false; 518 } 519 } 520 521 /** 522 * Disassociate from the currently active access point. This may result 523 * in the asynchronous delivery of state change events. 524 * @return {@code true} if the operation succeeded 525 */ 526 public boolean disconnect() { 527 try { 528 mService.disconnect(); 529 return true; 530 } catch (RemoteException e) { 531 return false; 532 } 533 } 534 535 /** 536 * Reconnect to the currently active access point, if we are currently 537 * disconnected. This may result in the asynchronous delivery of state 538 * change events. 539 * @return {@code true} if the operation succeeded 540 */ 541 public boolean reconnect() { 542 try { 543 mService.reconnect(); 544 return true; 545 } catch (RemoteException e) { 546 return false; 547 } 548 } 549 550 /** 551 * Reconnect to the currently active access point, even if we are already 552 * connected. This may result in the asynchronous delivery of state 553 * change events. 554 * @return {@code true} if the operation succeeded 555 */ 556 public boolean reassociate() { 557 try { 558 mService.reassociate(); 559 return true; 560 } catch (RemoteException e) { 561 return false; 562 } 563 } 564 565 /** 566 * Check that the supplicant daemon is responding to requests. 567 * @return {@code true} if we were able to communicate with the supplicant and 568 * it returned the expected response to the PING message. 569 */ 570 public boolean pingSupplicant() { 571 if (mService == null) 572 return false; 573 try { 574 return mService.pingSupplicant(); 575 } catch (RemoteException e) { 576 return false; 577 } 578 } 579 580 /** 581 * Request a scan for access points. Returns immediately. The availability 582 * of the results is made known later by means of an asynchronous event sent 583 * on completion of the scan. 584 * @return {@code true} if the operation succeeded, i.e., the scan was initiated 585 */ 586 public boolean startScan() { 587 try { 588 mService.startScan(false); 589 return true; 590 } catch (RemoteException e) { 591 return false; 592 } 593 } 594 595 /** 596 * Request a scan for access points. Returns immediately. The availability 597 * of the results is made known later by means of an asynchronous event sent 598 * on completion of the scan. 599 * This is a variant of startScan that forces an active scan, even if passive 600 * scans are the current default 601 * @return {@code true} if the operation succeeded, i.e., the scan was initiated 602 * 603 * @hide 604 */ 605 public boolean startScanActive() { 606 try { 607 mService.startScan(true); 608 return true; 609 } catch (RemoteException e) { 610 return false; 611 } 612 } 613 614 /** 615 * Return dynamic information about the current Wi-Fi connection, if any is active. 616 * @return the Wi-Fi information, contained in {@link WifiInfo}. 617 */ 618 public WifiInfo getConnectionInfo() { 619 try { 620 return mService.getConnectionInfo(); 621 } catch (RemoteException e) { 622 return null; 623 } 624 } 625 626 /** 627 * Return the results of the latest access point scan. 628 * @return the list of access points found in the most recent scan. 629 */ 630 public List<ScanResult> getScanResults() { 631 try { 632 return mService.getScanResults(); 633 } catch (RemoteException e) { 634 return null; 635 } 636 } 637 638 /** 639 * Tell the supplicant to persist the current list of configured networks. 640 * <p> 641 * Note: It is possible for this method to change the network IDs of 642 * existing networks. You should assume the network IDs can be different 643 * after calling this method. 644 * 645 * @return {@code true} if the operation succeeded 646 */ 647 public boolean saveConfiguration() { 648 try { 649 return mService.saveConfiguration(); 650 } catch (RemoteException e) { 651 return false; 652 } 653 } 654 655 /** 656 * Return the number of frequency channels that are allowed 657 * to be used in the current regulatory domain. 658 * @return the number of allowed channels, or {@code -1} if an error occurs 659 * 660 * @hide pending API council 661 */ 662 public int getNumAllowedChannels() { 663 try { 664 return mService.getNumAllowedChannels(); 665 } catch (RemoteException e) { 666 return -1; 667 } 668 } 669 670 /** 671 * Set the number of frequency channels that are allowed to be used 672 * in the current regulatory domain. This method should be used only 673 * if the correct number of channels cannot be determined automatically 674 * for some reason. 675 * @param numChannels the number of allowed channels. Must be greater than 0 676 * and less than or equal to 16. 677 * @param persist {@code true} if you want this remembered 678 * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g., 679 * {@code numChannels} is out of range. 680 * 681 * @hide pending API council 682 */ 683 public boolean setNumAllowedChannels(int numChannels, boolean persist) { 684 try { 685 return mService.setNumAllowedChannels(numChannels, persist); 686 } catch (RemoteException e) { 687 return false; 688 } 689 } 690 691 /** 692 * Return the list of valid values for the number of allowed radio channels 693 * for various regulatory domains. 694 * @return the list of channel counts, or {@code null} if the operation fails 695 * 696 * @hide pending API council review 697 */ 698 public int[] getValidChannelCounts() { 699 try { 700 return mService.getValidChannelCounts(); 701 } catch (RemoteException e) { 702 return null; 703 } 704 } 705 706 /** 707 * Return the DHCP-assigned addresses from the last successful DHCP request, 708 * if any. 709 * @return the DHCP information 710 */ 711 public DhcpInfo getDhcpInfo() { 712 try { 713 return mService.getDhcpInfo(); 714 } catch (RemoteException e) { 715 return null; 716 } 717 } 718 719 720 /** 721 * Enable or disable Wi-Fi. 722 * @param enabled {@code true} to enable, {@code false} to disable. 723 * @return {@code true} if the operation succeeds (or if the existing state 724 * is the same as the requested state). 725 */ 726 public boolean setWifiEnabled(boolean enabled) { 727 try { 728 return mService.setWifiEnabled(enabled); 729 } catch (RemoteException e) { 730 return false; 731 } 732 } 733 734 /** 735 * Gets the Wi-Fi enabled state. 736 * @return One of {@link #WIFI_STATE_DISABLED}, 737 * {@link #WIFI_STATE_DISABLING}, {@link #WIFI_STATE_ENABLED}, 738 * {@link #WIFI_STATE_ENABLING}, {@link #WIFI_STATE_UNKNOWN} 739 * @see #isWifiEnabled() 740 */ 741 public int getWifiState() { 742 try { 743 return mService.getWifiEnabledState(); 744 } catch (RemoteException e) { 745 return WIFI_STATE_UNKNOWN; 746 } 747 } 748 749 /** 750 * Return whether Wi-Fi is enabled or disabled. 751 * @return {@code true} if Wi-Fi is enabled 752 * @see #getWifiState() 753 */ 754 public boolean isWifiEnabled() { 755 return getWifiState() == WIFI_STATE_ENABLED; 756 } 757 758 /** 759 * Calculates the level of the signal. This should be used any time a signal 760 * is being shown. 761 * 762 * @param rssi The power of the signal measured in RSSI. 763 * @param numLevels The number of levels to consider in the calculated 764 * level. 765 * @return A level of the signal, given in the range of 0 to numLevels-1 766 * (both inclusive). 767 */ 768 public static int calculateSignalLevel(int rssi, int numLevels) { 769 if (rssi <= MIN_RSSI) { 770 return 0; 771 } else if (rssi >= MAX_RSSI) { 772 return numLevels - 1; 773 } else { 774 float inputRange = (MAX_RSSI - MIN_RSSI); 775 float outputRange = (numLevels - 1); 776 return (int)((float)(rssi - MIN_RSSI) * outputRange / inputRange); 777 } 778 } 779 780 /** 781 * Compares two signal strengths. 782 * 783 * @param rssiA The power of the first signal measured in RSSI. 784 * @param rssiB The power of the second signal measured in RSSI. 785 * @return Returns <0 if the first signal is weaker than the second signal, 786 * 0 if the two signals have the same strength, and >0 if the first 787 * signal is stronger than the second signal. 788 */ 789 public static int compareSignalLevel(int rssiA, int rssiB) { 790 return rssiA - rssiB; 791 } 792 793 /** 794 * Start AccessPoint mode with the specified 795 * configuration. If the radio is already running in 796 * AP mode, update the new configuration 797 * Note that starting in access point mode disables station 798 * mode operation 799 * @param wifiConfig SSID, security and channel details as 800 * part of WifiConfiguration 801 * @return {@code true} if the operation succeeds, {@code false} otherwise 802 * 803 * @hide Dont open up yet 804 */ 805 public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { 806 try { 807 return mService.setWifiApEnabled(wifiConfig, enabled); 808 } catch (RemoteException e) { 809 return false; 810 } 811 } 812 813 /** 814 * Gets the Wi-Fi enabled state. 815 * @return One of {@link #WIFI_AP_STATE_DISABLED}, 816 * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED}, 817 * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED} 818 * @see #isWifiApEnabled() 819 * 820 * @hide Dont open yet 821 */ 822 public int getWifiApState() { 823 try { 824 return mService.getWifiApEnabledState(); 825 } catch (RemoteException e) { 826 return WIFI_AP_STATE_FAILED; 827 } 828 } 829 830 /** 831 * Return whether Wi-Fi AP is enabled or disabled. 832 * @return {@code true} if Wi-Fi AP is enabled 833 * @see #getWifiApState() 834 * 835 * @hide Dont open yet 836 */ 837 public boolean isWifiApEnabled() { 838 return getWifiApState() == WIFI_AP_STATE_ENABLED; 839 } 840 841 /** 842 * Gets the Wi-Fi AP Configuration. 843 * @return AP details in WifiConfiguration 844 * 845 * @hide Dont open yet 846 */ 847 public WifiConfiguration getWifiApConfiguration() { 848 try { 849 return mService.getWifiApConfiguration(); 850 } catch (RemoteException e) { 851 return null; 852 } 853 } 854 855 /** 856 * Sets the Wi-Fi AP Configuration. 857 * @return {@code true} if the operation succeeded, {@code false} otherwise 858 * 859 * @hide Dont open yet 860 */ 861 public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) { 862 try { 863 mService.setWifiApConfiguration(wifiConfig); 864 return true; 865 } catch (RemoteException e) { 866 return false; 867 } 868 } 869 870 /** 871 * Start the driver and connect to network. 872 * 873 * This function will over-ride WifiLock and device idle status. For example, 874 * even if the device is idle or there is only a scan-only lock held, 875 * a start wifi would mean that wifi connection is kept active until 876 * a stopWifi() is sent. 877 * 878 * This API is used by WifiStateTracker 879 * 880 * @return {@code true} if the operation succeeds else {@code false} 881 * @hide 882 */ 883 public boolean startWifi() { 884 try { 885 mService.startWifi(); 886 return true; 887 } catch (RemoteException e) { 888 return false; 889 } 890 } 891 892 /** 893 * Disconnect from a network (if any) and stop the driver. 894 * 895 * This function will over-ride WifiLock and device idle status. Wi-Fi 896 * stays inactive until a startWifi() is issued. 897 * 898 * This API is used by WifiStateTracker 899 * 900 * @return {@code true} if the operation succeeds else {@code false} 901 * @hide 902 */ 903 public boolean stopWifi() { 904 try { 905 mService.stopWifi(); 906 return true; 907 } catch (RemoteException e) { 908 return false; 909 } 910 } 911 912 /** 913 * Add a bssid to the supplicant blacklist 914 * 915 * This API is used by WifiWatchdogService 916 * 917 * @return {@code true} if the operation succeeds else {@code false} 918 * @hide 919 */ 920 public boolean addToBlacklist(String bssid) { 921 try { 922 mService.addToBlacklist(bssid); 923 return true; 924 } catch (RemoteException e) { 925 return false; 926 } 927 } 928 929 /** 930 * Clear the supplicant blacklist 931 * 932 * This API is used by WifiWatchdogService 933 * 934 * @return {@code true} if the operation succeeds else {@code false} 935 * @hide 936 */ 937 public boolean clearBlacklist() { 938 try { 939 mService.clearBlacklist(); 940 return true; 941 } catch (RemoteException e) { 942 return false; 943 } 944 } 945 946 /* TODO: deprecate synchronous API and open up the following API */ 947 /** 948 * Connect to a network with the given configuration. The network also 949 * gets added to the supplicant configuration. 950 * 951 * For a new network, this function is used instead of a 952 * sequence of addNetwork(), enableNetwork(), saveConfiguration() and 953 * reconnect() 954 * 955 * @param config the set of variables that describe the configuration, 956 * contained in a {@link WifiConfiguration} object. 957 * @hide 958 */ 959 public void connectNetwork(WifiConfiguration config) { 960 if (config == null) { 961 return; 962 } 963 try { 964 mService.connectNetworkWithConfig(config); 965 } catch (RemoteException e) { } 966 } 967 968 /** 969 * Connect to a network with the given networkId. 970 * 971 * This function is used instead of a enableNetwork(), saveConfiguration() and 972 * reconnect() 973 * 974 * @param networkId the network id identifiying the network in the 975 * supplicant configuration list 976 * @hide 977 */ 978 public void connectNetwork(int networkId) { 979 if (networkId < 0) { 980 return; 981 } 982 try { 983 mService.connectNetworkWithId(networkId); 984 } catch (RemoteException e) { } 985 } 986 987 /** 988 * Save the given network in the supplicant config. If the network already 989 * exists, the configuration is updated. A new network is enabled 990 * by default. 991 * 992 * For a new network, this function is used instead of a 993 * sequence of addNetwork(), enableNetwork() and saveConfiguration(). 994 * 995 * For an existing network, it accomplishes the task of updateNetwork() 996 * and saveConfiguration() 997 * 998 * @param config the set of variables that describe the configuration, 999 * contained in a {@link WifiConfiguration} object. 1000 * @hide 1001 */ 1002 public void saveNetwork(WifiConfiguration config) { 1003 if (config == null) { 1004 return; 1005 } 1006 try { 1007 mService.saveNetwork(config); 1008 } catch (RemoteException e) { } 1009 } 1010 1011 /** 1012 * Delete the network in the supplicant config. 1013 * 1014 * This function is used instead of a sequence of removeNetwork() 1015 * and saveConfiguration(). 1016 * 1017 * @param config the set of variables that describe the configuration, 1018 * contained in a {@link WifiConfiguration} object. 1019 * @hide 1020 */ 1021 public void forgetNetwork(int netId) { 1022 if (netId < 0) { 1023 return; 1024 } 1025 try { 1026 mService.forgetNetwork(netId); 1027 } catch (RemoteException e) { } 1028 } 1029 1030 /** 1031 * Allows an application to keep the Wi-Fi radio awake. 1032 * Normally the Wi-Fi radio may turn off when the user has not used the device in a while. 1033 * Acquiring a WifiLock will keep the radio on until the lock is released. Multiple 1034 * applications may hold WifiLocks, and the radio will only be allowed to turn off when no 1035 * WifiLocks are held in any application. 1036 * 1037 * Before using a WifiLock, consider carefully if your application requires Wi-Fi access, or 1038 * could function over a mobile network, if available. A program that needs to download large 1039 * files should hold a WifiLock to ensure that the download will complete, but a program whose 1040 * network usage is occasional or low-bandwidth should not hold a WifiLock to avoid adversely 1041 * affecting battery life. 1042 * 1043 * Note that WifiLocks cannot override the user-level "Wi-Fi Enabled" setting, nor Airplane 1044 * Mode. They simply keep the radio from turning off when Wi-Fi is already on but the device 1045 * is idle. 1046 */ 1047 public class WifiLock { 1048 private String mTag; 1049 private final IBinder mBinder; 1050 private int mRefCount; 1051 int mLockType; 1052 private boolean mRefCounted; 1053 private boolean mHeld; 1054 private WorkSource mWorkSource; 1055 1056 private WifiLock(int lockType, String tag) { 1057 mTag = tag; 1058 mLockType = lockType; 1059 mBinder = new Binder(); 1060 mRefCount = 0; 1061 mRefCounted = true; 1062 mHeld = false; 1063 } 1064 1065 /** 1066 * Locks the Wi-Fi radio on until {@link #release} is called. 1067 * 1068 * If this WifiLock is reference-counted, each call to {@code acquire} will increment the 1069 * reference count, and the radio will remain locked as long as the reference count is 1070 * above zero. 1071 * 1072 * If this WifiLock is not reference-counted, the first call to {@code acquire} will lock 1073 * the radio, but subsequent calls will be ignored. Only one call to {@link #release} 1074 * will be required, regardless of the number of times that {@code acquire} is called. 1075 */ 1076 public void acquire() { 1077 synchronized (mBinder) { 1078 if (mRefCounted ? (++mRefCount > 0) : (!mHeld)) { 1079 try { 1080 mService.acquireWifiLock(mBinder, mLockType, mTag, mWorkSource); 1081 synchronized (WifiManager.this) { 1082 if (mActiveLockCount >= MAX_ACTIVE_LOCKS) { 1083 mService.releaseWifiLock(mBinder); 1084 throw new UnsupportedOperationException( 1085 "Exceeded maximum number of wifi locks"); 1086 } 1087 mActiveLockCount++; 1088 } 1089 } catch (RemoteException ignore) { 1090 } 1091 mHeld = true; 1092 } 1093 } 1094 } 1095 1096 /** 1097 * Unlocks the Wi-Fi radio, allowing it to turn off when the device is idle. 1098 * 1099 * If this WifiLock is reference-counted, each call to {@code release} will decrement the 1100 * reference count, and the radio will be unlocked only when the reference count reaches 1101 * zero. If the reference count goes below zero (that is, if {@code release} is called 1102 * a greater number of times than {@link #acquire}), an exception is thrown. 1103 * 1104 * If this WifiLock is not reference-counted, the first call to {@code release} (after 1105 * the radio was locked using {@link #acquire}) will unlock the radio, and subsequent 1106 * calls will be ignored. 1107 */ 1108 public void release() { 1109 synchronized (mBinder) { 1110 if (mRefCounted ? (--mRefCount == 0) : (mHeld)) { 1111 try { 1112 mService.releaseWifiLock(mBinder); 1113 synchronized (WifiManager.this) { 1114 mActiveLockCount--; 1115 } 1116 } catch (RemoteException ignore) { 1117 } 1118 mHeld = false; 1119 } 1120 if (mRefCount < 0) { 1121 throw new RuntimeException("WifiLock under-locked " + mTag); 1122 } 1123 } 1124 } 1125 1126 /** 1127 * Controls whether this is a reference-counted or non-reference-counted WifiLock. 1128 * 1129 * Reference-counted WifiLocks keep track of the number of calls to {@link #acquire} and 1130 * {@link #release}, and only allow the radio to sleep when every call to {@link #acquire} 1131 * has been balanced with a call to {@link #release}. Non-reference-counted WifiLocks 1132 * lock the radio whenever {@link #acquire} is called and it is unlocked, and unlock the 1133 * radio whenever {@link #release} is called and it is locked. 1134 * 1135 * @param refCounted true if this WifiLock should keep a reference count 1136 */ 1137 public void setReferenceCounted(boolean refCounted) { 1138 mRefCounted = refCounted; 1139 } 1140 1141 /** 1142 * Checks whether this WifiLock is currently held. 1143 * 1144 * @return true if this WifiLock is held, false otherwise 1145 */ 1146 public boolean isHeld() { 1147 synchronized (mBinder) { 1148 return mHeld; 1149 } 1150 } 1151 1152 public void setWorkSource(WorkSource ws) { 1153 synchronized (mBinder) { 1154 if (ws != null && ws.size() == 0) { 1155 ws = null; 1156 } 1157 boolean changed = true; 1158 if (ws == null) { 1159 mWorkSource = null; 1160 } else if (mWorkSource == null) { 1161 changed = mWorkSource != null; 1162 mWorkSource = new WorkSource(ws); 1163 } else { 1164 changed = mWorkSource.diff(ws); 1165 if (changed) { 1166 mWorkSource.set(ws); 1167 } 1168 } 1169 if (changed && mHeld) { 1170 try { 1171 mService.updateWifiLockWorkSource(mBinder, mWorkSource); 1172 } catch (RemoteException e) { 1173 } 1174 } 1175 } 1176 } 1177 1178 public String toString() { 1179 String s1, s2, s3; 1180 synchronized (mBinder) { 1181 s1 = Integer.toHexString(System.identityHashCode(this)); 1182 s2 = mHeld ? "held; " : ""; 1183 if (mRefCounted) { 1184 s3 = "refcounted: refcount = " + mRefCount; 1185 } else { 1186 s3 = "not refcounted"; 1187 } 1188 return "WifiLock{ " + s1 + "; " + s2 + s3 + " }"; 1189 } 1190 } 1191 1192 @Override 1193 protected void finalize() throws Throwable { 1194 super.finalize(); 1195 synchronized (mBinder) { 1196 if (mHeld) { 1197 try { 1198 mService.releaseWifiLock(mBinder); 1199 synchronized (WifiManager.this) { 1200 mActiveLockCount--; 1201 } 1202 } catch (RemoteException ignore) { 1203 } 1204 } 1205 } 1206 } 1207 } 1208 1209 /** 1210 * Creates a new WifiLock. 1211 * 1212 * @param lockType the type of lock to create. See {@link #WIFI_MODE_FULL} and 1213 * {@link #WIFI_MODE_SCAN_ONLY} for descriptions of the types of Wi-Fi locks. 1214 * @param tag a tag for the WifiLock to identify it in debugging messages. This string is 1215 * never shown to the user under normal conditions, but should be descriptive 1216 * enough to identify your application and the specific WifiLock within it, if it 1217 * holds multiple WifiLocks. 1218 * 1219 * @return a new, unacquired WifiLock with the given tag. 1220 * 1221 * @see WifiLock 1222 */ 1223 public WifiLock createWifiLock(int lockType, String tag) { 1224 return new WifiLock(lockType, tag); 1225 } 1226 1227 /** 1228 * Creates a new WifiLock. 1229 * 1230 * @param tag a tag for the WifiLock to identify it in debugging messages. This string is 1231 * never shown to the user under normal conditions, but should be descriptive 1232 * enough to identify your application and the specific WifiLock within it, if it 1233 * holds multiple WifiLocks. 1234 * 1235 * @return a new, unacquired WifiLock with the given tag. 1236 * 1237 * @see WifiLock 1238 */ 1239 public WifiLock createWifiLock(String tag) { 1240 return new WifiLock(WIFI_MODE_FULL, tag); 1241 } 1242 1243 1244 /** 1245 * Create a new MulticastLock 1246 * 1247 * @param tag a tag for the MulticastLock to identify it in debugging 1248 * messages. This string is never shown to the user under 1249 * normal conditions, but should be descriptive enough to 1250 * identify your application and the specific MulticastLock 1251 * within it, if it holds multiple MulticastLocks. 1252 * 1253 * @return a new, unacquired MulticastLock with the given tag. 1254 * 1255 * @see MulticastLock 1256 */ 1257 public MulticastLock createMulticastLock(String tag) { 1258 return new MulticastLock(tag); 1259 } 1260 1261 /** 1262 * Allows an application to receive Wifi Multicast packets. 1263 * Normally the Wifi stack filters out packets not explicitly 1264 * addressed to this device. Acquring a MulticastLock will 1265 * cause the stack to receive packets addressed to multicast 1266 * addresses. Processing these extra packets can cause a noticable 1267 * battery drain and should be disabled when not needed. 1268 */ 1269 public class MulticastLock { 1270 private String mTag; 1271 private final IBinder mBinder; 1272 private int mRefCount; 1273 private boolean mRefCounted; 1274 private boolean mHeld; 1275 1276 private MulticastLock(String tag) { 1277 mTag = tag; 1278 mBinder = new Binder(); 1279 mRefCount = 0; 1280 mRefCounted = true; 1281 mHeld = false; 1282 } 1283 1284 /** 1285 * Locks Wifi Multicast on until {@link #release} is called. 1286 * 1287 * If this MulticastLock is reference-counted each call to 1288 * {@code acquire} will increment the reference count, and the 1289 * wifi interface will receive multicast packets as long as the 1290 * reference count is above zero. 1291 * 1292 * If this MulticastLock is not reference-counted, the first call to 1293 * {@code acquire} will turn on the multicast packets, but subsequent 1294 * calls will be ignored. Only one call to {@link #release} will 1295 * be required, regardless of the number of times that {@code acquire} 1296 * is called. 1297 * 1298 * Note that other applications may also lock Wifi Multicast on. 1299 * Only they can relinquish their lock. 1300 * 1301 * Also note that applications cannot leave Multicast locked on. 1302 * When an app exits or crashes, any Multicast locks will be released. 1303 */ 1304 public void acquire() { 1305 synchronized (mBinder) { 1306 if (mRefCounted ? (++mRefCount > 0) : (!mHeld)) { 1307 try { 1308 mService.acquireMulticastLock(mBinder, mTag); 1309 synchronized (WifiManager.this) { 1310 if (mActiveLockCount >= MAX_ACTIVE_LOCKS) { 1311 mService.releaseMulticastLock(); 1312 throw new UnsupportedOperationException( 1313 "Exceeded maximum number of wifi locks"); 1314 } 1315 mActiveLockCount++; 1316 } 1317 } catch (RemoteException ignore) { 1318 } 1319 mHeld = true; 1320 } 1321 } 1322 } 1323 1324 /** 1325 * Unlocks Wifi Multicast, restoring the filter of packets 1326 * not addressed specifically to this device and saving power. 1327 * 1328 * If this MulticastLock is reference-counted, each call to 1329 * {@code release} will decrement the reference count, and the 1330 * multicast packets will only stop being received when the reference 1331 * count reaches zero. If the reference count goes below zero (that 1332 * is, if {@code release} is called a greater number of times than 1333 * {@link #acquire}), an exception is thrown. 1334 * 1335 * If this MulticastLock is not reference-counted, the first call to 1336 * {@code release} (after the radio was multicast locked using 1337 * {@link #acquire}) will unlock the multicast, and subsequent calls 1338 * will be ignored. 1339 * 1340 * Note that if any other Wifi Multicast Locks are still outstanding 1341 * this {@code release} call will not have an immediate effect. Only 1342 * when all applications have released all their Multicast Locks will 1343 * the Multicast filter be turned back on. 1344 * 1345 * Also note that when an app exits or crashes all of its Multicast 1346 * Locks will be automatically released. 1347 */ 1348 public void release() { 1349 synchronized (mBinder) { 1350 if (mRefCounted ? (--mRefCount == 0) : (mHeld)) { 1351 try { 1352 mService.releaseMulticastLock(); 1353 synchronized (WifiManager.this) { 1354 mActiveLockCount--; 1355 } 1356 } catch (RemoteException ignore) { 1357 } 1358 mHeld = false; 1359 } 1360 if (mRefCount < 0) { 1361 throw new RuntimeException("MulticastLock under-locked " 1362 + mTag); 1363 } 1364 } 1365 } 1366 1367 /** 1368 * Controls whether this is a reference-counted or non-reference- 1369 * counted MulticastLock. 1370 * 1371 * Reference-counted MulticastLocks keep track of the number of calls 1372 * to {@link #acquire} and {@link #release}, and only stop the 1373 * reception of multicast packets when every call to {@link #acquire} 1374 * has been balanced with a call to {@link #release}. Non-reference- 1375 * counted MulticastLocks allow the reception of multicast packets 1376 * whenever {@link #acquire} is called and stop accepting multicast 1377 * packets whenever {@link #release} is called. 1378 * 1379 * @param refCounted true if this MulticastLock should keep a reference 1380 * count 1381 */ 1382 public void setReferenceCounted(boolean refCounted) { 1383 mRefCounted = refCounted; 1384 } 1385 1386 /** 1387 * Checks whether this MulticastLock is currently held. 1388 * 1389 * @return true if this MulticastLock is held, false otherwise 1390 */ 1391 public boolean isHeld() { 1392 synchronized (mBinder) { 1393 return mHeld; 1394 } 1395 } 1396 1397 public String toString() { 1398 String s1, s2, s3; 1399 synchronized (mBinder) { 1400 s1 = Integer.toHexString(System.identityHashCode(this)); 1401 s2 = mHeld ? "held; " : ""; 1402 if (mRefCounted) { 1403 s3 = "refcounted: refcount = " + mRefCount; 1404 } else { 1405 s3 = "not refcounted"; 1406 } 1407 return "MulticastLock{ " + s1 + "; " + s2 + s3 + " }"; 1408 } 1409 } 1410 1411 @Override 1412 protected void finalize() throws Throwable { 1413 super.finalize(); 1414 setReferenceCounted(false); 1415 release(); 1416 } 1417 } 1418 1419 /** 1420 * Check multicast filter status. 1421 * 1422 * @return true if multicast packets are allowed. 1423 * 1424 * @hide pending API council approval 1425 */ 1426 public boolean isMulticastEnabled() { 1427 try { 1428 return mService.isMulticastEnabled(); 1429 } catch (RemoteException e) { 1430 return false; 1431 } 1432 } 1433 1434 /** 1435 * Initialize the multicast filtering to 'on' 1436 * @hide no intent to publish 1437 */ 1438 public boolean initializeMulticastFiltering() { 1439 try { 1440 mService.initializeMulticastFiltering(); 1441 return true; 1442 } catch (RemoteException e) { 1443 return false; 1444 } 1445 } 1446} 1447