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
17package android.net.lowpan;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.net.IpPrefix;
23import android.net.LinkAddress;
24import android.os.DeadObjectException;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.RemoteException;
28import android.os.ServiceSpecificException;
29import android.util.Log;
30import 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
38public 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