mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 09:01:55 +01:00
Rebase
This commit is contained in:
parent
fb04e0b48f
commit
340bbec8ee
@ -343,6 +343,13 @@
|
||||
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".externalevents.BluetoothScanCallbackReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="nodomain.freeyourgadget.gadgetbridge.blescancallback" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".service.receivers.GBMusicControlReceiver"
|
||||
android:exported="false">
|
||||
|
@ -0,0 +1,80 @@
|
||||
/* Copyright (C) 2019 Andreas Böhler
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.le.BluetoothLeScanner;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
public class BluetoothScanCallbackReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BluetoothScanCallbackReceiver.class);
|
||||
private String mSeenScanCallbackUUID = "";
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if(!action.equals("nodomain.freeyourgadget.gadgetbridge.blescancallback") || !intent.hasExtra("address") || !intent.hasExtra("uuid")) {
|
||||
return;
|
||||
}
|
||||
|
||||
String wantedAddress = intent.getExtras().getString("address");
|
||||
String uuid = intent.getExtras().getString("uuid");
|
||||
|
||||
int bleCallbackType = intent.getIntExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, -1);
|
||||
if(bleCallbackType != -1) {
|
||||
//LOG.debug("Passive background scan callback type: " + bleCallbackType);
|
||||
ArrayList<ScanResult> scanResults = intent.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT);
|
||||
for(ScanResult result: scanResults) {
|
||||
BluetoothDevice device = result.getDevice();
|
||||
if(device.getAddress().equals(wantedAddress) && !mSeenScanCallbackUUID.equals(uuid)) {
|
||||
mSeenScanCallbackUUID = uuid;
|
||||
LOG.info("ScanCallbackReceiver has found " + device.getAddress() + "(" + device.getName() + ")");
|
||||
BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner().stopScan(getScanCallbackIntent(GBApplication.getContext(), wantedAddress, uuid));
|
||||
GBApplication.deviceService().connect(DeviceHelper.getInstance().toSupportedDevice(device));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public static PendingIntent getScanCallbackIntent(Context context, String address, String uuid) {
|
||||
Intent intent = new Intent(context, BluetoothScanCallbackReceiver.class);
|
||||
intent.setAction("nodomain.freeyourgadget.gadgetbridge.blescancallback");
|
||||
intent.putExtra("address", address);
|
||||
intent.putExtra("uuid", uuid);
|
||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
}
|
@ -34,11 +34,13 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
|
||||
/**
|
||||
* Abstract base class for all devices connected through Bluetooth Low Energy (LE) aka
|
||||
@ -74,6 +76,14 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
if (mQueue == null) {
|
||||
mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, this, getContext(), mSupportedServerServices);
|
||||
mQueue.setAutoReconnect(getAutoReconnect());
|
||||
GBPrefs prefs = GBApplication.getGBPrefs();
|
||||
boolean autoReconnectScan = GBPrefs.AUTO_RECONNECT_SCAN_DEFAULT;
|
||||
if (prefs != null) {
|
||||
autoReconnectScan = prefs.getAutoReconnectScan();
|
||||
}
|
||||
// Override the user preference if required by the device
|
||||
autoReconnectScan = autoReconnectScan || useBleScannerForReconnect();
|
||||
mQueue.setBleScannerForReconnect(autoReconnectScan);
|
||||
}
|
||||
return mQueue.connect();
|
||||
}
|
||||
@ -385,4 +395,8 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
public boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean useBleScannerForReconnect() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
@ -28,7 +30,13 @@ import android.bluetooth.BluetoothGattServerCallback;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.le.BluetoothLeScanner;
|
||||
import android.bluetooth.le.ScanCallback;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.bluetooth.le.ScanSettings;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
@ -39,6 +47,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@ -46,6 +55,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import androidx.annotation.Nullable;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothScanCallbackReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||
@ -69,6 +79,8 @@ public final class BtLEQueue {
|
||||
private volatile boolean mAbortTransaction;
|
||||
private volatile boolean mAbortServerTransaction;
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
private final Context mContext;
|
||||
private CountDownLatch mWaitForActionResultLatch;
|
||||
private CountDownLatch mWaitForServerActionResultLatch;
|
||||
@ -76,7 +88,27 @@ public final class BtLEQueue {
|
||||
private BluetoothGattCharacteristic mWaitCharacteristic;
|
||||
private final InternalGattCallback internalGattCallback;
|
||||
private final InternalGattServerCallback internalGattServerCallback;
|
||||
private boolean mAutoReconnect;
|
||||
private boolean mAutoReconnect = false;
|
||||
|
||||
private BluetoothLeScanner mBluetoothScanner;
|
||||
private boolean mUseBleScannerForReconnect = false;
|
||||
private PendingIntent mScanCallbackIntent = null;
|
||||
|
||||
private Runnable mRestartRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Restarting background scan due to Android N limitations...");
|
||||
startBleBackgroundScan();
|
||||
}
|
||||
};
|
||||
|
||||
private Runnable mReduceBleScanIntervalRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Restarting BLE background scan with lower priority...");
|
||||
startBleBackgroundScan(false);
|
||||
}
|
||||
};
|
||||
|
||||
private Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") {
|
||||
|
||||
@ -200,6 +232,10 @@ public final class BtLEQueue {
|
||||
mAutoReconnect = enable;
|
||||
}
|
||||
|
||||
public void setBleScannerForReconnect(boolean enable) {
|
||||
mUseBleScannerForReconnect = enable;
|
||||
}
|
||||
|
||||
protected boolean isConnected() {
|
||||
return mGbDevice.isConnected();
|
||||
}
|
||||
@ -269,6 +305,23 @@ public final class BtLEQueue {
|
||||
public void disconnect() {
|
||||
synchronized (mGattMonitor) {
|
||||
LOG.debug("disconnect()");
|
||||
|
||||
BluetoothGattServer gattServer = mBluetoothGattServer;
|
||||
if (gattServer != null) {
|
||||
mBluetoothGattServer = null;
|
||||
BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (bluetoothManager == null) {
|
||||
LOG.error("Error getting bluetoothManager");
|
||||
} else {
|
||||
List<BluetoothDevice> devices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER);
|
||||
for(BluetoothDevice device : devices) {
|
||||
LOG.debug("Disconnecting device: " + device.getAddress());
|
||||
gattServer.cancelConnection(device);
|
||||
}
|
||||
}
|
||||
gattServer.clearServices();
|
||||
gattServer.close();
|
||||
}
|
||||
BluetoothGatt gatt = mBluetoothGatt;
|
||||
if (gatt != null) {
|
||||
mBluetoothGatt = null;
|
||||
@ -277,12 +330,6 @@ public final class BtLEQueue {
|
||||
gatt.close();
|
||||
setDeviceConnectionState(State.NOT_CONNECTED);
|
||||
}
|
||||
BluetoothGattServer gattServer = mBluetoothGattServer;
|
||||
if (gattServer != null) {
|
||||
mBluetoothGattServer = null;
|
||||
gattServer.clearServices();
|
||||
gattServer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,8 +346,6 @@ public final class BtLEQueue {
|
||||
mWaitForServerActionResultLatch.countDown();
|
||||
}
|
||||
|
||||
boolean wasInitialized = mGbDevice.isInitialized();
|
||||
|
||||
setDeviceConnectionState(State.NOT_CONNECTED);
|
||||
|
||||
// either we've been disconnected because the device is out of range
|
||||
@ -310,7 +355,7 @@ public final class BtLEQueue {
|
||||
// reconnecting automatically, so we try to fix this by re-creating mBluetoothGatt.
|
||||
// Not sure if this actually works without re-initializing the device...
|
||||
if (mBluetoothGatt != null) {
|
||||
if (!wasInitialized || !maybeReconnect()) {
|
||||
if (!maybeReconnect()) {
|
||||
disconnect(); // ensure that we start over cleanly next time
|
||||
}
|
||||
}
|
||||
@ -323,16 +368,121 @@ public final class BtLEQueue {
|
||||
*/
|
||||
private boolean maybeReconnect() {
|
||||
if (mAutoReconnect && mBluetoothGatt != null) {
|
||||
LOG.info("Enabling automatic ble reconnect...");
|
||||
boolean result = mBluetoothGatt.connect();
|
||||
if (result) {
|
||||
setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
|
||||
if(!mUseBleScannerForReconnect) {
|
||||
LOG.info("Enabling automatic ble reconnect...");
|
||||
boolean result = mBluetoothGatt.connect();
|
||||
if (result) {
|
||||
setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
LOG.info("Enabling BLE background scan");
|
||||
disconnect(); // ensure that we start over cleanly next time
|
||||
startBleBackgroundScan();
|
||||
setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
PendingIntent getScanCallbackIntent(boolean newUuid) {
|
||||
if(newUuid || mScanCallbackIntent == null) {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
mScanCallbackIntent = BluetoothScanCallbackReceiver.getScanCallbackIntent(mContext, mGbDevice.getAddress(), uuid);
|
||||
}
|
||||
return mScanCallbackIntent;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void stopBleBackgroundScan() {
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
mHandler.removeCallbacks(mReduceBleScanIntervalRunnable);
|
||||
if(mBluetoothScanner != null) {
|
||||
mBluetoothScanner.stopScan(getScanCallbackIntent(false));
|
||||
}
|
||||
} else {
|
||||
mHandler.removeCallbacks(mRestartRunnable);
|
||||
if(mBluetoothScanner != null) {
|
||||
mBluetoothScanner.stopScan(mScanCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void startBleBackgroundScan() {
|
||||
startBleBackgroundScan(true);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void startBleBackgroundScan(boolean highPowerMode) {
|
||||
if(mBluetoothScanner == null)
|
||||
mBluetoothScanner = mBluetoothAdapter.getBluetoothLeScanner();
|
||||
|
||||
ScanSettings settings;
|
||||
if(highPowerMode) {
|
||||
settings = new ScanSettings.Builder()
|
||||
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||
.build();
|
||||
} else {
|
||||
settings = new ScanSettings.Builder()
|
||||
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
|
||||
.build();
|
||||
}
|
||||
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
LOG.info("Using Android O+ BLE scanner");
|
||||
List<ScanFilter> filters = Collections.singletonList(new ScanFilter.Builder().build());
|
||||
mBluetoothScanner.stopScan(getScanCallbackIntent(false));
|
||||
mBluetoothScanner.startScan(filters, settings, getScanCallbackIntent(true));
|
||||
// If high power mode is requested, we scan for 5 minutes
|
||||
// and then continue scanning with lower priority (scan mode balanced) in order
|
||||
// to conserve power.
|
||||
if(highPowerMode) {
|
||||
mHandler.postDelayed(mReduceBleScanIntervalRunnable, 5 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.info("Using Android L-N BLE scanner");
|
||||
List<ScanFilter> filters = Collections.singletonList(new ScanFilter.Builder().setDeviceAddress(mGbDevice.getAddress()).build()); mBluetoothScanner.stopScan(mScanCallback);
|
||||
mBluetoothScanner.startScan(filters, settings, mScanCallback);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
mHandler.postDelayed(mRestartRunnable, 25 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private ScanCallback mScanCallback = new ScanCallback() {
|
||||
@Override
|
||||
public void onScanResult(int callbackType, ScanResult result) {
|
||||
String deviceName = result.getDevice().getName();
|
||||
String deviceAddress = result.getDevice().getAddress();
|
||||
|
||||
LOG.info("Scanner: Found: " + deviceName + " " + deviceAddress);
|
||||
// The filter already filtered for our specific device, so it is enough to connect to it
|
||||
mBluetoothScanner.stopScan(mScanCallback);
|
||||
mHandler.removeCallbacks(mRestartRunnable);
|
||||
connect();
|
||||
setDeviceConnectionState(State.CONNECTING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchScanResults(List<ScanResult> results) {
|
||||
for (ScanResult sr : results) {
|
||||
LOG.info("ScanCallback.onBatchScanResults.each:" + sr.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScanFailed(int errorCode) {
|
||||
LOG.error("ScanCallback.onScanFailed:" + errorCode);
|
||||
}
|
||||
};
|
||||
|
||||
public void dispose() {
|
||||
if (mDisposed) {
|
||||
return;
|
||||
@ -340,6 +490,9 @@ public final class BtLEQueue {
|
||||
mDisposed = true;
|
||||
// try {
|
||||
disconnect();
|
||||
if(mUseBleScannerForReconnect) {
|
||||
stopBleBackgroundScan();
|
||||
}
|
||||
dispatchThread.interrupt();
|
||||
dispatchThread = null;
|
||||
// dispatchThread.join();
|
||||
|
@ -24,6 +24,7 @@ public class GBPrefs {
|
||||
public static final String PACKAGE_BLACKLIST = "package_blacklist";
|
||||
public static final String PACKAGE_PEBBLEMSG_BLACKLIST = "package_pebblemsg_blacklist";
|
||||
public static final String CALENDAR_BLACKLIST = "calendar_blacklist";
|
||||
public static final String AUTO_RECONNECT_SCAN = "general_autoreconnectscan";
|
||||
public static final String AUTO_RECONNECT = "general_autocreconnect";
|
||||
private static final String AUTO_START = "general_autostartonboot";
|
||||
public static final String AUTO_EXPORT_ENABLED = "auto_export_enabled";
|
||||
@ -35,6 +36,7 @@ public class GBPrefs {
|
||||
public static final String RTL_SUPPORT = "rtl";
|
||||
public static final String RTL_CONTEXTUAL_ARABIC = "contextualArabic";
|
||||
public static boolean AUTO_RECONNECT_DEFAULT = true;
|
||||
public static boolean AUTO_RECONNECT_SCAN_DEFAULT = false;
|
||||
|
||||
public static final String USER_NAME = "mi_user_alias";
|
||||
public static final String USER_NAME_DEFAULT = "gadgetbridge-user";
|
||||
@ -53,6 +55,10 @@ public class GBPrefs {
|
||||
return mPrefs.getBoolean(AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT);
|
||||
}
|
||||
|
||||
public boolean getAutoReconnectScan() {
|
||||
return mPrefs.getBoolean(AUTO_RECONNECT_SCAN, AUTO_RECONNECT_SCAN_DEFAULT);
|
||||
}
|
||||
|
||||
public boolean getAutoStart() {
|
||||
return mPrefs.getBoolean(AUTO_START, AUTO_START_DEFAULT);
|
||||
}
|
||||
|
@ -692,4 +692,5 @@
|
||||
<string name="mode_configuration">Mode Configuration</string>
|
||||
<string name="save_configuration">Save Configuration</string>
|
||||
<string name="appwidget_not_connected">Not connected, alarm not set.</string>
|
||||
<string name="pref_title_general_autoreconnectscan">Use BLE Scanner for Reconnect</string>
|
||||
</resources>
|
||||
|
@ -18,6 +18,11 @@
|
||||
android:defaultValue="false"
|
||||
android:key="general_autocreconnect"
|
||||
android:title="@string/pref_title_general_autoreconnect" />
|
||||
<CheckBoxPreference
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:defaultValue="false"
|
||||
android:key="general_autoreconnectscan"
|
||||
android:title="@string/pref_title_general_autoreconnectscan" />
|
||||
<ListPreference
|
||||
android:defaultValue="default"
|
||||
android:key="audio_player"
|
||||
|
Loading…
Reference in New Issue
Block a user