From b76a8267b8bc69fd1634256cbd704f52bbc495fe Mon Sep 17 00:00:00 2001 From: TaaviE Date: Mon, 20 Jul 2020 00:58:21 +0300 Subject: [PATCH] Added CompanionDeviceManager support, added target 29 support, refactored the scan display and UI. --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 53 +- .../activities/DiscoveryActivity.java | 658 +++++++++++------- .../BluetoothStateChangeReceiver.java | 2 +- .../main/res/layout/activity_discovery.xml | 63 +- app/src/main/res/values/strings.xml | 4 + app/src/main/res/xml/preferences.xml | 6 + build.gradle | 1 - 8 files changed, 517 insertions(+), 276 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 63a9964b3..d455a9395 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,13 +16,13 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } - compileSdkVersion 28 - buildToolsVersion '29.0.2' + compileSdkVersion 29 + buildToolsVersion '29.0.3' defaultConfig { applicationId "nodomain.freeyourgadget.gadgetbridge" minSdkVersion 19 - targetSdkVersion 28 + targetSdkVersion 29 // Note: always bump BOTH versionCode and versionName! versionName "0.46.0" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 648c2b345..91cea8047 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,12 +1,21 @@ + + + + + + + + @@ -16,19 +25,29 @@ + - - + + + - + + + + + + + @@ -38,6 +57,9 @@ + - + + @@ -302,6 +325,7 @@ + @@ -346,13 +370,21 @@ + + android:exported="false" + android:permission="android.permission.BLUETOOTH,android.permission.BLUETOOTH_ADMIN"> + + + + + + @@ -370,7 +402,7 @@ + android:exported="false" /> + + @@ -525,5 +561,4 @@ android:name=".devices.qhybrid.ImageEditActivity" android:exported="true" /> - \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java index 3d0e9f82c..fd29f6385 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java @@ -31,19 +31,25 @@ import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; +import android.companion.AssociationRequest; +import android.companion.BluetoothDeviceFilter; +import android.companion.CompanionDeviceManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.location.LocationManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.ParcelUuid; import android.os.Parcelable; +import android.provider.Settings; import android.view.View; import android.widget.AdapterView; import android.widget.Button; @@ -52,6 +58,7 @@ import android.widget.ProgressBar; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; @@ -73,26 +80,51 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast; public class DiscoveryActivity extends AbstractGBActivity implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener { private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class); - private static final long SCAN_DURATION = 60000; // 60s + private static final long SCAN_DURATION = 30000; // 30s + private static final int REQUEST_CODE = 1; + private ScanCallback newBLEScanCallback = null; - private ScanCallback newLeScanCallback = null; - - // Disabled for testing, it seems worse for a few people - private boolean disableNewBLEScanning = false; + /** Use old BLE scanning **/ + private boolean oldBleScanning = false; + /** If already bonded devices are to be ignored when scanning */ + private boolean ignoreBonded = true; + /** If new CompanionDevice-type pairing is enabled on newer Androids **/ + private boolean enableCompanionDevicePairing = false; private final Handler handler = new Handler(); + private ProgressBar bluetoothProgress; + private ProgressBar bluetoothLEProgress; + private final Runnable stopRunnable = new Runnable() { + @Override + public void run() { + if (isScanning == Scanning.SCANNING_BT_NEXT_BLE) { + // Start the next scan in the series + stopDiscovery(); + startDiscovery(Scanning.SCANNING_BLE); + } else { + stopDiscovery(); + } + } + }; + private DeviceCandidateAdapter deviceCandidateAdapter; private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (Objects.requireNonNull(intent.getAction())) { case BluetoothAdapter.ACTION_DISCOVERY_STARTED: - if (isScanning != Scanning.SCANNING_BTLE && isScanning != Scanning.SCANNING_NEW_BTLE) { - discoveryStarted(Scanning.SCANNING_BT); + if (isScanning != Scanning.SCANNING_BLE) { + if (isScanning != Scanning.SCANNING_BT_NEXT_BLE) { + isScanning = Scanning.SCANNING_BT; + } + startButton.setText(getString(R.string.discovery_stop_scanning)); } break; case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: @@ -100,13 +132,10 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView @Override public void run() { // continue with LE scan, if available - if (isScanning == Scanning.SCANNING_BT) { + if (isScanning == Scanning.SCANNING_BT || isScanning == Scanning.SCANNING_BT_NEXT_BLE) { checkAndRequestLocationPermission(); - if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) { - startDiscovery(Scanning.SCANNING_NEW_BTLE); - } else { - startDiscovery(Scanning.SCANNING_BTLE); - } + stopDiscovery(); + startDiscovery(Scanning.SCANNING_BLE); } else { discoveryFinished(); } @@ -145,16 +174,16 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView }; private void connectAndFinish(GBDevice device) { - GB.toast(DiscoveryActivity.this, getString(R.string.discovery_trying_to_connect_to, device.getName()), Toast.LENGTH_SHORT, GB.INFO); + toast(DiscoveryActivity.this, getString(R.string.discovery_trying_to_connect_to, device.getName()), Toast.LENGTH_SHORT, GB.INFO); GBApplication.deviceService().connect(device, true); finish(); } private void createBond(final GBDeviceCandidate deviceCandidate, int bondingStyle) { if (bondingStyle == DeviceCoordinator.BONDING_STYLE_NONE) { + // Do nothing return; - } - if (bondingStyle == DeviceCoordinator.BONDING_STYLE_ASK) { + } else if (bondingStyle == DeviceCoordinator.BONDING_STYLE_ASK) { new AlertDialog.Builder(this) .setCancelable(true) .setTitle(DiscoveryActivity.this.getString(R.string.discovery_pair_title, deviceCandidate.getName())) @@ -176,25 +205,20 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } else { doCreatePair(deviceCandidate); } + LOG.debug("Bonding initiated"); } private void doCreatePair(GBDeviceCandidate deviceCandidate) { - GB.toast(DiscoveryActivity.this, getString(R.string.discovery_attempting_to_pair, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.INFO); - if (deviceCandidate.getDevice().createBond()) { - // async, wait for bonding event to finish this activity - LOG.info("Bonding in progress..."); - bondingDevice = deviceCandidate; + toast(DiscoveryActivity.this, getString(R.string.discovery_attempting_to_pair, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.INFO); + if (enableCompanionDevicePairing) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + companionDevicePair(deviceCandidate); + } } else { - GB.toast(DiscoveryActivity.this, getString(R.string.discovery_bonding_failed_immediately, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.ERROR); + deviceBond(deviceCandidate); } } - private void handleDeviceBonded() { - GB.toast(DiscoveryActivity.this, getString(R.string.discovery_successfully_bonded, bondingDevice.getName()), Toast.LENGTH_SHORT, GB.INFO); - GBDevice device = DeviceHelper.getInstance().toSupportedDevice(bondingDevice); - connectAndFinish(device); - } - private final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { @@ -203,38 +227,14 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } }; - - // why use a method to get callback? - // because this callback need API >= 21 - // we cant add @TARGETAPI("Lollipop") at class header - // so use a method with SDK check to return this callback - private ScanCallback getScanCallback() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - newLeScanCallback = new ScanCallback() { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public void onScanResult(int callbackType, ScanResult result) { - super.onScanResult(callbackType, result); - try { - ScanRecord scanRecord = result.getScanRecord(); - ParcelUuid[] uuids = null; - if (scanRecord != null) { - //logMessageContent(scanRecord.getBytes()); - List serviceUuids = scanRecord.getServiceUuids(); - if (serviceUuids != null) { - uuids = serviceUuids.toArray(new ParcelUuid[0]); - } - } - LOG.warn(result.getDevice().getName() + ": " + - ((scanRecord != null) ? scanRecord.getBytes().length : -1)); - handleDeviceFound(result.getDevice(), (short) result.getRssi(), uuids); - } catch (NullPointerException e) { - LOG.warn("Error handling scan result", e); - } - } - }; + private void deviceBond(GBDeviceCandidate deviceCandidate) { + if (deviceCandidate.getDevice().createBond()) { + // Async, wait for bonding event to finish this activity + LOG.info("Bonding in progress..."); + bondingDevice = deviceCandidate; + } else { + toast(DiscoveryActivity.this, getString(R.string.discovery_bonding_failed_immediately, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.ERROR); } - return newLeScanCallback; } public void logMessageContent(byte[] value) { @@ -243,40 +243,121 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } } - private final Runnable stopRunnable = new Runnable() { - @Override - public void run() { - stopDiscovery(); - } - }; + @RequiresApi(Build.VERSION_CODES.O) + private void companionDevicePair(final GBDeviceCandidate deviceCandidate) { + CompanionDeviceManager deviceManager = getSystemService(CompanionDeviceManager.class); + + BluetoothDeviceFilter deviceFilter = new BluetoothDeviceFilter.Builder() + .setAddress(deviceCandidate.getMacAddress()) + .build(); + + AssociationRequest pairingRequest = new AssociationRequest.Builder() + .addDeviceFilter(deviceFilter) + .setSingleDevice(true) + .build(); + + deviceManager.associate(pairingRequest, + new CompanionDeviceManager.Callback() { + @Override + public void onFailure(CharSequence error) { + toast(DiscoveryActivity.this, getString(R.string.discovery_bonding_failed_immediately, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.ERROR); + } + + @Override + public void onDeviceFound(IntentSender chooserLauncher) { + try { + startIntentSenderForResult(chooserLauncher, + REQUEST_CODE, null, 0, 0, 0); + } catch (IntentSender.SendIntentException e) { + e.printStackTrace(); + } + } + }, + null + ); + } + + @RequiresApi(Build.VERSION_CODES.O) + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE && + resultCode == Activity.RESULT_OK) { + + BluetoothDevice deviceToPair = + data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE); + + if (deviceToPair != null) { + deviceBond(new GBDeviceCandidate(deviceToPair, (short) 0, null)); + handleDeviceBonded(); + } + } + } + + private void handleDeviceBonded() { + if (bondingDevice == null) { + return; + } + + toast(DiscoveryActivity.this, getString(R.string.discovery_successfully_bonded, bondingDevice.getName()), Toast.LENGTH_SHORT, GB.INFO); + GBDevice device = DeviceHelper.getInstance().toSupportedDevice(bondingDevice); + connectAndFinish(device); + } - private ProgressBar progressView; private BluetoothAdapter adapter; private final ArrayList deviceCandidates = new ArrayList<>(); - private DeviceCandidateAdapter cadidateListAdapter; + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private ScanCallback getScanCallback() { + if (newBLEScanCallback != null) { + return newBLEScanCallback; + } + + newBLEScanCallback = new ScanCallback() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void onScanResult(int callbackType, ScanResult result) { + super.onScanResult(callbackType, result); + try { + ScanRecord scanRecord = result.getScanRecord(); + ParcelUuid[] uuids = null; + if (scanRecord != null) { + //logMessageContent(scanRecord.getBytes()); + List serviceUuids = scanRecord.getServiceUuids(); + if (serviceUuids != null) { + uuids = serviceUuids.toArray(new ParcelUuid[0]); + } + } + LOG.warn(result.getDevice().getName() + ": " + + ((scanRecord != null) ? scanRecord.getBytes().length : -1)); + handleDeviceFound(result.getDevice(), (short) result.getRssi(), uuids); + } catch (NullPointerException e) { + LOG.warn("Error handling scan result", e); + } + } + }; + + return newBLEScanCallback; + } private Button startButton; private Scanning isScanning = Scanning.SCANNING_OFF; private GBDeviceCandidate bondingDevice; - private boolean ignoreBonded = true; - - - private enum Scanning { - SCANNING_BT, - SCANNING_BTLE, - SCANNING_NEW_BTLE, - SCANNING_OFF - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ignoreBonded = GBApplication.getPrefs().getBoolean("ignore_bonded_devices", true); + Prefs prefs = GBApplication.getPrefs(); + ignoreBonded = prefs.getBoolean("ignore_bonded_devices", true); - disableNewBLEScanning = GBApplication.getPrefs().getBoolean("disable_new_ble_scanning", false); - if (disableNewBLEScanning) { - LOG.info("new BLE scanning disabled via settings, using old method"); + oldBleScanning = prefs.getBoolean("disable_new_ble_scanning", false); + if (oldBleScanning) { + LOG.info("New BLE scanning disabled via settings, using old method"); + } + + enableCompanionDevicePairing = prefs.getBoolean("enable_companiondevice_pairing", false); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + enableCompanionDevicePairing = false; // No support below 26 } setContentView(R.layout.activity_discovery); @@ -288,14 +369,19 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } }); - progressView = findViewById(R.id.discovery_progressbar); - progressView.setProgress(0); - progressView.setIndeterminate(true); - progressView.setVisibility(View.GONE); - ListView deviceCandidatesView = findViewById(R.id.discovery_deviceCandidatesView); + bluetoothProgress = findViewById(R.id.discovery_progressbar); + bluetoothProgress.setProgress(0); + bluetoothProgress.setIndeterminate(true); + bluetoothProgress.setVisibility(View.GONE); + ListView deviceCandidatesView = findViewById(R.id.discovery_device_candidates_list); - cadidateListAdapter = new DeviceCandidateAdapter(this, deviceCandidates); - deviceCandidatesView.setAdapter(cadidateListAdapter); + bluetoothLEProgress = findViewById(R.id.discovery_ble_progressbar); + bluetoothLEProgress.setProgress(0); + bluetoothLEProgress.setIndeterminate(true); + bluetoothLEProgress.setVisibility(View.GONE); + + deviceCandidateAdapter = new DeviceCandidateAdapter(this, deviceCandidates); + deviceCandidatesView.setAdapter(deviceCandidateAdapter); deviceCandidatesView.setOnItemClickListener(this); deviceCandidatesView.setOnItemLongClickListener(this); @@ -310,11 +396,42 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView registerReceiver(bluetoothReceiver, bluetoothIntents); if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - GB.toast(DiscoveryActivity.this, getString(R.string.error_no_location_access), Toast.LENGTH_SHORT, GB.ERROR); + toast(DiscoveryActivity.this, getString(R.string.error_no_location_access), Toast.LENGTH_SHORT, GB.ERROR); LOG.error("No permission to access coarse location!"); checkAndRequestLocationPermission(); + + // We can't be sure location was granted, cancel scan start and wait for user action + return; + } + + LocationManager locationManager = (LocationManager) DiscoveryActivity.this.getSystemService(Context.LOCATION_SERVICE); + try { + if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + // Do nothing + LOG.debug("Some location provider is enabled, assuming location is enabled"); + } else { + toast(DiscoveryActivity.this, getString(R.string.require_location_provider), Toast.LENGTH_LONG, GB.ERROR); + DiscoveryActivity.this.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + // We can't be sure location was enabled, cancel scan start and wait for new user action + return; + } + } catch (Exception ex) { + LOG.error("Exception when checking location status: ", ex); + } + + startDiscovery(Scanning.SCANNING_BT_NEXT_BLE); + } + + public void onStartButtonClick(View button) { + LOG.debug("Start Button clicked"); + if (isScanning()) { + stopDiscovery(); } else { - startDiscovery(); + if (GB.supportsBluetoothLE()) { + startDiscovery(Scanning.SCANNING_BT_NEXT_BLE); + } else { + startDiscovery(Scanning.SCANNING_BT); + } } } @@ -336,28 +453,21 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } } - public void onStartButtonClick(View button) { - LOG.debug("Start Button clicked"); - if (isScanning()) { - stopDiscovery(); - } else { - startDiscovery(); - } - } - @Override protected void onDestroy() { try { unregisterReceiver(bluetoothReceiver); } catch (IllegalArgumentException e) { - LOG.warn("Tried to unregister Bluetooth Receiver that wasn't registered."); + LOG.warn("Tried to unregister Bluetooth Receiver that wasn't registered"); + LOG.warn(e.getMessage()); } + super.onDestroy(); } private void handleDeviceFound(BluetoothDevice device, short rssi) { if (device.getName() != null) { - if (handleDeviceFound(device,rssi, null)) { + if (handleDeviceFound(device, rssi, null)) { LOG.info("found supported device " + device.getName() + " without scanning services, skipping service scan."); return; } @@ -372,7 +482,6 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView handleDeviceFound(device, rssi, uuids); } - private boolean handleDeviceFound(BluetoothDevice device, short rssi, ParcelUuid[] uuids) { LOG.debug("found device: " + device.getName() + ", " + device.getAddress()); if (LOG.isDebugEnabled()) { @@ -398,46 +507,55 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } else { deviceCandidates.add(candidate); } - cadidateListAdapter.notifyDataSetChanged(); + deviceCandidateAdapter.notifyDataSetChanged(); return true; } return false; } - /** - * Pre: bluetooth is available, enabled and scanning is off. - * Post: BT is discovering - */ - private void startDiscovery() { + private void startDiscovery(Scanning what) { if (isScanning()) { LOG.warn("Not starting discovery, because already scanning."); return; } - startDiscovery(Scanning.SCANNING_BT); - } - private void startDiscovery(Scanning what) { LOG.info("Starting discovery: " + what); - discoveryStarted(what); // just to make sure - if (ensureBluetoothReady()) { - if (what == Scanning.SCANNING_BT) { - startBTDiscovery(); - } else if (what == Scanning.SCANNING_BTLE) { - if (GB.supportsBluetoothLE()) { + startButton.setText(getString(R.string.discovery_stop_scanning)); + if (ensureBluetoothReady() && isScanning == Scanning.SCANNING_OFF) { + if (what == Scanning.SCANNING_BT || what == Scanning.SCANNING_BT_NEXT_BLE) { + startBTDiscovery(what); + } else if (what == Scanning.SCANNING_BLE && GB.supportsBluetoothLE()) { + if (oldBleScanning) { startBTLEDiscovery(); } else { - discoveryFinished(); - } - } else if (what == Scanning.SCANNING_NEW_BTLE) { - if (GB.supportsBluetoothLE()) { startNEWBTLEDiscovery(); - } else { - discoveryFinished(); } + } else { + discoveryFinished(); + toast(DiscoveryActivity.this, getString(R.string.discovery_enable_bluetooth), Toast.LENGTH_SHORT, GB.ERROR); } } else { discoveryFinished(); - GB.toast(DiscoveryActivity.this, getString(R.string.discovery_enable_bluetooth), Toast.LENGTH_SHORT, GB.ERROR); + toast(DiscoveryActivity.this, getString(R.string.discovery_enable_bluetooth), Toast.LENGTH_SHORT, GB.ERROR); + } + } + + private void stopDiscovery() { + LOG.info("Stopping discovery"); + if (isScanning()) { + Scanning wasScanning = isScanning; + if (wasScanning == Scanning.SCANNING_BT || wasScanning == Scanning.SCANNING_BT_NEXT_BLE) { + stopBTDiscovery(); + } else if (wasScanning == Scanning.SCANNING_BLE) { + if (oldBleScanning) { + stopOldBLEDiscovery(); + } else { + stopBLEDiscovery(); + } + } + + discoveryFinished(); + handler.removeMessages(0, stopRunnable); } } @@ -445,57 +563,110 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView return isScanning != Scanning.SCANNING_OFF; } - private void stopDiscovery() { - LOG.info("Stopping discovery"); - if (isScanning()) { - Scanning wasScanning = isScanning; - // unfortunately, we don't always get a call back when stopping the scan, so - // we do it manually; BEFORE stopping the scan! - discoveryFinished(); + private void startBTLEDiscovery() { + LOG.info("Starting old BLE discovery"); + isScanning = Scanning.SCANNING_BLE; - if (wasScanning == Scanning.SCANNING_BT) { - stopBTDiscovery(); - } else if (wasScanning == Scanning.SCANNING_BTLE) { - stopBTLEDiscovery(); - } else if (wasScanning == Scanning.SCANNING_NEW_BTLE) { - stopNewBTLEDiscovery(); - } - handler.removeMessages(0, stopRunnable); + handler.removeMessages(0, stopRunnable); + handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION); + adapter.startLeScan(leScanCallback); + + bluetoothLEProgress.setVisibility(View.VISIBLE); + } + + private void stopOldBLEDiscovery() { + if (adapter != null) { + adapter.stopLeScan(leScanCallback); + + isScanning = Scanning.SCANNING_OFF; + bluetoothLEProgress.setVisibility(View.GONE); + LOG.info("Stopped old BLE discovery"); } } - private void stopBTLEDiscovery() { - if (adapter != null) - adapter.stopLeScan(leScanCallback); - } + // New BTLE Discovery use startScan (List filters, + // ScanSettings settings, + // ScanCallback callback) + // It's added on API21 + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void startNEWBTLEDiscovery() { + // Only use new API when user uses Lollipop+ device + LOG.info("Starting BLE discovery"); + isScanning = Scanning.SCANNING_BLE; - private void stopBTDiscovery() { - if (adapter != null) - adapter.cancelDiscovery(); + handler.removeMessages(0, stopRunnable); + handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION); + + // Filters being non-null would be a very good idea with background scan, but in this case, + // not really required. + adapter.getBluetoothLeScanner().startScan(null, getScanSettings(), getScanCallback()); + + bluetoothLEProgress.setVisibility(View.VISIBLE); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void stopNewBTLEDiscovery() { - if (adapter == null) + private void stopBLEDiscovery() { + if (adapter == null) { return; + } BluetoothLeScanner bluetoothLeScanner = adapter.getBluetoothLeScanner(); if (bluetoothLeScanner == null) { - LOG.warn("could not get BluetoothLeScanner()!"); + LOG.warn("Could not get BluetoothLeScanner()!"); return; } - if (newLeScanCallback == null) { + if (newBLEScanCallback == null) { LOG.warn("newLeScanCallback == null!"); return; } try { - bluetoothLeScanner.stopScan(newLeScanCallback); + bluetoothLeScanner.stopScan(newBLEScanCallback); } catch (NullPointerException e) { LOG.warn("Internal NullPointerException when stopping the scan!"); return; } + + isScanning = Scanning.SCANNING_OFF; + bluetoothLEProgress.setVisibility(View.GONE); + LOG.debug("Stopped BLE discovery"); } + /** + * Starts a regular Bluetooth scan + * + * @param what The scan type, only either SCANNING_BT or SCANNING_BT_NEXT_BLE! + */ + private void startBTDiscovery(Scanning what) { + LOG.info("Starting BT discovery"); + isScanning = what; + + handler.removeMessages(0, stopRunnable); + handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION); + if (adapter.startDiscovery()) { + LOG.error("Discovery starting failed"); + } + + bluetoothProgress.setVisibility(View.VISIBLE); + } + + private void stopBTDiscovery() { + if (adapter != null) { + adapter.cancelDiscovery(); + + bluetoothProgress.setVisibility(View.GONE); + isScanning = Scanning.SCANNING_OFF; + LOG.info("Stopped BT discovery"); + } + } + + private void discoveryFinished() { + if (isScanning != Scanning.SCANNING_OFF) { + LOG.warn("Scan was not properly: " + String.valueOf(isScanning)); + } + startButton.setText(getString(R.string.discovery_start_scanning)); + } + + private void bluetoothStateChanged(int newState) { discoveryFinished(); if (newState == BluetoothAdapter.STATE_ON) { @@ -507,40 +678,16 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } } - private void discoveryFinished() { - isScanning = Scanning.SCANNING_OFF; - progressView.setVisibility(View.GONE); - startButton.setText(getString(R.string.discovery_start_scanning)); - } - - private void discoveryStarted(Scanning what) { - isScanning = what; - progressView.setVisibility(View.VISIBLE); - startButton.setText(getString(R.string.discovery_stop_scanning)); - } - - private boolean ensureBluetoothReady() { - boolean available = checkBluetoothAvailable(); - startButton.setEnabled(available); - if (available) { - adapter.cancelDiscovery(); - // must not return the result of cancelDiscovery() - // appears to return false when currently not scanning - return true; - } - return false; - } - private boolean checkBluetoothAvailable() { BluetoothManager bluetoothService = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE); if (bluetoothService == null) { - LOG.warn("No bluetooth available"); + LOG.warn("No bluetooth service available"); this.adapter = null; return false; } BluetoothAdapter adapter = bluetoothService.getAdapter(); if (adapter == null) { - LOG.warn("No bluetooth available"); + LOG.warn("No bluetooth adapter available"); this.adapter = null; return false; } @@ -555,17 +702,40 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView return true; } - // New BTLE Discovery use startScan (List filters, - // ScanSettings settings, - // ScanCallback callback) - // It's added on API21 + private boolean ensureBluetoothReady() { + boolean available = checkBluetoothAvailable(); + startButton.setEnabled(available); + if (available) { + adapter.cancelDiscovery(); + // must not return the result of cancelDiscovery() + // appears to return false when currently not scanning + return true; + } + return false; + } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void startNEWBTLEDiscovery() { - // Only use new API when user uses Lollipop+ device - LOG.info("Start New BTLE Discovery"); - handler.removeMessages(0, stopRunnable); - handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION); - adapter.getBluetoothLeScanner().startScan(getScanFilters(), getScanSettings(), getScanCallback()); + private ScanSettings getScanSettings() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return new ScanSettings.Builder() + .setCallbackType(android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setScanMode(android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY) + .setMatchMode(android.bluetooth.le.ScanSettings.MATCH_MODE_AGGRESSIVE) + .setPhy(android.bluetooth.le.ScanSettings.PHY_LE_ALL_SUPPORTED) + .setNumOfMatches(android.bluetooth.le.ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) + .build(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return new ScanSettings.Builder() + .setCallbackType(android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setScanMode(android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY) + .setMatchMode(android.bluetooth.le.ScanSettings.MATCH_MODE_AGGRESSIVE) + .setNumOfMatches(android.bluetooth.le.ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) + .build(); + } else { + return new ScanSettings.Builder() + .setScanMode(android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY) + .build(); + } } private List getScanFilters() { @@ -576,32 +746,10 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView return allFilters; } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private ScanSettings getScanSettings() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return new ScanSettings.Builder() - .setScanMode(android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY) - .setMatchMode(android.bluetooth.le.ScanSettings.MATCH_MODE_STICKY) - .build(); - } else { - return new ScanSettings.Builder() - .setScanMode(android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY) - .build(); - } - } - - private void startBTLEDiscovery() { - LOG.info("Starting BTLE Discovery"); - handler.removeMessages(0, stopRunnable); - handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION); - adapter.startLeScan(leScanCallback); - } - - private void startBTDiscovery() { - LOG.info("Starting BT Discovery"); - handler.removeMessages(0, stopRunnable); - handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION); - adapter.startDiscovery(); + private Message getPostMessage(Runnable runnable) { + Message message = Message.obtain(handler, runnable); + message.obj = runnable; + return message; } private void checkAndRequestLocationPermission() { @@ -610,33 +758,6 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } } - private Message getPostMessage(Runnable runnable) { - Message m = Message.obtain(handler, runnable); - m.obj = runnable; - return m; - } - - @Override - public boolean onItemLongClick(AdapterView adapterView, View view, int position, long id) { - GBDeviceCandidate deviceCandidate = deviceCandidates.get(position); - if (deviceCandidate == null) { - LOG.error("Device candidate clicked, but item not found"); - return true; - } - - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate); - GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate); - if (coordinator.getSupportedDeviceSpecificSettings(device) == null) { - return true; - } - - Intent startIntent; - startIntent = new Intent(this, DeviceSettingsActivity.class); - startIntent.putExtra(GBDevice.EXTRA_DEVICE, device); - startActivity(startIntent); - return true; - } - @Override public void onItemClick(AdapterView parent, View view, int position, long id) { GBDeviceCandidate deviceCandidate = deviceCandidates.get(position); @@ -654,7 +775,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView String authKey = sharedPrefs.getString("authkey", null); if (authKey == null || authKey.isEmpty() || authKey.getBytes().length < 34 || !authKey.substring(0, 2).equals("0x")) { - GB.toast(DiscoveryActivity.this, getString(R.string.discovery_need_to_enter_authkey), Toast.LENGTH_LONG, GB.WARN); + toast(DiscoveryActivity.this, getString(R.string.discovery_need_to_enter_authkey), Toast.LENGTH_LONG, GB.WARN); return; } } @@ -680,14 +801,16 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView createBond(deviceCandidate, bondingStyle); break; } - case BluetoothDevice.BOND_BONDING: + case BluetoothDevice.BOND_BONDING: { // async, wait for bonding event to finish this activity bondingDevice = deviceCandidate; break; - case BluetoothDevice.BOND_BONDED: + } + case BluetoothDevice.BOND_BONDED: { bondingDevice = deviceCandidate; handleDeviceBonded(); break; + } } } catch (Exception e) { LOG.error("Error pairing device: " + deviceCandidate.getMacAddress()); @@ -695,13 +818,56 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView } } + @Override + public boolean onItemLongClick(AdapterView adapterView, View view, int position, long id) { + GBDeviceCandidate deviceCandidate = deviceCandidates.get(position); + if (deviceCandidate == null) { + LOG.error("Device candidate clicked, but item not found"); + return true; + } + + DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate); + GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate); + if (coordinator.getSupportedDeviceSpecificSettings(device) == null) { + return true; + } + + Intent startIntent; + startIntent = new Intent(this, DeviceSettingsActivity.class); + startIntent.putExtra(GBDevice.EXTRA_DEVICE, device); + startActivity(startIntent); + return true; + } + @Override protected void onPause() { super.onPause(); stopBTDiscovery(); - stopBTLEDiscovery(); - if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) { - stopNewBTLEDiscovery(); + if (oldBleScanning) { + stopOldBLEDiscovery(); + } else { + if (GBApplication.isRunningLollipopOrLater()) { + stopBLEDiscovery(); + } } } + + private enum Scanning { + /** + * Regular Bluetooth scan + */ + SCANNING_BT, + /** + * Regular Bluetooth scan but when ends, start BLE scan + */ + SCANNING_BT_NEXT_BLE, + /** + * Regular BLE scan + */ + SCANNING_BLE, + /** + * Scanning has ended or hasn't been started + */ + SCANNING_OFF + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java index 6a8ba86e1..801f66abd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java @@ -48,7 +48,7 @@ public class BluetoothStateChangeReceiver extends BroadcastReceiver { return; } - LOG.info("Bluetooth turned on => connecting..."); + LOG.info("Bluetooth turned on (ACTION_STATE_CHANGED) => connecting..."); GBApplication.deviceService().connect(); } else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) { LOG.info("Bluetooth turned off => disconnecting..."); diff --git a/app/src/main/res/layout/activity_discovery.xml b/app/src/main/res/layout/activity_discovery.xml index 485e5d6bd..331bc9e2e 100644 --- a/app/src/main/res/layout/activity_discovery.xml +++ b/app/src/main/res/layout/activity_discovery.xml @@ -1,4 +1,4 @@ -