Added scannable-only devices (#3621)

Co-authored-by: Daniel Dakhno <dakhnod@gmail.com>
Co-committed-by: Daniel Dakhno <dakhnod@gmail.com>
This commit is contained in:
Daniel Dakhno 2024-03-28 21:07:05 +00:00 committed by José Rebelo
parent bf762a25a5
commit 8cf87a418b
13 changed files with 245 additions and 8 deletions

View File

@ -1005,10 +1005,15 @@ public class DebugActivity extends AbstractGBActivity {
return;
}
DeviceType deviceType = DeviceType.values()[(int) deviceKey];
String deviceName = deviceType.name();
int deviceNameResource = deviceType.getDeviceCoordinator().getDeviceNameResource();
if(deviceNameResource != 0){
deviceName = context.getString(deviceNameResource);
}
try (
DBHandler db = GBApplication.acquireDB()) {
DaoSession daoSession = db.getDaoSession();
GBDevice gbDevice = new GBDevice(deviceMac, deviceType.name(), "", null, deviceType);
GBDevice gbDevice = new GBDevice(deviceMac, deviceName, "", null, deviceType);
gbDevice.setFirmwareVersion("N/A");
gbDevice.setFirmwareVersion2("N/A");
@ -1018,7 +1023,7 @@ public class DebugActivity extends AbstractGBActivity {
Device device = DBHelper.getDevice(gbDevice, daoSession); //the addition happens here
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
GB.toast(context, "Added test device: " + deviceType.name(), Toast.LENGTH_SHORT, GB.INFO);
GB.toast(context, "Added test device: " + deviceName, Toast.LENGTH_SHORT, GB.INFO);
} catch (
Exception e) {

View File

@ -258,6 +258,16 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
holder.root.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
}
void handleDeviceConnect(GBDevice device){
if(!device.getDeviceCoordinator().isConnectable()){
device.setState(GBDevice.State.WAITING_FOR_SCAN);
device.sendDeviceUpdateIntent(GBApplication.getContext(), GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
return;
}
GBApplication.deviceService(device).connect();
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
final GBDevice device = devicesListWithFolders.get(position);
@ -298,7 +308,7 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
createDynamicShortcut(device);
}
GBApplication.deviceService(device).connect();
handleDeviceConnect(device);
}
}
});
@ -853,7 +863,7 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
case R.id.controlcenter_device_submenu_connect:
if (device.getState() != GBDevice.State.CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_connecting);
GBApplication.deviceService(device).connect();
handleDeviceConnect(device);
}
return true;
case R.id.controlcenter_device_submenu_disconnect:

View File

@ -115,6 +115,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return ConnectionType.BOTH;
}
@Override
public boolean isConnectable(){
return true;
}
@NonNull
@Override
public Collection<? extends ScanFilter> createBLEScanFilters() {

View File

@ -123,6 +123,14 @@ public interface DeviceCoordinator {
*/
ConnectionType getConnectionType();
/**
* Returns false is the Device is not connectable,
* only scannable, like beacons
*
* @return boolean
*/
boolean isConnectable();
/**
* Checks whether this coordinator handles the given candidate.
*

View File

@ -0,0 +1,61 @@
package nodomain.freeyourgadget.gadgetbridge.devices.scannable;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
public class ScannableDeviceCoordinator extends AbstractBLEDeviceCoordinator {
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@Override
public int[] getSupportedDeviceSpecificApplicationSettings() {
return new int[]{
R.xml.devicesettings_scannable
};
}
@Override
public boolean isConnectable() {
return false;
}
@Override
public String getManufacturer() {
return "unknown";
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass() {
return null;
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_scannable;
}
@Override
public int getBatteryCount() {
return 0;
}
@Override
public int getDefaultIconResource() {
return R.drawable.ic_device_scannable;
}
@Override
public int getDisabledIconResource() {
return R.drawable.ic_device_scannable_disabled;
}
}

View File

@ -310,7 +310,7 @@ public class GBDevice implements Parcelable {
}
public boolean isConnected() {
return mState.equalsOrHigherThan(State.CONNECTED);
return mState == State.SCANNED || mState.equalsOrHigherThan(State.CONNECTED);
}
public boolean isInitializing() {
@ -318,7 +318,7 @@ public class GBDevice implements Parcelable {
}
public boolean isInitialized() {
return mState.equalsOrHigherThan(State.INITIALIZED);
return mState == State.SCANNED || mState.equalsOrHigherThan(State.INITIALIZED);
}
public boolean isConnecting() {
@ -728,6 +728,7 @@ public class GBDevice implements Parcelable {
NOT_CONNECTED(R.string.not_connected),
WAITING_FOR_RECONNECT(R.string.waiting_for_reconnect),
WAITING_FOR_SCAN(R.string.device_state_waiting_scan),
SCANNED(R.string.state_scanned),
CONNECTING(R.string.connecting),
CONNECTED(R.string.connected, R.string.connecting),
INITIALIZING(R.string.initializing, R.string.connecting),

View File

@ -151,6 +151,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.qc35.QC35Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.scannable.ScannableDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss.SMAQ2OSSCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.soflow.SoFlowCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyLinkBudsCoordinator;
@ -374,6 +375,7 @@ public enum DeviceType {
SONY_WENA_3(SonyWena3Coordinator.class),
FEMOMETER_VINCA2(FemometerVinca2DeviceCoordinator.class),
PIXOO(PixooCoordinator.class),
SCANNABLE(ScannableDeviceCoordinator.class),
TEST(TestDeviceCoordinator.class);
private DeviceCoordinator coordinator;

View File

@ -255,6 +255,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
private OsmandEventReceiver mOsmandAidlHelper = null;
private HashMap<String, Long> deviceLastScannedTimestamps = new HashMap<>();
private final String[] mMusicActions = {
"com.android.music.metachanged",
"com.android.music.playstatechanged",
@ -270,21 +272,26 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
private final String COMMAND_BLUETOOTH_CONNECT = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECT";
private final String ACTION_DEVICE_CONNECTED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECTED";
private final String ACTION_DEVICE_SCANNED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_SCANNED";
private final int NOTIFICATIONS_CACHE_MAX = 10; // maximum amount of notifications to cache per device while disconnected
private boolean allowBluetoothIntentApi = false;
private boolean reconnectViaScan = GBPrefs.RECONNECT_SCAN_DEFAULT;
private void sendDeviceConnectedBroadcast(String address){
private void sendDeviceAPIBroadcast(String address, String action){
if(!allowBluetoothIntentApi){
GB.log("not sending API event due to settings", GB.INFO, null);
return;
}
Intent intent = new Intent(ACTION_DEVICE_CONNECTED);
Intent intent = new Intent(action);
intent.putExtra("EXTRA_DEVICE_ADDRESS", address);
sendBroadcast(intent);
}
private void sendDeviceConnectedBroadcast(String address){
sendDeviceAPIBroadcast(address, ACTION_DEVICE_CONNECTED);
}
BroadcastReceiver bluetoothCommandReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@ -368,6 +375,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
LOG.debug("device state update reason");
sendDeviceConnectedBroadcast(device.getAddress());
sendCachedNotifications(device);
}else if(subject == GBDevice.DeviceUpdateSubject.CONNECTION_STATE && (device.getState() == GBDevice.State.SCANNED)){
sendDeviceAPIBroadcast(device.getAddress(), ACTION_DEVICE_SCANNED);
}
}else if(BLEScanService.EVENT_DEVICE_FOUND.equals(action)){
String deviceAddress = intent.getStringExtra(BLEScanService.EXTRA_DEVICE_ADDRESS);
@ -382,6 +391,44 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
return;
}
if(!target.getDeviceCoordinator().isConnectable()){
int actualRSSI = intent.getIntExtra(BLEScanService.EXTRA_RSSI, 0);
Prefs prefs = new Prefs(
GBApplication.getDeviceSpecificSharedPrefs(target.getAddress())
);
long timeoutSeconds = prefs.getLong("devicesetting_scannable_debounce", 60);
long minimumUnseenSeconds = prefs.getLong("devicesetting_scannable_unseen", 0);
int thresholdRSSI = prefs.getInt("devicesetting_scannable_rssi", -100);
if(actualRSSI < thresholdRSSI){
LOG.debug("ignoring {} since RSSI is too low ({} < {})", deviceAddress, actualRSSI, thresholdRSSI);
return;
}
Long lastSeenTimestamp = deviceLastScannedTimestamps.get(deviceAddress);
deviceLastScannedTimestamps.put(deviceAddress, System.currentTimeMillis());
if(lastSeenTimestamp != null){
long secondsSince = (System.currentTimeMillis() - lastSeenTimestamp) / 1000;
if(secondsSince < minimumUnseenSeconds){
LOG.debug("ignoring {}, since only {} seconds passed (< {})", deviceAddress, secondsSince, minimumUnseenSeconds);
return;
}
}
target.setState(GBDevice.State.SCANNED);
target.sendDeviceUpdateIntent(DeviceCommunicationService.this, GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
new Handler().postDelayed(() -> {
if(target.getState() != GBDevice.State.SCANNED){
return;
}
deviceLastScannedTimestamps.put(target.getAddress(), System.currentTimeMillis());
target.setState(GBDevice.State.WAITING_FOR_SCAN);
target.sendDeviceUpdateIntent(DeviceCommunicationService.this, GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
}, timeoutSeconds * 1000);
return;
}
connectToDevice(target);
}
}
@ -501,6 +548,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
private void connectToDevice(GBDevice device, boolean firstTime){
if(!device.getDeviceCoordinator().isConnectable()){
GB.toast("Cannot connect to Scannable Device", Toast.LENGTH_SHORT, GB.INFO);
return;
}
List<GBDevice> gbDevs = null;
boolean fromExtra = false;

View File

@ -62,6 +62,7 @@ public class BLEScanService extends Service {
public static final String EXTRA_DEVICE = "EXTRA_DEVICE";
public static final String EXTRA_DEVICE_ADDRESS = "EXTRA_DEVICE_ADDRESS";
public static final String EXTRA_RSSI = "EXTRA_RSSI";
// 5 minutes scan restart interval
private final int DELAY_SCAN_RESTART = 5 * 60 * 1000;
@ -100,6 +101,7 @@ public class BLEScanService extends Service {
Intent intent = new Intent(EVENT_DEVICE_FOUND);
intent.putExtra(EXTRA_DEVICE_ADDRESS, device.getAddress());
intent.putExtra(EXTRA_RSSI, result.getRssi());
localBroadcastManager.sendBroadcast(intent);
// device found, attempt connection

View File

@ -0,0 +1,28 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:name="path"
android:pathData="M 3.871 3.877 L 24.796 3.877 C 24.962 3.877 25.126 3.92 25.27 4.004 C 25.414 4.087 25.534 4.206 25.617 4.35 C 25.7 4.494 25.744 4.658 25.744 4.824 L 25.744 24.834 C 25.744 25 25.7 25.164 25.617 25.308 C 25.534 25.452 25.414 25.572 25.27 25.655 C 25.126 25.738 24.962 25.782 24.796 25.782 L 3.871 25.782 C 3.705 25.782 3.541 25.738 3.397 25.655 C 3.253 25.572 3.134 25.452 3.051 25.308 C 2.967 25.164 2.924 25 2.924 24.834 L 2.924 4.824 C 2.924 4.573 3.024 4.332 3.201 4.154 C 3.379 3.977 3.62 3.877 3.871 3.877 Z"
android:fillColor="#1f7fdb"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 3.879 3.035 L 24.804 3.035 C 25.055 3.035 25.296 3.135 25.474 3.312 C 25.651 3.49 25.751 3.731 25.751 3.982 L 25.751 23.992 C 25.751 24.158 25.708 24.322 25.624 24.466 C 25.541 24.61 25.422 24.73 25.278 24.813 C 25.134 24.896 24.97 24.94 24.804 24.94 L 3.88 24.94 C 3.714 24.94 3.55 24.896 3.406 24.813 C 3.262 24.73 3.143 24.61 3.06 24.466 C 2.976 24.322 2.933 24.158 2.933 23.992 L 2.933 3.982 C 2.933 3.731 3.033 3.49 3.21 3.312 C 3.388 3.135 3.629 3.035 3.88 3.035 Z"
android:fillColor="#4dabf5"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 3.871 3.413 L 24.796 3.413 C 24.962 3.413 25.126 3.456 25.27 3.54 C 25.414 3.623 25.534 3.742 25.617 3.886 C 25.7 4.03 25.744 4.194 25.744 4.36 L 25.744 24.37 C 25.744 24.536 25.7 24.7 25.617 24.844 C 25.534 24.988 25.414 25.108 25.27 25.191 C 25.126 25.274 24.962 25.318 24.796 25.318 L 3.871 25.318 C 3.705 25.318 3.541 25.274 3.397 25.191 C 3.253 25.108 3.134 24.988 3.051 24.844 C 2.967 24.7 2.924 24.536 2.924 24.37 L 2.924 4.36 C 2.924 4.109 3.024 3.868 3.201 3.691 C 3.378 3.513 3.619 3.413 3.87 3.413 Z"
android:fillColor="#2196f3"
android:strokeWidth="1"/>
<path
android:name="path_3"
android:pathData="M 16.122 14.46 L 17.825 16.245 C 18.03 15.691 18.148 15.083 18.148 14.452 C 18.148 13.821 18.03 13.229 17.832 12.675 Z M 20.005 10.382 L 19.08 11.351 C 19.543 12.282 19.8 13.329 19.8 14.444 C 19.8 15.56 19.535 16.614 19.08 17.538 L 19.961 18.461 C 20.673 17.276 21.092 15.875 21.092 14.375 C 21.084 12.921 20.688 11.551 20.005 10.382 Z M 17.201 11.151 L 13.009 6.758 L 12.275 6.758 L 12.275 12.598 L 8.906 9.066 L 7.871 10.151 L 11.974 14.452 L 7.871 18.753 L 8.906 19.838 L 12.275 16.306 L 12.275 22.147 L 13.009 22.147 L 17.201 17.753 L 14.044 14.452 Z M 13.743 9.705 L 15.123 11.151 L 13.743 12.598 Z M 15.123 17.753 L 13.743 19.2 L 13.743 16.306 Z"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
</vector>

View File

@ -0,0 +1,26 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:fillColor="#7a7a7a"
android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
android:strokeWidth="3.57115173" />
<path
android:fillAlpha="0.9411765"
android:fillColor="#9f9f9f"
android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
android:strokeWidth="3.57115173" />
<path
android:fillColor="#8a8a8a"
android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
android:strokeWidth="3.57115173" />
<path
android:name="path_3"
android:pathData="M 16.122 14.46 L 17.825 16.245 C 18.03 15.691 18.148 15.083 18.148 14.452 C 18.148 13.821 18.03 13.229 17.832 12.675 Z M 20.005 10.382 L 19.08 11.351 C 19.543 12.282 19.8 13.329 19.8 14.444 C 19.8 15.56 19.535 16.614 19.08 17.538 L 19.961 18.461 C 20.673 17.276 21.092 15.875 21.092 14.375 C 21.084 12.921 20.688 11.551 20.005 10.382 Z M 17.201 11.151 L 13.009 6.758 L 12.275 6.758 L 12.275 12.598 L 8.906 9.066 L 7.871 10.151 L 11.974 14.452 L 7.871 18.753 L 8.906 19.838 L 12.275 16.306 L 12.275 22.147 L 13.009 22.147 L 17.201 17.753 L 14.044 14.452 Z M 13.743 9.705 L 15.123 11.151 L 13.743 12.598 Z M 15.123 17.753 L 13.743 19.2 L 13.743 16.306 Z"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
</vector>

View File

@ -2711,4 +2711,12 @@
<string name="unbind_before_pair_message">This device is already bound in Android settings, which can make pairing fail for some devices.\n\nIf adding the device fails, please remove it in Android settings and try again.</string>
<string name="companion_pairing_request_title">Companion device</string>
<string name="companion_pairing_request_description">Pair this device as companion?.\n\nThis is recommended for some functions such as find device, and provides a better connection.</string>
<string name="devicetype_scannable">Scannable device</string>
<string name="state_scanned">Scanned</string>
<string name="devicesetting_scannable_debounce">Scannable debounce timeout (seconds)</string>
<string name="devicesetting_scannable_minimum_unseen">Minimum unseen time (seconds)</string>
<string name="devicesetting_scannable_rssi">Minimal RSSI threshold</string>
<string name="devicesetting_scannable_debounce_summary">After being scanned, the device will stick as scanned and ignored for the specified amount of time</string>
<string name="devicesetting_scannable_minimum_unseen_summary">After being scanned, the device has to be unseen for this amount of time before being registered again</string>
<string name="devicesetting_scannable_rssi_summary">The minimum RSSI threshold for detection</string>
</resources>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
android:title="@string/devicesetting_scannable_debounce"
android:summary="@string/devicesetting_scannable_debounce_summary"
android:key="devicesetting_scannable_debounce"
android:defaultValue="60"
android:inputType="number" />
<EditTextPreference
android:title="@string/devicesetting_scannable_minimum_unseen"
android:summary="@string/devicesetting_scannable_minimum_unseen_summary"
android:key="devicesetting_scannable_unseen"
android:defaultValue="0"
android:inputType="number" />
<EditTextPreference
android:title="@string/devicesetting_scannable_rssi"
android:summary="@string/devicesetting_scannable_rssi_summary"
android:key="devicesetting_scannable_rssi"
android:defaultValue="-100"
android:min="-100"
android:max="0"
android:inputType="number" />
</PreferenceScreen>