Android - приемник Bluetooth не работает на рекламном устройстве

avatar
Andrea Pantieri
1 июля 2021 в 18:30
121
1
1

Это мое первое приложение, работающее с Bluetooth в Android, и у меня возникла уникальная проблема: приемник Bluetooth не работает на устройстве, рекламирующем службу Bluetooth.

Я тестировал приложение одновременно на 2 телефонах (я буду называть их телефонами А и Б, чтобы объяснить лучше). Сначала я запускаю рекламу с телефона А, затем запускаю открытие с телефона Б и, наконец, нажимаю кнопку в телефоне Б для отправки данных. Эта кнопка должна сначала запустить Gatt-соединение, и, если она работает, она должна передать сообщение, подтверждающее соединение. Чтобы увидеть это, я использовал журнал в широковещательном приемнике, но в результате я получаю это сообщение, появляющееся только в логарифме телефона B, но не на телефоне A.

Я просмотрел множество примеров и разместил сообщения на Stackoverflow, но не могу найти решение этой проблемы.

Поэтому я действительно не могу понять, в чем тут настоящая проблема. Может быть, я просто плохо использую классы Bluetooth или мне просто не хватает знаний. В любом случае здесь находится весь код MainActivity, так как это единственный класс этого простого проекта.

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView mText;
private Button mAdvertiseButton;
private Button mDiscoverButton;
private Button mSendButton;
private String TAG = "INFOBLERESULTS";
private BluetoothLeScanner mBluetoothLeScanner;
private BluetoothDevice bluetoothDevice;
private Handler mHandler = new Handler();
private ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        Log.d(TAG, result.getDevice().getAddress());
        super.onScanResult(callbackType, result);
        if( result == null
                || result.getDevice() == null
                || TextUtils.isEmpty(result.getDevice().getAddress()) )
            return;

        StringBuilder builder = new StringBuilder( result.getDevice().getAddress() );
        builder.append("\n").append(result.getDevice().getName());

        //builder.append("\n").append(new String(result.getScanRecord().getServiceData(result.getScanRecord().getServiceUuids().get(0)), Charset.forName("UTF-8")));

        mText.setText(builder.toString());
        bluetoothDevice = result.getDevice();
        bluetoothLeService = new BluetoothLeService();
        bluetoothLeService.setAddress(result.getDevice().getAddress());
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
    }

    @Override
    public void onScanFailed(int errorCode) {
        Log.e( TAG, "Discovery onScanFailed: " + errorCode );
        super.onScanFailed(errorCode);
    }
};
private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
private boolean connected = false;

//Inner classes
class BluetoothLeService extends Service {
    public final static String ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
    public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";

    private static final int STATE_DISCONNECTED = 0;
    private static final int STATE_CONNECTED = 2;

    private int connectionState;
    public Context ctx;

    protected BluetoothGatt bluetoothGatt;

    protected final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            Log.i(TAG, "GATT status = "+ status + " newState = " + newState);
            if(status == BluetoothGatt.GATT_SUCCESS){
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    // successfully connected to the GATT Server
                    connectionState = STATE_CONNECTED;
                    broadcastUpdate(ACTION_GATT_CONNECTED);
                    bluetoothGatt = gatt;
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    // disconnected from the GATT Server
                    connectionState = STATE_DISCONNECTED;
                    broadcastUpdate(ACTION_GATT_DISCONNECTED);
                    gatt.close();
                }
            }else{
                gatt.close();
            }

        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        public void onCharacteristicRead(
                BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic,
                int status
        ) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }

        @Override
        public void onCharacteristicChanged(
                BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic
        ) {
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        }


    };

    private Binder binder = new LocalBinder();
    private String address = "";

    public void setAddress(String address) {
        this.address = address;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        close();
        return super.onUnbind(intent);
    }

    private void close() {
        if (bluetoothGatt == null) {
            return;
        }
        bluetoothGatt.close();
        bluetoothGatt = null;
    }

    public boolean connect() {
        if (bluetoothAdapter == null || this.address == null || this.address.equals("")) {
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }

        try {
            final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
            bluetoothGatt = device.connectGatt(MainActivity.this.getApplicationContext(), false, gattCallback);
            Log.d(TAG,"GATT "+ bluetoothGatt);
            return true;

        } catch (IllegalArgumentException exception) {
            Log.w(TAG, "Device not found with provided address.");
            return false;
        }
    }

    private void broadcastUpdate(final String action) {
        final Intent intent = new Intent(action);
        Log.i(TAG, intent + "");
        MainActivity.this.getApplicationContext().sendBroadcast(intent);
    }

    private void broadcastUpdate(final String action, BluetoothGattCharacteristic characteristic) {
        final Intent intent = new Intent(action);
        
        sendBroadcast(intent);

    }

    public List<BluetoothGattService> getSupportedGattServices() {
        if (bluetoothGatt == null) return null;
        return bluetoothGatt.getServices();
    }

    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (bluetoothGatt == null) {
            Log.w(TAG, "BluetoothGatt not initialized");
            return;
        }
        bluetoothGatt.readCharacteristic(characteristic);
    }

    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enabled) {
        if (bluetoothGatt == null) {
            Log.w(TAG, "BluetoothGatt not initialized");
            return;
        }
        bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
    }



    class LocalBinder extends Binder {
        public BluetoothLeService getService() {
            return BluetoothLeService.this;
        }
    }
}
private BluetoothLeService bluetoothLeService = new BluetoothLeService();

private final ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        bluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
        if (bluetoothLeService != null) {
            if (!bluetoothAdapter.isEnabled()) {
                Log.e(TAG, "Unable to initialize Bluetooth");
            }
            else{
                bluetoothLeService.connect();
                Log.i(TAG, "Service connected");
            }
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        bluetoothLeService = null;
    }
};

private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        Log.d(TAG, "RECEIVED " + action);
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            connected = true;
            //Log.d(TAG, "CONNECTED");
        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
            connected = false;
            //Log.d(TAG, "DISCONNECTED");
        }
    }

};



private final ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                Log.i("RESULT", result.getResultCode() + "");
                setup();
            }
        });

private void setup() {
    bluetoothLeService.ctx = this.getApplicationContext();
    Log.d("APPLICATIONCONTEXT", bluetoothLeService.ctx + "");
    mDiscoverButton.setOnClickListener(this);
    mAdvertiseButton.setOnClickListener(this);
    mSendButton.setOnClickListener(this);

    mBluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();

    if (!BluetoothAdapter.getDefaultAdapter().isMultipleAdvertisementSupported()) {
        Toast.makeText(this, "Multiple advertisement not supported", Toast.LENGTH_SHORT).show();
        mAdvertiseButton.setEnabled(false);
        mDiscoverButton.setEnabled(false);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull @org.jetbrains.annotations.NotNull String[] permissions, @NonNull @org.jetbrains.annotations.NotNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 2) {
        final LocationManager manager = (LocationManager) getSystemService( Context.LOCATION_SERVICE );
        if(!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
            Intent enableLocationIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
            someActivityResultLauncher.launch(enableLocationIntent);
        }
        else{
            setup();
        }


    }

}

@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mText = (TextView) findViewById( R.id.text );
    mDiscoverButton = (Button) findViewById( R.id.discover_btn );
    mAdvertiseButton = (Button) findViewById( R.id.advertise_btn );
    mSendButton = (Button) findViewById( R.id.send_btn );

    this.requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 2);

}

@Override
protected void onResume() {
    super.onResume();
    MainActivity.this.getApplicationContext().registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter());
}
@Override
protected void onPause() {
    super.onPause();
    MainActivity.this.getApplicationContext().unregisterReceiver(gattUpdateReceiver);
}




@Override
public void onClick(View v) {
    Log.d(TAG, getString( R.string.ble_uuid ));
    if( v.getId() == R.id.discover_btn ) {
        discover();
    } else if( v.getId() == R.id.advertise_btn ) {
        advertise();
        //MainActivity.this.getApplicationContext().registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter());
    } else if (v.getId() == R.id.send_btn){
        //MainActivity.this.getApplicationContext().unregisterReceiver(gattUpdateReceiver);
        send();

    }
}

public void advertise(){
    BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();

    AdvertiseSettings settings = new AdvertiseSettings.Builder().setTimeout(0)
            .setAdvertiseMode( AdvertiseSettings.ADVERTISE_MODE_BALANCED )
            .setTxPowerLevel( AdvertiseSettings.ADVERTISE_TX_POWER_HIGH )
            .setConnectable( true )
            .build();
    ParcelUuid pUuid = ParcelUuid.fromString( getString( R.string.ble_uuid ) ) ;

    AdvertiseData data = new AdvertiseData.Builder()
            .addServiceUuid( pUuid ).setIncludeDeviceName(false)
            .build();
    AdvertiseCallback advertisingCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            Log.d(TAG, "START ADVERTISING");
            super.onStartSuccess(settingsInEffect);
        }

        @Override
        public void onStartFailure(int errorCode) {
            Log.e( TAG, "Advertising onStartFailure: " + errorCode );
            super.onStartFailure(errorCode);
        }
    };

    advertiser.startAdvertising( settings, data, advertisingCallback );


}

public void discover(){
    ScanFilter filter = new ScanFilter.Builder()
            .setServiceUuid( ParcelUuid.fromString( getString(R.string.ble_uuid ) ) )
            .build();
    List<ScanFilter> filters = new ArrayList<>();
    filters.add( filter );
    ScanSettings settings = new ScanSettings.Builder()
            .setScanMode( ScanSettings.SCAN_MODE_BALANCED )
            .build();
    mBluetoothLeScanner.startScan(filters, settings, mScanCallback);
    Log.d(TAG, "Discovery started");
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "Discovery stopped");
            mBluetoothLeScanner.stopScan(mScanCallback);
        }
    }, 10000);
}

public void send(){
    Log.d(TAG, "START CONNECTIONG GATT");
    mBluetoothLeScanner.stopScan(mScanCallback);
    //boundGatt();
    connectGatt();
}

public void boundGatt(){
    Intent gattServiceIntent = new Intent(MainActivity.this.getApplicationContext(), BluetoothLeService.class);
    bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}

public void connectGatt(){
    bluetoothLeService.connect();
}

private static IntentFilter makeGattUpdateIntentFilter() {
    final IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
    intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
    intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
    intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
    return intentFilter;
}

Источник

Ответы (1)

avatar
alexander.cpp
2 июля 2021 в 02:14
1

Ваше периферийное устройство (телефон A) выполняет только рекламу, но BluetoothGattServer не настроено. Это может быть причиной описанного поведения - реклама и сканирование работают, но соединение не работает.

BluetoothGatt + BluetoothGattCallback предназначены для центральной роли (вы называете это телефоном B). BluetoothGattServer + BluetoothGattServerCallback для периферийного устройства (телефон A).

Примечания:

  1. Соединение с центральной стороны (телефон B) выглядит хорошо, потому что, когда рекламируемое устройство найдено, вы получаете его с помощью bluetoothAdapter.getRemoteDevice(address), затем звоните device.connectGatt
  2. Для передачи некоторых данных вам потребуется добавить BluetoothGattService с BluetoothGattCharacteristic на ваш BluetoothGattServer - пример настройки в Kotlin
  3. Пример проекта на github: BLEProof - это на Котлине, 2 приложения взаимодействуют друг с другом: центральное и периферийное, весь код в MainActivity.kt