/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.lowpan; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.IpPrefix; import android.net.LinkAddress; import android.os.DeadObjectException; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; import java.util.HashMap; /** * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface. * * @hide */ // @SystemApi public class LowpanInterface { private static final String TAG = LowpanInterface.class.getSimpleName(); /** Detached role. The interface is not currently attached to a network. */ public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED; /** End-device role. End devices do not route traffic for other nodes. */ public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE; /** Router role. Routers help route traffic around the mesh network. */ public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER; /** * Sleepy End-Device role. * *

End devices with this role are nominally asleep, waking up periodically to check in with * their parent to see if there are packets destined for them. Such devices are capable of * extraordinarilly low power consumption, but packet latency can be on the order of dozens of * seconds(depending on how the node is configured). */ public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE; /** * Sleepy-router role. * *

Routers with this role are nominally asleep, waking up periodically to check in with other * routers and their children. */ public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER; /** TODO: doc */ public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER; /** TODO: doc */ public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR; /** * Offline state. * *

This is the initial state of the LoWPAN interface when the underlying driver starts. In * this state the NCP is idle and not connected to any network. * *

This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or * setUp(false), with the later two only working if we were not previously in the * {@link #STATE_FAULT} state. * * @see #getState() * @see #STATE_FAULT */ public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE; /** * Commissioning state. * *

The interface enters this state after a call to {@link #startCommissioningSession()}. This * state may only be entered directly from the {@link #STATE_OFFLINE} state. * * @see #startCommissioningSession() * @see #getState() * @hide */ public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING; /** * Attaching state. * *

The interface enters this state when it starts the process of trying to find other nodes * so that it can attach to any pre-existing network fragment, or when it is in the process of * calculating the optimal values for unspecified parameters when forming a new network. * *

The interface may stay in this state for a prolonged period of time (or may spontaneously * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot * create their own network fragments. * * @see #STATE_ATTACHED * @see #getState() */ public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING; /** * Attached state. * *

The interface enters this state from {@link #STATE_ATTACHING} once it is actively * participating on a network fragment. * * @see #STATE_ATTACHING * @see #getState() */ public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED; /** * Fault state. * *

The interface will enter this state when the driver has detected some sort of problem from * which it was not immediately able to recover. * *

This state can be entered spontaneously from any other state. Calling {@link #reset} will * cause the device to return to the {@link #STATE_OFFLINE} state. * * @see #getState * @see #STATE_OFFLINE */ public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT; /** * Network type for Thread 1.x networks. * * @see android.net.lowpan.LowpanIdentity#getType * @see #getLowpanIdentity * @hide */ public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_THREAD_V1; public static final String EMPTY_PARTITION_ID = ""; /** * Callback base class for LowpanInterface * * @hide */ // @SystemApi public abstract static class Callback { public void onConnectedChanged(boolean value) {} public void onEnabledChanged(boolean value) {} public void onUpChanged(boolean value) {} public void onRoleChanged(@NonNull String value) {} public void onStateChanged(@NonNull String state) {} public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {} public void onLinkNetworkAdded(IpPrefix prefix) {} public void onLinkNetworkRemoved(IpPrefix prefix) {} public void onLinkAddressAdded(LinkAddress address) {} public void onLinkAddressRemoved(LinkAddress address) {} } private final ILowpanInterface mBinder; private final Looper mLooper; private final HashMap mListenerMap = new HashMap<>(); /** * Create a new LowpanInterface instance. Applications will almost always want to use {@link * LowpanManager#getInterface LowpanManager.getInterface()} instead of this. * * @param context the application context * @param service the Binder interface * @param looper the Binder interface * @hide */ public LowpanInterface(Context context, ILowpanInterface service, Looper looper) { /* We aren't currently using the context, but if we need * it later on we can easily add it to the class. */ mBinder = service; mLooper = looper; } /** * Returns the ILowpanInterface object associated with this interface. * * @hide */ public ILowpanInterface getService() { return mBinder; } // Public Actions /** * Form a new network with the given network information optional credential. Unspecified fields * in the network information will be filled in with reasonable values. If the network * credential is unspecified, one will be generated automatically. * *

This method will block until either the network was successfully formed or an error * prevents the network form being formed. * *

Upon success, the interface will be up and attached to the newly formed network. * * @see #join(LowpanProvision) */ public void form(@NonNull LowpanProvision provision) throws LowpanException { try { mBinder.form(provision); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Attempts to join a new network with the given network information. This method will block * until either the network was successfully joined or an error prevented the network from being * formed. Upon success, the interface will be up and attached to the newly joined network. * *

Note that “joining” is distinct from “attaching”: Joining requires at least one other peer * device to be present in order for the operation to complete successfully. */ public void join(@NonNull LowpanProvision provision) throws LowpanException { try { mBinder.join(provision); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Attaches to the network described by identity and credential. This is similar to {@link * #join}, except that (assuming the identity and credential are valid) it will always succeed * and provision the interface, even if there are no peers nearby. * *

This method will block execution until the operation has completed. */ public void attach(@NonNull LowpanProvision provision) throws LowpanException { try { mBinder.attach(provision); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Bring down the network interface and forget all non-volatile details about the current * network. * *

This method will block execution until the operation has completed. */ public void leave() throws LowpanException { try { mBinder.leave(); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Start a new commissioning session. Will fail if the interface is attached to a network or if * the interface is disabled. */ public @NonNull LowpanCommissioningSession startCommissioningSession( @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException { try { mBinder.startCommissioningSession(beaconInfo); return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Reset this network interface as if it has been power cycled. Will bring the network interface * down if it was previously up. Will not erase any non-volatile settings. * *

This method will block execution until the operation has completed. * * @hide */ public void reset() throws LowpanException { try { mBinder.reset(); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } // Public Getters and Setters /** Returns the name of this network interface. */ @NonNull public String getName() { try { return mBinder.getName(); } catch (DeadObjectException x) { return ""; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** * Indicates if the interface is enabled or disabled. * * @see #setEnabled * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED */ public boolean isEnabled() { try { return mBinder.isEnabled(); } catch (DeadObjectException x) { return false; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** * Enables or disables the LoWPAN interface. When disabled, the interface is put into a * low-power state and all commands that require the NCP to be queried will fail with {@link * android.net.lowpan.LowpanException#LOWPAN_DISABLED}. * * @see #isEnabled * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED * @hide */ public void setEnabled(boolean enabled) throws LowpanException { try { mBinder.setEnabled(enabled); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Indicates if the network interface is up or down. * * @hide */ public boolean isUp() { try { return mBinder.isUp(); } catch (DeadObjectException x) { return false; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** * Indicates if there is at least one peer in range. * * @return true if we have at least one other peer in range, false * otherwise. */ public boolean isConnected() { try { return mBinder.isConnected(); } catch (DeadObjectException x) { return false; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** * Indicates if this interface is currently commissioned onto an existing network. If the * interface is commissioned, the interface may be brought up using setUp(). */ public boolean isCommissioned() { try { return mBinder.isCommissioned(); } catch (DeadObjectException x) { return false; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** * Get interface state * *

State Diagram

* * * * @return The current state of the interface. * @see #STATE_OFFLINE * @see #STATE_COMMISSIONING * @see #STATE_ATTACHING * @see #STATE_ATTACHED * @see #STATE_FAULT */ public String getState() { try { return mBinder.getState(); } catch (DeadObjectException x) { return STATE_FAULT; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** Get network partition/fragment identifier. */ public String getPartitionId() { try { return mBinder.getPartitionId(); } catch (DeadObjectException x) { return EMPTY_PARTITION_ID; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** TODO: doc */ public LowpanIdentity getLowpanIdentity() { try { return mBinder.getLowpanIdentity(); } catch (DeadObjectException x) { return new LowpanIdentity(); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** TODO: doc */ @NonNull public String getRole() { try { return mBinder.getRole(); } catch (DeadObjectException x) { return ROLE_DETACHED; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } /** TODO: doc */ @Nullable public LowpanCredential getLowpanCredential() { try { return mBinder.getLowpanCredential(); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } public @NonNull String[] getSupportedNetworkTypes() throws LowpanException { try { return mBinder.getSupportedNetworkTypes(); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException { try { return mBinder.getSupportedChannels(); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } // Listener Support /** * Registers a subclass of {@link LowpanInterface.Callback} to receive events. * * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events. * @param handler If not null, events will be dispatched via the given handler * object. If null, the thread upon which events will be dispatched is * unspecified. * @see #registerCallback(Callback) * @see #unregisterCallback(Callback) */ public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) { ILowpanInterfaceListener.Stub listenerBinder = new ILowpanInterfaceListener.Stub() { private Handler mHandler; { if (handler != null) { mHandler = handler; } else if (mLooper != null) { mHandler = new Handler(mLooper); } else { mHandler = new Handler(); } } @Override public void onEnabledChanged(boolean value) { mHandler.post(() -> cb.onEnabledChanged(value)); } @Override public void onConnectedChanged(boolean value) { mHandler.post(() -> cb.onConnectedChanged(value)); } @Override public void onUpChanged(boolean value) { mHandler.post(() -> cb.onUpChanged(value)); } @Override public void onRoleChanged(String value) { mHandler.post(() -> cb.onRoleChanged(value)); } @Override public void onStateChanged(String value) { mHandler.post(() -> cb.onStateChanged(value)); } @Override public void onLowpanIdentityChanged(LowpanIdentity value) { mHandler.post(() -> cb.onLowpanIdentityChanged(value)); } @Override public void onLinkNetworkAdded(IpPrefix value) { mHandler.post(() -> cb.onLinkNetworkAdded(value)); } @Override public void onLinkNetworkRemoved(IpPrefix value) { mHandler.post(() -> cb.onLinkNetworkRemoved(value)); } @Override public void onLinkAddressAdded(String value) { LinkAddress la; try { la = new LinkAddress(value); } catch (IllegalArgumentException x) { Log.e( TAG, "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x); return; } mHandler.post(() -> cb.onLinkAddressAdded(la)); } @Override public void onLinkAddressRemoved(String value) { LinkAddress la; try { la = new LinkAddress(value); } catch (IllegalArgumentException x) { Log.e( TAG, "onLinkAddressRemoved: Bad LinkAddress \"" + value + "\", " + x); return; } mHandler.post(() -> cb.onLinkAddressRemoved(la)); } @Override public void onReceiveFromCommissioner(byte[] packet) { // This is only used by the LowpanCommissioningSession. } }; try { mBinder.addListener(listenerBinder); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } synchronized (mListenerMap) { mListenerMap.put(System.identityHashCode(cb), listenerBinder); } } /** * Registers a subclass of {@link LowpanInterface.Callback} to receive events. * *

The thread upon which events will be dispatched is unspecified. * * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events. * @see #registerCallback(Callback, Handler) * @see #unregisterCallback(Callback) */ public void registerCallback(Callback cb) { registerCallback(cb, null); } /** * Unregisters a previously registered callback class. * * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to * receive events. * @see #registerCallback(Callback, Handler) * @see #registerCallback(Callback) */ public void unregisterCallback(Callback cb) { int hashCode = System.identityHashCode(cb); synchronized (mListenerMap) { ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode); if (listenerBinder != null) { mListenerMap.remove(hashCode); try { mBinder.removeListener(listenerBinder); } catch (DeadObjectException x) { // We ignore a dead object exception because that // pretty clearly means our callback isn't registered. } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } } } } // Active and Passive Scanning /** * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface. * *

This method allocates a new unique object for each call. * * @see android.net.lowpan.LowpanScanner */ public @NonNull LowpanScanner createScanner() { return new LowpanScanner(mBinder); } // Route Management /** * Makes a copy of the internal list of LinkAddresses. * * @hide */ public LinkAddress[] getLinkAddresses() throws LowpanException { try { String[] linkAddressStrings = mBinder.getLinkAddresses(); LinkAddress[] ret = new LinkAddress[linkAddressStrings.length]; int i = 0; for (String str : linkAddressStrings) { ret[i++] = new LinkAddress(str); } return ret; } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Makes a copy of the internal list of networks reachable on via this link. * * @hide */ public IpPrefix[] getLinkNetworks() throws LowpanException { try { return mBinder.getLinkNetworks(); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Advertise the given IP prefix as an on-mesh prefix. * * @hide */ public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException { try { mBinder.addOnMeshPrefix(prefix, flags); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh * prefixes. * * @hide */ public void removeOnMeshPrefix(IpPrefix prefix) { try { mBinder.removeOnMeshPrefix(prefix); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { // Catch and ignore all service exceptions Log.e(TAG, x.toString()); } } /** * Advertise this device to other devices on the mesh network as having a specific route to the * given network. This device will then receive forwarded traffic for that network. * * @hide */ public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException { try { mBinder.addExternalRoute(prefix, flags); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { throw LowpanException.rethrowFromServiceSpecificException(x); } } /** * Revoke a previously advertised specific route to the given network. * * @hide */ public void removeExternalRoute(IpPrefix prefix) { try { mBinder.removeExternalRoute(prefix); } catch (RemoteException x) { throw x.rethrowAsRuntimeException(); } catch (ServiceSpecificException x) { // Catch and ignore all service exceptions Log.e(TAG, x.toString()); } } }