blob: 57e91357b2378ff399961b6554e2dd3688295922 [file] [log] [blame]
/*
* 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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
* <code>setUp(false)</code>, 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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>The interface will enter this state when the driver has detected some sort of problem from
* which it was not immediately able to recover.
*
* <p>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<Integer, ILowpanInterfaceListener> 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.
*
* <p>This method will block until either the network was successfully formed or an error
* prevents the network form being formed.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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 <code>true</code> if we have at least one other peer in range, <code>false</code>
* 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
*
* <h3>State Diagram</h3>
*
* <img src="LowpanInterface-1.png" />
*
* @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 <code>null</code>, events will be dispatched via the given handler
* object. If <code>null</code>, 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.
*
* <p>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.
*
* <p>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());
}
}
}