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 @@
-
-
-
+ android:layout_gravity="center_horizontal"
+ android:text="@string/discovery_start_scanning" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -54,9 +87,7 @@
android:text="@string/discovery_connected_devices_hint"
android:id="@+id/discovery_hint"
android:textColor="@color/secondarytext"
- android:gravity="bottom"
android:textIsSelectable="true" />
-
-
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 233901632..24f5241d8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -886,6 +886,9 @@
Many thanks to all unlisted contributors for contributing code, support, ideas, motivation, bug reports, money… ✊
All these permissions are required and instability might occur if not granted
CAUTION: Error when checking version information! You should not continue! Saw version name \"%s\"
+ Location must be enabled
+ CompanionDevice Pairing
+ Enables the new CompanionDevice API support (above Android 8 only!) which theoretically increases reliability, requires re-pairing to have an effect
Failed to start background service
Starting the background service failed because…
ALREADY BONDED
@@ -898,6 +901,7 @@
Error retrieving devices from database
Ignore bonded devices
Enabling this option will ignore devices that have been bonded/paired already when scanning
+
- %d hour
- %d hours
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 1e77c4b02..7573f8eec 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -650,6 +650,12 @@
android:key="disable_new_ble_scanning"
android:summary="@string/pref_summary_disable_new_ble_scanning"
android:title="@string/pref_disable_new_ble_scanning" />
+
diff --git a/build.gradle b/build.gradle
index 5fa35a0b0..b4e031e32 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,6 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
- classpath "gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:2.0.0"
classpath 'gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:2.0.0'
// NOTE: Do not place your application dependencies here; they belong