package com.idevicesinc.sweetblue; import java.util.UUID; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import com.idevicesinc.sweetblue.BleServer.IncomingListener; import static com.idevicesinc.sweetblue.BleServer.IncomingListener.*; import static com.idevicesinc.sweetblue.BleServer.OutgoingListener.*; import com.idevicesinc.sweetblue.utils.P_Const; import com.idevicesinc.sweetblue.utils.Utils; import com.idevicesinc.sweetblue.utils.Uuids; class P_BleServer_Listeners extends BluetoothGattServerCallback { private final BleServer m_server; private final P_Logger m_logger; private final P_TaskQueue m_queue; final PA_Task.I_StateListener m_taskStateListener = new PA_Task.I_StateListener() { @Override public void onStateChange(PA_Task task, PE_TaskState state) { if ( task.getClass() == P_Task_DisconnectServer.class ) { if( state.isEndingState() ) { if( state == PE_TaskState.SUCCEEDED ) { final P_Task_DisconnectServer task_cast = (P_Task_DisconnectServer) task; m_server.onNativeDisconnect(task_cast.m_nativeDevice.getAddress(), task_cast.isExplicit(), task_cast.getGattStatus()); } } } else if( task.getClass() == P_Task_ConnectServer.class ) { if( state.isEndingState() ) { final P_Task_ConnectServer task_cast = (P_Task_ConnectServer) task; if( state == PE_TaskState.SUCCEEDED ) { m_server.onNativeConnect(task_cast.m_nativeDevice.getAddress(), task_cast.isExplicit()); } else if( state == PE_TaskState.REDUNDANT ) { // nothing to do, but maybe should assert? } else if( state == PE_TaskState.FAILED_IMMEDIATELY ) { final BleServer.ConnectionFailListener.Status status = task_cast.getStatus(); if( status == BleServer.ConnectionFailListener.Status.SERVER_OPENING_FAILED || status == BleServer.ConnectionFailListener.Status.NATIVE_CONNECTION_FAILED_IMMEDIATELY ) { m_server.onNativeConnectFail(task_cast.m_nativeDevice, status, task_cast.getGattStatus()); } else { m_server.getManager().ASSERT(false, "Didn't expect server failed-immediately status to be something else."); m_server.onNativeConnectFail(task_cast.m_nativeDevice, BleServer.ConnectionFailListener.Status.NATIVE_CONNECTION_FAILED_IMMEDIATELY, task_cast.getGattStatus()); } } else if( state == PE_TaskState.FAILED ) { m_server.onNativeConnectFail(task_cast.m_nativeDevice, BleServer.ConnectionFailListener.Status.NATIVE_CONNECTION_FAILED_EVENTUALLY, task_cast.getGattStatus()); } else if( state == PE_TaskState.TIMED_OUT ) { m_server.onNativeConnectFail(task_cast.m_nativeDevice, BleServer.ConnectionFailListener.Status.TIMED_OUT, task_cast.getGattStatus()); } else if( state == PE_TaskState.SOFTLY_CANCELLED ) { // do nothing...this was handled upstream back in time } else { m_server.getManager().ASSERT(false, "Did not expect ending state " + state + " for connect task failure."); m_server.onNativeConnectFail(task_cast.m_nativeDevice, BleServer.ConnectionFailListener.Status.NATIVE_CONNECTION_FAILED_EVENTUALLY, task_cast.getGattStatus()); } } } } }; public P_BleServer_Listeners( BleServer server ) { m_server = server; m_logger = m_server.getManager().getLogger(); m_queue = m_server.getManager().getTaskQueue(); } private boolean hasCurrentDisconnectTaskFor(final BluetoothDevice device) { final P_Task_DisconnectServer disconnectTask = m_queue.getCurrent(P_Task_DisconnectServer.class, m_server); return disconnectTask != null && disconnectTask.isFor(m_server, device.getAddress()); } private boolean hasCurrentConnectTaskFor(final BluetoothDevice device) { final P_Task_ConnectServer connectTask = m_queue.getCurrent(P_Task_ConnectServer.class, m_server); return connectTask != null && connectTask.isFor(m_server, device.getAddress()); } private void failDisconnectTaskIfPossibleFor(final BluetoothDevice device) { final P_Task_DisconnectServer disconnectTask = m_queue.getCurrent(P_Task_DisconnectServer.class, m_server); if( disconnectTask != null && disconnectTask.isFor(m_server, device.getAddress()) ) { m_queue.fail(P_Task_DisconnectServer.class, m_server); } } private boolean failConnectTaskIfPossibleFor(final BluetoothDevice device, final int gattStatus) { if( hasCurrentConnectTaskFor(device) ) { final P_Task_ConnectServer connectTask = m_queue.getCurrent(P_Task_ConnectServer.class, m_server); connectTask.onNativeFail(gattStatus); return true; } else { return false; } } private void onNativeConnectFail(final BluetoothDevice nativeDevice, final int gattStatus) { //--- DRK > NOTE: Making an assumption that the underlying stack agrees that the connection state is STATE_DISCONNECTED. //--- This is backed up by basic testing, but even if the underlying stack uses a different value, it can probably //--- be assumed that it will eventually go to STATE_DISCONNECTED, so SweetBlue library logic is sounder "living under the lie" for a bit regardless. m_server.m_nativeWrapper.updateNativeConnectionState(nativeDevice.getAddress(), BluetoothProfile.STATE_DISCONNECTED); if( hasCurrentConnectTaskFor(nativeDevice) ) { final P_Task_ConnectServer connectTask = m_queue.getCurrent(P_Task_ConnectServer.class, m_server); connectTask.onNativeFail(gattStatus); } else { m_server.onNativeConnectFail(nativeDevice, BleServer.ConnectionFailListener.Status.NATIVE_CONNECTION_FAILED_EVENTUALLY, gattStatus); } } @Override public void onConnectionStateChange(final BluetoothDevice device, final int gattStatus, final int newState) { m_server.getManager().getPostManager().runOrPostToUpdateThread(new Runnable() { @Override public void run() { onConnectionStateChange_updateThread(device, gattStatus, newState); } }); } private void onConnectionStateChange_updateThread(final BluetoothDevice device, final int gattStatus, final int newState) { m_logger.log_conn_status(gattStatus, m_logger.gattConn(newState)); if( newState == BluetoothProfile.STATE_DISCONNECTED ) { m_server.m_nativeWrapper.updateNativeConnectionState(device.getAddress(), newState); final boolean wasConnecting = hasCurrentConnectTaskFor(device); if( !failConnectTaskIfPossibleFor(device, gattStatus) ) { if( hasCurrentDisconnectTaskFor(device) ) { final P_Task_DisconnectServer disconnectTask = m_queue.getCurrent(P_Task_DisconnectServer.class, m_server); disconnectTask.onNativeSuccess(gattStatus); } else { m_server.onNativeDisconnect(device.getAddress(), /*explicit=*/false, gattStatus); } } } else if( newState == BluetoothProfile.STATE_CONNECTING ) { if( Utils.isSuccess(gattStatus) ) { m_server.m_nativeWrapper.updateNativeConnectionState(device.getAddress(), newState); // m_device.onConnecting(/*definitelyExplicit=*/false, /*isReconnect=*/false, P_BondManager.OVERRIDE_EMPTY_STATES, /*bleConnect=*/true); failDisconnectTaskIfPossibleFor(device); if( !hasCurrentConnectTaskFor(device) ) { final P_Task_ConnectServer task = new P_Task_ConnectServer(m_server, device, m_taskStateListener, /*explicit=*/false, PE_TaskPriority.FOR_IMPLICIT_BONDING_AND_CONNECTING); m_queue.add(task); } else { m_server.onNativeConnecting_implicit(device.getAddress()); } } else { onNativeConnectFail(device, gattStatus); } } else if( newState == BluetoothProfile.STATE_CONNECTED ) { if( Utils.isSuccess(gattStatus) ) { m_server.m_nativeWrapper.updateNativeConnectionState(device.getAddress(), newState); failDisconnectTaskIfPossibleFor(device); if( hasCurrentConnectTaskFor(device) ) { m_queue.succeed(P_Task_ConnectServer.class, m_server); } else { m_server.onNativeConnect(device.getAddress(), /*explicit=*/false); } } else { onNativeConnectFail(device, gattStatus); } } //--- DRK > NOTE: never seen this case happen with BleDevice, we'll see if it happens with the server. else if( newState == BluetoothProfile.STATE_DISCONNECTING ) { m_server.m_nativeWrapper.updateNativeConnectionState(device.getAddress(), newState); //--- DRK > error level just so it's noticeable...never seen this with client connections so we'll see if it hits with server ones. m_logger.e("Actually natively disconnecting server!"); if( !hasCurrentDisconnectTaskFor(device) ) { P_Task_DisconnectServer task = new P_Task_DisconnectServer(m_server, device, m_taskStateListener, /*explicit=*/false, PE_TaskPriority.FOR_IMPLICIT_BONDING_AND_CONNECTING); m_queue.add(task); } failConnectTaskIfPossibleFor(device, gattStatus); } else { m_server.m_nativeWrapper.updateNativeConnectionState(device); } } @Override public void onServiceAdded(final int gattStatus, final BluetoothGattService service) { m_server.getManager().getPostManager().runOrPostToUpdateThread(new Runnable() { @Override public void run() { onServiceAdded_updateThread(gattStatus, service); } }); } private void onServiceAdded_updateThread(final int gattStatus, final BluetoothGattService service) { final P_Task_AddService task = m_queue.getCurrent(P_Task_AddService.class, m_server); if( task != null && task.getService().equals(service) ) { task.onServiceAdded(gattStatus, service); } else { final BleServer.ServiceAddListener.Status status = Utils.isSuccess(gattStatus) ? BleServer.ServiceAddListener.Status.SUCCESS : BleServer.ServiceAddListener.Status.FAILED_EVENTUALLY; final BleServer.ServiceAddListener.ServiceAddEvent e = new BleServer.ServiceAddListener.ServiceAddEvent ( m_server, service, status, gattStatus, /*solicited=*/false ); m_server.serviceMngr_server().invokeListeners(e, null); } } private OutgoingEvent newEarlyOutResponse_Read(final BluetoothDevice device, final UUID serviceUuid, final UUID charUuid, final UUID descUuid_nullable, final int requestId, final int offset, final BleServer.OutgoingListener.Status status) { final Target target = descUuid_nullable == null ? Target.CHARACTERISTIC : Target.DESCRIPTOR; final OutgoingEvent e = new OutgoingEvent ( m_server, device, serviceUuid, charUuid, descUuid_nullable, Type.READ, target, P_Const.EMPTY_BYTE_ARRAY, P_Const.EMPTY_BYTE_ARRAY, requestId, offset, /*responseNeeded=*/true, status, BleStatuses.GATT_STATUS_NOT_APPLICABLE, BleStatuses.GATT_STATUS_NOT_APPLICABLE, /*solicited=*/true ); return e; } private void onReadRequest_updateThread(final BluetoothDevice device, final int requestId, final int offset, final UUID serviceUuid, final UUID charUuid, final UUID descUuid_nullable) { final Target target = descUuid_nullable == null ? Target.CHARACTERISTIC : Target.DESCRIPTOR; final IncomingListener listener = m_server.getListener_Incoming() != null ? m_server.getListener_Incoming() : m_server.getManager().m_defaultServerIncomingListener; if( listener == null ) { m_server.invokeOutgoingListeners(newEarlyOutResponse_Read(device, serviceUuid, charUuid, /*descUuid=*/null, requestId, offset, Status.NO_REQUEST_LISTENER_SET), null); } else { final IncomingEvent requestEvent = new IncomingEvent ( m_server, device, serviceUuid, charUuid, descUuid_nullable, Type.READ, target, P_Const.EMPTY_BYTE_ARRAY, requestId, offset, /*responseNeeded=*/true ); final IncomingListener.Please please = listener.onEvent(requestEvent); if( please == null) { m_server.invokeOutgoingListeners(newEarlyOutResponse_Read(device, serviceUuid, charUuid, descUuid_nullable, requestId, offset, Status.NO_RESPONSE_ATTEMPTED), null); } else { final boolean attemptResponse = please.m_respond; if( attemptResponse ) { final P_Task_SendReadWriteResponse responseTask = new P_Task_SendReadWriteResponse(m_server, requestEvent, please); m_queue.add(responseTask); } else { m_server.invokeOutgoingListeners(newEarlyOutResponse_Read(device, serviceUuid, charUuid, descUuid_nullable, requestId, offset, Status.NO_RESPONSE_ATTEMPTED), please.m_outgoingListener); } } } } @Override public void onCharacteristicReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattCharacteristic characteristic) { m_server.getManager().getPostManager().runOrPostToUpdateThread(new Runnable() { @Override public void run() { onReadRequest_updateThread(device, requestId, offset, characteristic.getService().getUuid(), characteristic.getUuid(), /*descUuid=*/null); } }); } @Override public void onDescriptorReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattDescriptor descriptor) { m_server.getManager().getPostManager().runOrPostToUpdateThread(new Runnable() { @Override public void run() { onReadRequest_updateThread(device, requestId, offset, descriptor.getCharacteristic().getService().getUuid(), descriptor.getCharacteristic().getUuid(), descriptor.getUuid()); } }); } private OutgoingEvent newEarlyOutResponse_Write(final BluetoothDevice device, final Type type, final UUID serviceUuid, final UUID charUuid, final UUID descUuid_nullable, final int requestId, final int offset, final BleServer.OutgoingListener.Status status) { final Target target = descUuid_nullable == null ? Target.CHARACTERISTIC : Target.DESCRIPTOR; final OutgoingEvent e = new OutgoingEvent ( m_server, device, serviceUuid, charUuid, descUuid_nullable, type, target, P_Const.EMPTY_BYTE_ARRAY, P_Const.EMPTY_BYTE_ARRAY, requestId, offset, /*responseNeeded=*/true, status, BleStatuses.GATT_STATUS_NOT_APPLICABLE, BleStatuses.GATT_STATUS_NOT_APPLICABLE, /*solicited=*/true ); return e; } private void onWriteRequest_updateThread(final BluetoothDevice device, final byte[] data, final int requestId, final int offset, final boolean preparedWrite, final boolean responseNeeded, final UUID serviceUuid, final UUID charUuid, final UUID descUuid_nullable) { final Target target = descUuid_nullable == null ? Target.CHARACTERISTIC : Target.DESCRIPTOR; final Type type = preparedWrite ? Type.PREPARED_WRITE : Type.WRITE; final IncomingListener listener = m_server.getListener_Incoming() != null ? m_server.getListener_Incoming() : m_server.getManager().m_defaultServerIncomingListener; if( listener == null ) { m_server.invokeOutgoingListeners(newEarlyOutResponse_Write(device, type, serviceUuid, charUuid, /*descUuid=*/null, requestId, offset, Status.NO_REQUEST_LISTENER_SET), null); } else { final IncomingEvent requestEvent = new IncomingEvent ( m_server, device, serviceUuid, charUuid, descUuid_nullable, type, target, data, requestId, offset, responseNeeded ); final IncomingListener.Please please = listener.onEvent(requestEvent); if( please == null) { m_server.invokeOutgoingListeners(newEarlyOutResponse_Write(device, type, serviceUuid, charUuid, descUuid_nullable, requestId, offset, Status.NO_RESPONSE_ATTEMPTED), null); } else { final boolean attemptResponse = please.m_respond; if( attemptResponse ) { final P_Task_SendReadWriteResponse responseTask = new P_Task_SendReadWriteResponse(m_server, requestEvent, please); m_queue.add(responseTask); } else { m_server.invokeOutgoingListeners(newEarlyOutResponse_Write(device, type, serviceUuid, charUuid, descUuid_nullable, requestId, offset, Status.NO_RESPONSE_ATTEMPTED), please.m_outgoingListener); } } } } @Override public void onCharacteristicWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattCharacteristic characteristic, final boolean preparedWrite, final boolean responseNeeded, final int offset, final byte[] value) { m_server.getManager().getPostManager().runOrPostToUpdateThread(new Runnable() { @Override public void run() { onWriteRequest_updateThread(device, value, requestId, offset, preparedWrite, responseNeeded, characteristic.getService().getUuid(), characteristic.getUuid(), /*descUuid=*/null); } }); } @Override public void onDescriptorWriteRequest( final BluetoothDevice device, final int requestId, final BluetoothGattDescriptor descriptor, final boolean preparedWrite, final boolean responseNeeded, final int offset, final byte[] value) { m_server.getManager().getPostManager().runOrPostToUpdateThread(new Runnable() { @Override public void run() { onWriteRequest_updateThread(device, value, requestId, offset, preparedWrite, responseNeeded, descriptor.getCharacteristic().getService().getUuid(), descriptor.getCharacteristic().getUuid(), descriptor.getUuid()); } }); } @Override public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { } @Override public void onNotificationSent( final BluetoothDevice device, final int gattStatus ) { m_server.getManager().getPostManager().runOrPostToUpdateThread(new Runnable() { @Override public void run() { onNotificationSent_updateThread(device, gattStatus); } }); } private void onNotificationSent_updateThread(final BluetoothDevice device, final int gattStatus) { final P_Task_SendNotification task = m_queue.getCurrent(P_Task_SendNotification.class, m_server); if( task != null && task.m_macAddress.equals(device.getAddress()) ) { task.onNotificationSent(device, gattStatus); } else { final BleServer.OutgoingListener.OutgoingEvent e = new BleServer.OutgoingListener.OutgoingEvent ( m_server, device, Uuids.INVALID, Uuids.INVALID, BleServer.ExchangeListener.ExchangeEvent.NON_APPLICABLE_UUID, Type.NOTIFICATION, BleServer.ExchangeListener.Target.CHARACTERISTIC, P_Const.EMPTY_BYTE_ARRAY, P_Const.EMPTY_BYTE_ARRAY, BleServer.ExchangeListener.ExchangeEvent.NON_APPLICABLE_REQUEST_ID, /*offset=*/0, /*responseNeeded=*/false, BleServer.OutgoingListener.Status.SUCCESS, BleStatuses.GATT_STATUS_NOT_APPLICABLE, gattStatus, /*solicited=*/false ); m_server.invokeOutgoingListeners(e, null); } } }