public abstract class AbstractAction<T, E extends Throwable, I, R> {
    public enum Type {
        WRITE_CHARACTERISTIC(1),
        READ_CHARACTERISTIC(2),
        WRITE_DESCRIPTOR(3),
        READ_DESCRIPTOR(4),
        ENABLE_NOTIFICATION(5),
        READ_REMOTE_RSSI(6),
        CONNECT(101),
        DISCOVER_SERVICES(102),
        DISCONNECT(103);

        private final int mCode;

        Type(int code) {
            mCode = code;
        }

        public int getCode() {
            return mCode;
        }

        public static Type valueOf(int code) {
            for (Type type : values()) {
                if (type.getCode() == code) { return type; }
            }
            throw new IllegalArgumentException();
        }
    }

    public static final String KEY_IDENTITY = "key_identity";

    private final I mIdentity;
    private final Type mType;
    private final ResolveStrategy<T, E, R> mResolveStrategy;

    public AbstractAction(I identity, Type type, ResolveStrategy<T, E, R> resolveStrategy) {
        mIdentity = identity;
        mType = type;
        mResolveStrategy = resolveStrategy;
    }

    public final I getIdentity() {
        return mIdentity;
    }

    public final Type getType() {
        return mType;
    }

    public final R getResolver() {
        return mResolveStrategy.getResolver();
    }

    public void resolve(T value) {
        mResolveStrategy.resolve(value);
    }

    public void reject(E throwable) {
        mResolveStrategy.reject(throwable);
    }

    public final Message obtainMessage() {
        Message message = Message.obtain();
        message.what = mType.getCode();
        message.setData(getBundle());
        return message;
    }

    public Bundle getBundle() {
        Bundle bundle = new Bundle();
        if (mIdentity instanceof Serializable) {
            bundle.putSerializable(KEY_IDENTITY, (Serializable) mIdentity);
        }
        return bundle;
    }

    public abstract boolean execute(BluetoothGattWrapper gattWrapper);
} public abstract class AbstractConnectAction<R> extends AbstractAction<Void, BletiaException, Void, R> {
    public static final String TAG = AbstractConnectAction.class.getSimpleName();

    private final BluetoothDeviceWrapper mDevice;
    private final StateContainer mContainer;

    public AbstractConnectAction(ResolveStrategy<Void, BletiaException, R> resolveStrategy, BluetoothDeviceWrapper device, StateContainer container) {
        super(null, Type.CONNECT, resolveStrategy);
        mDevice = device;
        mContainer = container;
        HandlerThread thread = new HandlerThread(mDevice.getName());
        thread.start();
        mContainer.setMessageThread(new BleMessageThread(thread, mContainer));
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        mContainer.setState(BleState.CONNECTING);
        mContainer.setGattWrapper(mDevice.connectGatt(
                mContainer.getContext(), false, mContainer.getCallbackHandler()));
        return true;
    }
} public abstract class AbstractDisconnectAction<R> extends AbstractAction<Void, BletiaException, Void, R> {
    public static final String TAG = AbstractDisconnectAction.class.getSimpleName();

    private final StateContainer mContainer;

    public AbstractDisconnectAction(ResolveStrategy<Void, BletiaException, R> resolveStrategy, StateContainer container) {
        super(null, Type.DISCONNECT, resolveStrategy);
        mContainer = container;
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        mContainer.setState(BleState.DISCONNECTING);
        gattWrapper.disconnect();
        return true;
    }
} public abstract class AbstractDiscoverServicesAction<R> extends AbstractAction<Void, BletiaException, Void, R> {
    public static final String TAG = AbstractDiscoverServicesAction.class.getSimpleName();

    private final StateContainer mContaienr;

    public AbstractDiscoverServicesAction(ResolveStrategy<Void, BletiaException, R> resolveStrategy, StateContainer container) {
        super(null, Type.DISCOVER_SERVICES, resolveStrategy);
        mContaienr = container;
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        if (!gattWrapper.discoverServices()) {
            reject(new BletiaException(this, BleErrorType.REQUEST_FAILURE));
            return false;
        }
        mContaienr.setState(BleState.SERVICE_DISCOVERING);
        return true;
    }
} public abstract class AbstractEnableNotificationAction<R> extends AbstractCharacteristicAction<R> {

    private final boolean mEnabled;

    public AbstractEnableNotificationAction(BluetoothGattCharacteristic characteristic, boolean enabled,
                                            ResolveStrategy<BluetoothGattCharacteristic, BletiaException, R> resolveStrategy) {
        super(characteristic, Type.ENABLE_NOTIFICATION, resolveStrategy);
        mEnabled = enabled;
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        if (gattWrapper.setCharacteristicNotification(getCharacteristic(), mEnabled)) {
            BluetoothGattDescriptor descriptor = NotificationUtils.getDescriptor(getCharacteristic(), mEnabled);
            if (!gattWrapper.writeDescriptor(descriptor)) {
                reject(new BletiaException(this, BleErrorType.OPERATION_INITIATED_FAILURE));
                return false;
            }
        } else {
            reject(new BletiaException(this, BleErrorType.REQUEST_FAILURE));
            return false;
        }
        return true;
    }

    public boolean getEnabled() {
        return mEnabled;
    }
} public abstract class AbstractReadCharacteristicAction<R> extends AbstractCharacteristicAction<R> {

    public AbstractReadCharacteristicAction(BluetoothGattCharacteristic characteristic,
                                            ResolveStrategy<BluetoothGattCharacteristic, BletiaException, R> resolveStrategy) {
        super(characteristic, Type.READ_CHARACTERISTIC, resolveStrategy);
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        if (!gattWrapper.readCharacteristic(getCharacteristic())) {
            reject(new BletiaException(this, BleErrorType.OPERATION_INITIATED_FAILURE));
            return false;
        }
        return true;
    }
} public abstract class AbstractReadDescriptorAction<R> extends AbstractDescriptorAction<R> {

    public AbstractReadDescriptorAction(BluetoothGattDescriptor descriptor,
                                        ResolveStrategy<BluetoothGattDescriptor, BletiaException, R> resolveStrategy) {
        super(descriptor, Type.READ_DESCRIPTOR, resolveStrategy);
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        if (!gattWrapper.readDescriptor(getDescriptor())) {
            reject(new BletiaException(this, BleErrorType.OPERATION_INITIATED_FAILURE));
            return false;
        }
        return true;
    }
} public abstract class AbstractReadRemoteRssiAction<R> extends AbstractAction<Integer, BletiaException, Void, R> {
    public AbstractReadRemoteRssiAction(ResolveStrategy<Integer, BletiaException, R> resolveStrategy) {
        super(null, Type.READ_REMOTE_RSSI, resolveStrategy);
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        if (!gattWrapper.readRemoteRssi()) {
            reject(new BletiaException(this, BleErrorType.REQUEST_FAILURE));
            return false;
        }
        return true;
    }
} public abstract class AbstractWriteCharacteristicAction<R> extends AbstractCharacteristicAction<R> {

    public AbstractWriteCharacteristicAction(BluetoothGattCharacteristic characteristic,
                                             ResolveStrategy<BluetoothGattCharacteristic, BletiaException, R> resolveStrategy) {
        super(characteristic, Type.WRITE_CHARACTERISTIC, resolveStrategy);
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        if (!gattWrapper.writeCharacteristic(getCharacteristic())) {
            reject(new BletiaException(this, BleErrorType.OPERATION_INITIATED_FAILURE));
            return false;
        }
        return true;
    }
} public abstract class AbstractWriteDescriptorAction<R> extends AbstractDescriptorAction<R> {

    public AbstractWriteDescriptorAction(BluetoothGattDescriptor descriptor,
                                         ResolveStrategy<BluetoothGattDescriptor, BletiaException, R> resolveStrategy) {
        super(descriptor, Type.WRITE_DESCRIPTOR, resolveStrategy);
    }

    @Override
    public boolean execute(BluetoothGattWrapper gattWrapper) {
        if (!gattWrapper.writeDescriptor(getDescriptor())) {
            reject(new BletiaException(this, BleErrorType.OPERATION_INITIATED_FAILURE));
            return false;
        }
        return true;
    }
}
import android.os.Message;
import java.util.UUID;
import info.izumin.android.bletia.core.action.AbstractAction;
import info.izumin.android.bletia.core.action.AbstractConnectAction;
import info.izumin.android.bletia.core.action.AbstractDisconnectAction;
import info.izumin.android.bletia.core.action.AbstractDiscoverServicesAction;
import info.izumin.android.bletia.core.action.AbstractEnableNotificationAction;
import info.izumin.android.bletia.core.action.AbstractReadCharacteristicAction;
import info.izumin.android.bletia.core.action.AbstractReadDescriptorAction;
import info.izumin.android.bletia.core.action.AbstractReadRemoteRssiAction;
import info.izumin.android.bletia.core.action.AbstractWriteCharacteristicAction;
import info.izumin.android.bletia.core.action.AbstractWriteDescriptorAction;
            return (UUID) msg.getData().getSerializable(AbstractAction.KEY_IDENTITY);
        }

        private ActionQueue<AbstractWriteCharacteristicAction<?>, UUID> getQueueFromContainer(StateContainer container) {
            return container.getWriteCharacteristicActionQueue();
        }
    },
    READ_DESCRIPTOR(AbstractAction.Type.READ_DESCRIPTOR) {
        @Override
        public boolean execute(Message msg, StateContainer container) {
            return getQueueFromContainer(container).execute(getIdentityFromMessage(msg), container.getGattWrapper());
        }

        @Override
        public boolean isRunning(Message msg, StateContainer container) {
            return getQueueFromContainer(container).isRunning(getIdentityFromMessage(msg));
        }

        private UUID getIdentityFromMessage(Message msg) {
            return (UUID) msg.getData().getSerializable(AbstractAction.KEY_IDENTITY);
        }

        private ActionQueue<AbstractReadDescriptorAction<?>, UUID> getQueueFromContainer(StateContainer container) {
            return container.getReadDescriptorActionQueue();
        }
    },
    WRITE_DESCRIPTOR(AbstractAction.Type.WRITE_DESCRIPTOR) {
        @Override
        public boolean execute(Message msg, StateContainer container) {
            return getQueueFromContainer(container).execute(getIdentityFromMessage(msg), container.getGattWrapper());
        }

        @Override
        public boolean isRunning(Message msg, StateContainer container) {
            return getQueueFromContainer(container).isRunning(getIdentityFromMessage(msg));
        }

        private UUID getIdentityFromMessage(Message msg) {
            return (UUID) msg.getData().getSerializable(AbstractAction.KEY_IDENTITY);
        }

        private ActionQueue<AbstractWriteDescriptorAction<?>, UUID> getQueueFromContainer(StateContainer container) {
            return container.getWriteDescriptorActionQueue();
        }
    },
    ENABLE_NOTIFICATION(AbstractAction.Type.ENABLE_NOTIFICATION) {
        @Override
        public boolean execute(Message msg, StateContainer container) {
            return getQueueFromContainer(container).execute(getIdentityFromMessage(msg), container.getGattWrapper());
        }

        @Override
        public boolean isRunning(Message msg, StateContainer container) {
            return getQueueFromContainer(container).isRunning(getIdentityFromMessage(msg));
        }

        private UUID getIdentityFromMessage(Message msg) {
            return (UUID) msg.getData().getSerializable(AbstractAction.KEY_IDENTITY);
        }

        private ActionQueue<AbstractEnableNotificationAction<?>, UUID> getQueueFromContainer(StateContainer container) {
            return container.getEnableNotificationActionQueue();
        }
    },
    READ_REMOTE_RSSI(AbstractAction.Type.READ_REMOTE_RSSI) {
        @Override
        public boolean execute(Message msg, StateContainer container) {
            return getQueueFromContainer(container).execute(null, container.getGattWrapper());
        }

        @Override
        public boolean isRunning(Message msg, StateContainer container) {
            return getQueueFromContainer(container).isRunning(null);
        }

        private ActionQueue<AbstractReadRemoteRssiAction<?>, Void> getQueueFromContainer(StateContainer container) {
            return container.getReadRemoteRssiActionQueue();
        }
    },
    CONNECT(AbstractAction.Type.CONNECT) {
        @Override
        public boolean execute(Message msg, StateContainer container) {
            return getQueueFromContainer(container).execute(null, container.getGattWrapper());
        }

        @Override
        public boolean isRunning(Message msg, StateContainer container) {
            return getQueueFromContainer(container).isRunning(null);
        }

        private ActionQueue<AbstractConnectAction<?>, Void> getQueueFromContainer(StateContainer container) {
            return container.getConnectActionQueue();
        }
    },
    DISCOVER_SERVICES(AbstractAction.Type.DISCOVER_SERVICES) {
        @Override
        public boolean execute(Message msg, StateContainer container) {
            return getQueueFromContainer(container).execute(null, container.getGattWrapper());
        }

        @Override
        public boolean isRunning(Message msg, StateContainer container) {
            return getQueueFromContainer(container).isRunning(null);
        }

        private ActionQueue<AbstractDiscoverServicesAction<?>, Void> getQueueFromContainer(StateContainer container) {
            return container.getDiscoverServicesActionQueue();
        }
    },
    DISCONNECT(AbstractAction.Type.DISCONNECT) {
        @Override
        public boolean execute(Message msg, StateContainer container) {
            return getQueueFromContainer(container).execute(null, container.getGattWrapper());
        }

        @Override
        public boolean isRunning(Message msg, StateContainer container) {
            return getQueueFromContainer(container).isRunning(null);
        }

        private ActionQueue<AbstractDisconnectAction<?>, Void> getQueueFromContainer(StateContainer container) {