1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.lowpan; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.net.IpPrefix; 23 import android.net.LinkAddress; 24 import android.os.DeadObjectException; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.RemoteException; 28 import android.os.ServiceSpecificException; 29 import android.util.Log; 30 import java.util.HashMap; 31 32 /** 33 * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface. 34 * 35 * @hide 36 */ 37 // @SystemApi 38 public class LowpanInterface { 39 private static final String TAG = LowpanInterface.class.getSimpleName(); 40 41 /** Detached role. The interface is not currently attached to a network. */ 42 public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED; 43 44 /** End-device role. End devices do not route traffic for other nodes. */ 45 public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE; 46 47 /** Router role. Routers help route traffic around the mesh network. */ 48 public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER; 49 50 /** 51 * Sleepy End-Device role. 52 * 53 * <p>End devices with this role are nominally asleep, waking up periodically to check in with 54 * their parent to see if there are packets destined for them. Such devices are capable of 55 * extraordinarilly low power consumption, but packet latency can be on the order of dozens of 56 * seconds(depending on how the node is configured). 57 */ 58 public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE; 59 60 /** 61 * Sleepy-router role. 62 * 63 * <p>Routers with this role are nominally asleep, waking up periodically to check in with other 64 * routers and their children. 65 */ 66 public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER; 67 68 /** TODO: doc */ 69 public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER; 70 71 /** TODO: doc */ 72 public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR; 73 74 /** 75 * Offline state. 76 * 77 * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In 78 * this state the NCP is idle and not connected to any network. 79 * 80 * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or 81 * <code>setUp(false)</code>, with the later two only working if we were not previously in the 82 * {@link #STATE_FAULT} state. 83 * 84 * @see #getState() 85 * @see #STATE_FAULT 86 */ 87 public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE; 88 89 /** 90 * Commissioning state. 91 * 92 * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This 93 * state may only be entered directly from the {@link #STATE_OFFLINE} state. 94 * 95 * @see #startCommissioningSession() 96 * @see #getState() 97 * @hide 98 */ 99 public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING; 100 101 /** 102 * Attaching state. 103 * 104 * <p>The interface enters this state when it starts the process of trying to find other nodes 105 * so that it can attach to any pre-existing network fragment, or when it is in the process of 106 * calculating the optimal values for unspecified parameters when forming a new network. 107 * 108 * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously 109 * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is 110 * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link 111 * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot 112 * create their own network fragments. 113 * 114 * @see #STATE_ATTACHED 115 * @see #getState() 116 */ 117 public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING; 118 119 /** 120 * Attached state. 121 * 122 * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively 123 * participating on a network fragment. 124 * 125 * @see #STATE_ATTACHING 126 * @see #getState() 127 */ 128 public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED; 129 130 /** 131 * Fault state. 132 * 133 * <p>The interface will enter this state when the driver has detected some sort of problem from 134 * which it was not immediately able to recover. 135 * 136 * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will 137 * cause the device to return to the {@link #STATE_OFFLINE} state. 138 * 139 * @see #getState 140 * @see #STATE_OFFLINE 141 */ 142 public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT; 143 144 /** 145 * Network type for Thread 1.x networks. 146 * 147 * @see android.net.lowpan.LowpanIdentity#getType 148 * @see #getLowpanIdentity 149 * @hide 150 */ 151 public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_THREAD_V1; 152 153 public static final String EMPTY_PARTITION_ID = ""; 154 155 /** 156 * Callback base class for LowpanInterface 157 * 158 * @hide 159 */ 160 // @SystemApi 161 public abstract static class Callback { 162 public void onConnectedChanged(boolean value) {} 163 164 public void onEnabledChanged(boolean value) {} 165 166 public void onUpChanged(boolean value) {} 167 168 public void onRoleChanged(@NonNull String value) {} 169 170 public void onStateChanged(@NonNull String state) {} 171 172 public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {} 173 174 public void onLinkNetworkAdded(IpPrefix prefix) {} 175 176 public void onLinkNetworkRemoved(IpPrefix prefix) {} 177 178 public void onLinkAddressAdded(LinkAddress address) {} 179 180 public void onLinkAddressRemoved(LinkAddress address) {} 181 } 182 183 private final ILowpanInterface mBinder; 184 private final Looper mLooper; 185 private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>(); 186 187 /** 188 * Create a new LowpanInterface instance. Applications will almost always want to use {@link 189 * LowpanManager#getInterface LowpanManager.getInterface()} instead of this. 190 * 191 * @param context the application context 192 * @param service the Binder interface 193 * @param looper the Binder interface 194 * @hide 195 */ 196 public LowpanInterface(Context context, ILowpanInterface service, Looper looper) { 197 /* We aren't currently using the context, but if we need 198 * it later on we can easily add it to the class. 199 */ 200 201 mBinder = service; 202 mLooper = looper; 203 } 204 205 /** 206 * Returns the ILowpanInterface object associated with this interface. 207 * 208 * @hide 209 */ 210 public ILowpanInterface getService() { 211 return mBinder; 212 } 213 214 // Public Actions 215 216 /** 217 * Form a new network with the given network information optional credential. Unspecified fields 218 * in the network information will be filled in with reasonable values. If the network 219 * credential is unspecified, one will be generated automatically. 220 * 221 * <p>This method will block until either the network was successfully formed or an error 222 * prevents the network form being formed. 223 * 224 * <p>Upon success, the interface will be up and attached to the newly formed network. 225 * 226 * @see #join(LowpanProvision) 227 */ 228 public void form(@NonNull LowpanProvision provision) throws LowpanException { 229 try { 230 mBinder.form(provision); 231 232 } catch (RemoteException x) { 233 throw x.rethrowAsRuntimeException(); 234 235 } catch (ServiceSpecificException x) { 236 throw LowpanException.rethrowFromServiceSpecificException(x); 237 } 238 } 239 240 /** 241 * Attempts to join a new network with the given network information. This method will block 242 * until either the network was successfully joined or an error prevented the network from being 243 * formed. Upon success, the interface will be up and attached to the newly joined network. 244 * 245 * <p>Note that joining is distinct from attaching: Joining requires at least one other peer 246 * device to be present in order for the operation to complete successfully. 247 */ 248 public void join(@NonNull LowpanProvision provision) throws LowpanException { 249 try { 250 mBinder.join(provision); 251 252 } catch (RemoteException x) { 253 throw x.rethrowAsRuntimeException(); 254 255 } catch (ServiceSpecificException x) { 256 throw LowpanException.rethrowFromServiceSpecificException(x); 257 } 258 } 259 260 /** 261 * Attaches to the network described by identity and credential. This is similar to {@link 262 * #join}, except that (assuming the identity and credential are valid) it will always succeed 263 * and provision the interface, even if there are no peers nearby. 264 * 265 * <p>This method will block execution until the operation has completed. 266 */ 267 public void attach(@NonNull LowpanProvision provision) throws LowpanException { 268 try { 269 mBinder.attach(provision); 270 271 } catch (RemoteException x) { 272 throw x.rethrowAsRuntimeException(); 273 274 } catch (ServiceSpecificException x) { 275 throw LowpanException.rethrowFromServiceSpecificException(x); 276 } 277 } 278 279 /** 280 * Bring down the network interface and forget all non-volatile details about the current 281 * network. 282 * 283 * <p>This method will block execution until the operation has completed. 284 */ 285 public void leave() throws LowpanException { 286 try { 287 mBinder.leave(); 288 289 } catch (RemoteException x) { 290 throw x.rethrowAsRuntimeException(); 291 292 } catch (ServiceSpecificException x) { 293 throw LowpanException.rethrowFromServiceSpecificException(x); 294 } 295 } 296 297 /** 298 * Start a new commissioning session. Will fail if the interface is attached to a network or if 299 * the interface is disabled. 300 */ 301 public @NonNull LowpanCommissioningSession startCommissioningSession( 302 @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException { 303 try { 304 mBinder.startCommissioningSession(beaconInfo); 305 306 return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper); 307 308 } catch (RemoteException x) { 309 throw x.rethrowAsRuntimeException(); 310 311 } catch (ServiceSpecificException x) { 312 throw LowpanException.rethrowFromServiceSpecificException(x); 313 } 314 } 315 316 /** 317 * Reset this network interface as if it has been power cycled. Will bring the network interface 318 * down if it was previously up. Will not erase any non-volatile settings. 319 * 320 * <p>This method will block execution until the operation has completed. 321 * 322 * @hide 323 */ 324 public void reset() throws LowpanException { 325 try { 326 mBinder.reset(); 327 328 } catch (RemoteException x) { 329 throw x.rethrowAsRuntimeException(); 330 331 } catch (ServiceSpecificException x) { 332 throw LowpanException.rethrowFromServiceSpecificException(x); 333 } 334 } 335 336 // Public Getters and Setters 337 338 /** Returns the name of this network interface. */ 339 @NonNull 340 public String getName() { 341 try { 342 return mBinder.getName(); 343 344 } catch (DeadObjectException x) { 345 return ""; 346 347 } catch (RemoteException x) { 348 throw x.rethrowAsRuntimeException(); 349 } 350 } 351 352 /** 353 * Indicates if the interface is enabled or disabled. 354 * 355 * @see #setEnabled 356 * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED 357 */ 358 public boolean isEnabled() { 359 try { 360 return mBinder.isEnabled(); 361 362 } catch (DeadObjectException x) { 363 return false; 364 365 } catch (RemoteException x) { 366 throw x.rethrowAsRuntimeException(); 367 } 368 } 369 370 /** 371 * Enables or disables the LoWPAN interface. When disabled, the interface is put into a 372 * low-power state and all commands that require the NCP to be queried will fail with {@link 373 * android.net.lowpan.LowpanException#LOWPAN_DISABLED}. 374 * 375 * @see #isEnabled 376 * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED 377 * @hide 378 */ 379 public void setEnabled(boolean enabled) throws LowpanException { 380 try { 381 mBinder.setEnabled(enabled); 382 383 } catch (RemoteException x) { 384 throw x.rethrowAsRuntimeException(); 385 386 } catch (ServiceSpecificException x) { 387 throw LowpanException.rethrowFromServiceSpecificException(x); 388 } 389 } 390 391 /** 392 * Indicates if the network interface is up or down. 393 * 394 * @hide 395 */ 396 public boolean isUp() { 397 try { 398 return mBinder.isUp(); 399 400 } catch (DeadObjectException x) { 401 return false; 402 403 } catch (RemoteException x) { 404 throw x.rethrowAsRuntimeException(); 405 } 406 } 407 408 /** 409 * Indicates if there is at least one peer in range. 410 * 411 * @return <code>true</code> if we have at least one other peer in range, <code>false</code> 412 * otherwise. 413 */ 414 public boolean isConnected() { 415 try { 416 return mBinder.isConnected(); 417 418 } catch (DeadObjectException x) { 419 return false; 420 421 } catch (RemoteException x) { 422 throw x.rethrowAsRuntimeException(); 423 } 424 } 425 426 /** 427 * Indicates if this interface is currently commissioned onto an existing network. If the 428 * interface is commissioned, the interface may be brought up using setUp(). 429 */ 430 public boolean isCommissioned() { 431 try { 432 return mBinder.isCommissioned(); 433 434 } catch (DeadObjectException x) { 435 return false; 436 437 } catch (RemoteException x) { 438 throw x.rethrowAsRuntimeException(); 439 } 440 } 441 442 /** 443 * Get interface state 444 * 445 * <h3>State Diagram</h3> 446 * 447 * <img src="LowpanInterface-1.png" /> 448 * 449 * @return The current state of the interface. 450 * @see #STATE_OFFLINE 451 * @see #STATE_COMMISSIONING 452 * @see #STATE_ATTACHING 453 * @see #STATE_ATTACHED 454 * @see #STATE_FAULT 455 */ 456 public String getState() { 457 try { 458 return mBinder.getState(); 459 460 } catch (DeadObjectException x) { 461 return STATE_FAULT; 462 463 } catch (RemoteException x) { 464 throw x.rethrowAsRuntimeException(); 465 } 466 } 467 468 /** Get network partition/fragment identifier. */ 469 public String getPartitionId() { 470 try { 471 return mBinder.getPartitionId(); 472 473 } catch (DeadObjectException x) { 474 return EMPTY_PARTITION_ID; 475 476 } catch (RemoteException x) { 477 throw x.rethrowAsRuntimeException(); 478 } 479 } 480 481 /** TODO: doc */ 482 public LowpanIdentity getLowpanIdentity() { 483 try { 484 return mBinder.getLowpanIdentity(); 485 486 } catch (DeadObjectException x) { 487 return new LowpanIdentity(); 488 489 } catch (RemoteException x) { 490 throw x.rethrowAsRuntimeException(); 491 } 492 } 493 494 /** TODO: doc */ 495 @NonNull 496 public String getRole() { 497 try { 498 return mBinder.getRole(); 499 500 } catch (DeadObjectException x) { 501 return ROLE_DETACHED; 502 503 } catch (RemoteException x) { 504 throw x.rethrowAsRuntimeException(); 505 } 506 } 507 508 /** TODO: doc */ 509 @Nullable 510 public LowpanCredential getLowpanCredential() { 511 try { 512 return mBinder.getLowpanCredential(); 513 514 } catch (RemoteException x) { 515 throw x.rethrowAsRuntimeException(); 516 } 517 } 518 519 public @NonNull String[] getSupportedNetworkTypes() throws LowpanException { 520 try { 521 return mBinder.getSupportedNetworkTypes(); 522 523 } catch (RemoteException x) { 524 throw x.rethrowAsRuntimeException(); 525 526 } catch (ServiceSpecificException x) { 527 throw LowpanException.rethrowFromServiceSpecificException(x); 528 } 529 } 530 531 public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException { 532 try { 533 return mBinder.getSupportedChannels(); 534 535 } catch (RemoteException x) { 536 throw x.rethrowAsRuntimeException(); 537 538 } catch (ServiceSpecificException x) { 539 throw LowpanException.rethrowFromServiceSpecificException(x); 540 } 541 } 542 543 // Listener Support 544 545 /** 546 * Registers a subclass of {@link LowpanInterface.Callback} to receive events. 547 * 548 * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events. 549 * @param handler If not <code>null</code>, events will be dispatched via the given handler 550 * object. If <code>null</code>, the thread upon which events will be dispatched is 551 * unspecified. 552 * @see #registerCallback(Callback) 553 * @see #unregisterCallback(Callback) 554 */ 555 public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) { 556 ILowpanInterfaceListener.Stub listenerBinder = 557 new ILowpanInterfaceListener.Stub() { 558 private Handler mHandler; 559 560 { 561 if (handler != null) { 562 mHandler = handler; 563 } else if (mLooper != null) { 564 mHandler = new Handler(mLooper); 565 } else { 566 mHandler = new Handler(); 567 } 568 } 569 570 @Override 571 public void onEnabledChanged(boolean value) { 572 mHandler.post(() -> cb.onEnabledChanged(value)); 573 } 574 575 @Override 576 public void onConnectedChanged(boolean value) { 577 mHandler.post(() -> cb.onConnectedChanged(value)); 578 } 579 580 @Override 581 public void onUpChanged(boolean value) { 582 mHandler.post(() -> cb.onUpChanged(value)); 583 } 584 585 @Override 586 public void onRoleChanged(String value) { 587 mHandler.post(() -> cb.onRoleChanged(value)); 588 } 589 590 @Override 591 public void onStateChanged(String value) { 592 mHandler.post(() -> cb.onStateChanged(value)); 593 } 594 595 @Override 596 public void onLowpanIdentityChanged(LowpanIdentity value) { 597 mHandler.post(() -> cb.onLowpanIdentityChanged(value)); 598 } 599 600 @Override 601 public void onLinkNetworkAdded(IpPrefix value) { 602 mHandler.post(() -> cb.onLinkNetworkAdded(value)); 603 } 604 605 @Override 606 public void onLinkNetworkRemoved(IpPrefix value) { 607 mHandler.post(() -> cb.onLinkNetworkRemoved(value)); 608 } 609 610 @Override 611 public void onLinkAddressAdded(String value) { 612 LinkAddress la; 613 try { 614 la = new LinkAddress(value); 615 } catch (IllegalArgumentException x) { 616 Log.e( 617 TAG, 618 "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x); 619 return; 620 } 621 mHandler.post(() -> cb.onLinkAddressAdded(la)); 622 } 623 624 @Override 625 public void onLinkAddressRemoved(String value) { 626 LinkAddress la; 627 try { 628 la = new LinkAddress(value); 629 } catch (IllegalArgumentException x) { 630 Log.e( 631 TAG, 632 "onLinkAddressRemoved: Bad LinkAddress \"" 633 + value 634 + "\", " 635 + x); 636 return; 637 } 638 mHandler.post(() -> cb.onLinkAddressRemoved(la)); 639 } 640 641 @Override 642 public void onReceiveFromCommissioner(byte[] packet) { 643 // This is only used by the LowpanCommissioningSession. 644 } 645 }; 646 try { 647 mBinder.addListener(listenerBinder); 648 } catch (RemoteException x) { 649 throw x.rethrowAsRuntimeException(); 650 } 651 652 synchronized (mListenerMap) { 653 mListenerMap.put(System.identityHashCode(cb), listenerBinder); 654 } 655 } 656 657 /** 658 * Registers a subclass of {@link LowpanInterface.Callback} to receive events. 659 * 660 * <p>The thread upon which events will be dispatched is unspecified. 661 * 662 * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events. 663 * @see #registerCallback(Callback, Handler) 664 * @see #unregisterCallback(Callback) 665 */ 666 public void registerCallback(Callback cb) { 667 registerCallback(cb, null); 668 } 669 670 /** 671 * Unregisters a previously registered callback class. 672 * 673 * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to 674 * receive events. 675 * @see #registerCallback(Callback, Handler) 676 * @see #registerCallback(Callback) 677 */ 678 public void unregisterCallback(Callback cb) { 679 int hashCode = System.identityHashCode(cb); 680 synchronized (mListenerMap) { 681 ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode); 682 683 if (listenerBinder != null) { 684 mListenerMap.remove(hashCode); 685 686 try { 687 mBinder.removeListener(listenerBinder); 688 } catch (DeadObjectException x) { 689 // We ignore a dead object exception because that 690 // pretty clearly means our callback isn't registered. 691 } catch (RemoteException x) { 692 throw x.rethrowAsRuntimeException(); 693 } 694 } 695 } 696 } 697 698 // Active and Passive Scanning 699 700 /** 701 * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface. 702 * 703 * <p>This method allocates a new unique object for each call. 704 * 705 * @see android.net.lowpan.LowpanScanner 706 */ 707 public @NonNull LowpanScanner createScanner() { 708 return new LowpanScanner(mBinder); 709 } 710 711 // Route Management 712 713 /** 714 * Makes a copy of the internal list of LinkAddresses. 715 * 716 * @hide 717 */ 718 public LinkAddress[] getLinkAddresses() throws LowpanException { 719 try { 720 String[] linkAddressStrings = mBinder.getLinkAddresses(); 721 LinkAddress[] ret = new LinkAddress[linkAddressStrings.length]; 722 int i = 0; 723 for (String str : linkAddressStrings) { 724 ret[i++] = new LinkAddress(str); 725 } 726 return ret; 727 728 } catch (RemoteException x) { 729 throw x.rethrowAsRuntimeException(); 730 731 } catch (ServiceSpecificException x) { 732 throw LowpanException.rethrowFromServiceSpecificException(x); 733 } 734 } 735 736 /** 737 * Makes a copy of the internal list of networks reachable on via this link. 738 * 739 * @hide 740 */ 741 public IpPrefix[] getLinkNetworks() throws LowpanException { 742 try { 743 return mBinder.getLinkNetworks(); 744 745 } catch (RemoteException x) { 746 throw x.rethrowAsRuntimeException(); 747 748 } catch (ServiceSpecificException x) { 749 throw LowpanException.rethrowFromServiceSpecificException(x); 750 } 751 } 752 753 /** 754 * Advertise the given IP prefix as an on-mesh prefix. 755 * 756 * @hide 757 */ 758 public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException { 759 try { 760 mBinder.addOnMeshPrefix(prefix, flags); 761 762 } catch (RemoteException x) { 763 throw x.rethrowAsRuntimeException(); 764 765 } catch (ServiceSpecificException x) { 766 throw LowpanException.rethrowFromServiceSpecificException(x); 767 } 768 } 769 770 /** 771 * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh 772 * prefixes. 773 * 774 * @hide 775 */ 776 public void removeOnMeshPrefix(IpPrefix prefix) { 777 try { 778 mBinder.removeOnMeshPrefix(prefix); 779 780 } catch (RemoteException x) { 781 throw x.rethrowAsRuntimeException(); 782 783 } catch (ServiceSpecificException x) { 784 // Catch and ignore all service exceptions 785 Log.e(TAG, x.toString()); 786 } 787 } 788 789 /** 790 * Advertise this device to other devices on the mesh network as having a specific route to the 791 * given network. This device will then receive forwarded traffic for that network. 792 * 793 * @hide 794 */ 795 public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException { 796 try { 797 mBinder.addExternalRoute(prefix, flags); 798 799 } catch (RemoteException x) { 800 throw x.rethrowAsRuntimeException(); 801 802 } catch (ServiceSpecificException x) { 803 throw LowpanException.rethrowFromServiceSpecificException(x); 804 } 805 } 806 807 /** 808 * Revoke a previously advertised specific route to the given network. 809 * 810 * @hide 811 */ 812 public void removeExternalRoute(IpPrefix prefix) { 813 try { 814 mBinder.removeExternalRoute(prefix); 815 816 } catch (RemoteException x) { 817 throw x.rethrowAsRuntimeException(); 818 819 } catch (ServiceSpecificException x) { 820 // Catch and ignore all service exceptions 821 Log.e(TAG, x.toString()); 822 } 823 } 824 } 825