blob: 8f75e8db6243c8051c85377d0cfdf058201ebc1e [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.net.IpPrefix;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
/**
* Commissioning Session.
*
* <p>This class enables a device to learn the credential needed to join a network using a technique
* called "in-band commissioning".
*
* @hide
*/
// @SystemApi
public class LowpanCommissioningSession {
private final ILowpanInterface mBinder;
private final LowpanBeaconInfo mBeaconInfo;
private final ILowpanInterfaceListener mInternalCallback = new InternalCallback();
private final Looper mLooper;
private Handler mHandler;
private Callback mCallback = null;
private volatile boolean mIsClosed = false;
/**
* Callback base class for {@link LowpanCommissioningSession}
*
* @hide
*/
// @SystemApi
public abstract static class Callback {
public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
public void onClosed() {};
}
private class InternalCallback extends ILowpanInterfaceListener.Stub {
@Override
public void onStateChanged(String value) {
if (!mIsClosed) {
switch (value) {
case ILowpanInterface.STATE_OFFLINE:
case ILowpanInterface.STATE_FAULT:
synchronized (LowpanCommissioningSession.this) {
lockedCleanup();
}
}
}
}
@Override
public void onReceiveFromCommissioner(byte[] packet) {
mHandler.post(
() -> {
synchronized (LowpanCommissioningSession.this) {
if (!mIsClosed && (mCallback != null)) {
mCallback.onReceiveFromCommissioner(packet);
}
}
});
}
// We ignore all other callbacks.
@Override
public void onEnabledChanged(boolean value) {}
@Override
public void onConnectedChanged(boolean value) {}
@Override
public void onUpChanged(boolean value) {}
@Override
public void onRoleChanged(String value) {}
@Override
public void onLowpanIdentityChanged(LowpanIdentity value) {}
@Override
public void onLinkNetworkAdded(IpPrefix value) {}
@Override
public void onLinkNetworkRemoved(IpPrefix value) {}
@Override
public void onLinkAddressAdded(String value) {}
@Override
public void onLinkAddressRemoved(String value) {}
}
LowpanCommissioningSession(
ILowpanInterface binder, LowpanBeaconInfo beaconInfo, Looper looper) {
mBinder = binder;
mBeaconInfo = beaconInfo;
mLooper = looper;
if (mLooper != null) {
mHandler = new Handler(mLooper);
} else {
mHandler = new Handler();
}
try {
mBinder.addListener(mInternalCallback);
} catch (RemoteException x) {
throw x.rethrowAsRuntimeException();
}
}
private void lockedCleanup() {
// Note: this method is only called from synchronized contexts.
if (!mIsClosed) {
try {
mBinder.removeListener(mInternalCallback);
} catch (DeadObjectException x) {
/* We don't care if we receive a DOE at this point.
* DOE is as good as success as far as we are concerned.
*/
} catch (RemoteException x) {
throw x.rethrowAsRuntimeException();
}
if (mCallback != null) {
mHandler.post(() -> mCallback.onClosed());
}
}
mCallback = null;
mIsClosed = true;
}
/** TODO: doc */
@NonNull
public LowpanBeaconInfo getBeaconInfo() {
return mBeaconInfo;
}
/** TODO: doc */
public void sendToCommissioner(@NonNull byte[] packet) {
if (!mIsClosed) {
try {
mBinder.sendToCommissioner(packet);
} catch (DeadObjectException x) {
/* This method is a best-effort delivery.
* We don't care if we receive a DOE at this point.
*/
} catch (RemoteException x) {
throw x.rethrowAsRuntimeException();
}
}
}
/** TODO: doc */
public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
if (!mIsClosed) {
/* This class can be created with or without a default looper.
* Also, this method can be called with or without a specific
* handler. If a handler is specified, it is to always be used.
* Otherwise, if there was a Looper specified when this object
* was created, we create a new handle based on that looper.
* Otherwise we just create a default handler object. Since we
* don't really know how the previous handler was created, we
* end up always replacing it here. This isn't a huge problem
* because this method should be called infrequently.
*/
if (handler != null) {
mHandler = handler;
} else if (mLooper != null) {
mHandler = new Handler(mLooper);
} else {
mHandler = new Handler();
}
mCallback = cb;
}
}
/** TODO: doc */
public synchronized void close() {
if (!mIsClosed) {
try {
mBinder.closeCommissioningSession();
lockedCleanup();
} catch (DeadObjectException x) {
/* We don't care if we receive a DOE at this point.
* DOE is as good as success as far as we are concerned.
*/
} catch (RemoteException x) {
throw x.rethrowAsRuntimeException();
}
}
}
}