mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-01-12 10:55:49 +01:00
Mi Band 5: Send GPS location to band during workout
This commit is contained in:
parent
ee93cce16d
commit
b07cd54468
@ -84,6 +84,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksContentObserver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksContentObserver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
@ -527,6 +528,14 @@ public class DebugActivity extends AbstractGBActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Button stopPhoneGpsLocationListener = findViewById(R.id.stopPhoneGpsLocationListener);
|
||||||
|
stopPhoneGpsLocationListener.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
GBLocationManager.stopAll(getBaseContext());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Button showStatusFitnessAppTracking = findViewById(R.id.showStatusFitnessAppTracking);
|
Button showStatusFitnessAppTracking = findViewById(R.id.showStatusFitnessAppTracking);
|
||||||
final int delay = 2 * 1000;
|
final int delay = 2 * 1000;
|
||||||
|
|
||||||
|
@ -116,6 +116,8 @@ public class DeviceSettingsPreferenceConst {
|
|||||||
public static final String PREF_DO_NOT_DISTURB_AUTOMATIC = "automatic";
|
public static final String PREF_DO_NOT_DISTURB_AUTOMATIC = "automatic";
|
||||||
public static final String PREF_DO_NOT_DISTURB_SCHEDULED = "scheduled";
|
public static final String PREF_DO_NOT_DISTURB_SCHEDULED = "scheduled";
|
||||||
|
|
||||||
|
public static final String PREF_WORKOUT_SEND_GPS_TO_BAND = "workout_send_gps_to_band";
|
||||||
|
|
||||||
public static final String PREF_FIND_PHONE = "prefs_find_phone";
|
public static final String PREF_FIND_PHONE = "prefs_find_phone";
|
||||||
public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration";
|
public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration";
|
||||||
public static final String PREF_AUTOLIGHT = "autolight";
|
public static final String PREF_AUTOLIGHT = "autolight";
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.devices;
|
package nodomain.freeyourgadget.gadgetbridge.devices;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -130,4 +131,6 @@ public interface EventHandler {
|
|||||||
void onSetLedColor(int color);
|
void onSetLedColor(int color);
|
||||||
|
|
||||||
void onPowerOff();
|
void onPowerOff();
|
||||||
|
|
||||||
|
void onSetGpsLocation(Location location);
|
||||||
}
|
}
|
||||||
|
@ -363,6 +363,12 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
|||||||
return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST, false);
|
return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean getWorkoutSendGpsToBand(String deviceAddress) {
|
||||||
|
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||||
|
|
||||||
|
return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsScreenshots() {
|
public boolean supportsScreenshots() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -35,8 +35,9 @@ public class HuamiService {
|
|||||||
public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = UUID.fromString("00001532-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = UUID.fromString("00001532-0000-3512-2118-0009af100700");
|
||||||
|
|
||||||
public static final UUID UUID_UNKNOWN_CHARACTERISTIC0 = UUID.fromString("00000000-0000-3512-2118-0009af100700");
|
public static final UUID UUID_UNKNOWN_CHARACTERISTIC0 = UUID.fromString("00000000-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_UNKNOWN_CHARACTERISTIC1 = UUID.fromString("00000001-0000-3512-2118-0009af100700");
|
public static final UUID UUID_UNKNOWN_RAW_SENSOR_CONTROL = UUID.fromString("00000001-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_UNKNOWN_CHARACTERISTIC2 = UUID.fromString("00000002-0000-3512-2118-0009af100700");
|
public static final UUID UUID_UNKNOWN_RAW_SENSOR_DATA = UUID.fromString("00000002-0000-3512-2118-0009af100700");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alarms, Display and other configuration.
|
* Alarms, Display and other configuration.
|
||||||
*/
|
*/
|
||||||
@ -48,9 +49,11 @@ public class HuamiService {
|
|||||||
public static final UUID UUID_CHARACTERISTIC_8_USER_SETTINGS = UUID.fromString("00000008-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_8_USER_SETTINGS = UUID.fromString("00000008-0000-3512-2118-0009af100700");
|
||||||
// service uuid fee1
|
// service uuid fee1
|
||||||
public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700");
|
||||||
|
public static final UUID UUID_CHARACTERISTIC_WORKOUT = UUID.fromString("0000000f-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_CHARACTERISTIC_DEVICEEVENT = UUID.fromString("00000010-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_DEVICEEVENT = UUID.fromString("00000010-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_CHARACTERISTIC_AUDIO = UUID.fromString("00000012-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_AUDIO = UUID.fromString("00000012-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_CHARACTERISTIC_AUDIODATA = UUID.fromString("00000013-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_AUDIODATA = UUID.fromString("00000013-0000-3512-2118-0009af100700");
|
||||||
|
public static final UUID UUID_UNKNOWN_CHARACTERISTIC5 = UUID.fromString("00000014-0000-3512-2118-0009af100700");
|
||||||
|
|
||||||
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE = UUID.fromString("00000016-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE = UUID.fromString("00000016-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ = UUID.fromString("00000017-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ = UUID.fromString("00000017-0000-3512-2118-0009af100700");
|
||||||
|
@ -115,6 +115,7 @@ public class MiBand5Coordinator extends HuamiCoordinator {
|
|||||||
R.xml.devicesettings_nightmode,
|
R.xml.devicesettings_nightmode,
|
||||||
R.xml.devicesettings_liftwrist_display_sensitivity,
|
R.xml.devicesettings_liftwrist_display_sensitivity,
|
||||||
R.xml.devicesettings_inactivity_dnd,
|
R.xml.devicesettings_inactivity_dnd,
|
||||||
|
R.xml.devicesettings_workout_send_gps_to_band,
|
||||||
R.xml.devicesettings_swipeunlock,
|
R.xml.devicesettings_swipeunlock,
|
||||||
R.xml.devicesettings_sync_calendar,
|
R.xml.devicesettings_sync_calendar,
|
||||||
R.xml.devicesettings_reserve_reminders_calendar,
|
R.xml.devicesettings_reserve_reminders_calendar,
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
/* Copyright (C) 2022 José Rebelo
|
||||||
|
|
||||||
|
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.gps;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.LocationListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract location provider, which periodically sends a location update to the provided {@link LocationListener}.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractLocationProvider {
|
||||||
|
private final LocationListener locationListener;
|
||||||
|
|
||||||
|
public AbstractLocationProvider(final LocationListener locationListener) {
|
||||||
|
this.locationListener = locationListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final LocationListener getLocationListener() {
|
||||||
|
return this.locationListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start sending periodic location updates.
|
||||||
|
*
|
||||||
|
* @param context the {@link Context}.
|
||||||
|
*/
|
||||||
|
abstract void start(final Context context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop sending periodic location updates.
|
||||||
|
*
|
||||||
|
* @param context the {@link Context}.
|
||||||
|
*/
|
||||||
|
abstract void stop(final Context context);
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
/* Copyright (C) 2022 José Rebelo
|
||||||
|
|
||||||
|
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.gps;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationListener;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of a {@link LocationListener} that forwards the location updates to the
|
||||||
|
* provided {@link EventHandler}.
|
||||||
|
*/
|
||||||
|
public class GBLocationListener implements LocationListener {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(GBLocationListener.class);
|
||||||
|
|
||||||
|
private final EventHandler eventHandler;
|
||||||
|
|
||||||
|
private Location previousLocation;
|
||||||
|
|
||||||
|
public GBLocationListener(final EventHandler eventHandler) {
|
||||||
|
this.eventHandler = eventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationChanged(final Location location) {
|
||||||
|
LOG.info("Location changed: {}", location);
|
||||||
|
|
||||||
|
// The location usually doesn't contain speed, compute it from the previous location
|
||||||
|
if (previousLocation != null && !location.hasSpeed()) {
|
||||||
|
long timeInterval = (location.getTime() - previousLocation.getTime()) / 1000L;
|
||||||
|
float distanceInMeters = previousLocation.distanceTo(location);
|
||||||
|
location.setSpeed(distanceInMeters / timeInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
previousLocation = location;
|
||||||
|
|
||||||
|
eventHandler.onSetGpsLocation(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderDisabled(final String provider) {
|
||||||
|
LOG.info("onProviderDisabled: {}", provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderEnabled(final String provider) {
|
||||||
|
LOG.info("onProviderDisabled: {}", provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusChanged(final String provider, final int status, final Bundle extras) {
|
||||||
|
LOG.info("onStatusChanged: {}", provider, status);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/* Copyright (C) 2022 José Rebelo
|
||||||
|
|
||||||
|
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.gps;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationListener;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static location manager, which keeps track of what providers are currently running. A notification is kept
|
||||||
|
* while there is at least one provider runnin.
|
||||||
|
*/
|
||||||
|
public class GBLocationManager {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(GBLocationManager.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current number of running listeners.
|
||||||
|
*/
|
||||||
|
private static Map<EventHandler, AbstractLocationProvider> providers = new HashMap<>();
|
||||||
|
|
||||||
|
public static void start(final Context context, final EventHandler eventHandler) {
|
||||||
|
if (providers.containsKey(eventHandler)) {
|
||||||
|
LOG.warn("EventHandler already registered");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GB.createGpsNotification(context, providers.size() + 1);
|
||||||
|
|
||||||
|
final GBLocationListener locationListener = new GBLocationListener(eventHandler);
|
||||||
|
final AbstractLocationProvider locationProvider = new PhoneGpsLocationProvider(locationListener);
|
||||||
|
|
||||||
|
locationProvider.start(context);
|
||||||
|
|
||||||
|
providers.put(eventHandler, locationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stop(final Context context, final EventHandler eventHandler) {
|
||||||
|
final AbstractLocationProvider locationProvider = providers.remove(eventHandler);
|
||||||
|
|
||||||
|
if (locationProvider != null) {
|
||||||
|
LOG.warn("EventHandler not registered");
|
||||||
|
|
||||||
|
locationProvider.stop(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!providers.isEmpty()) {
|
||||||
|
GB.createGpsNotification(context, providers.size());
|
||||||
|
} else {
|
||||||
|
GB.removeGpsNotification(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopAll(final Context context) {
|
||||||
|
for (EventHandler eventHandler : providers.keySet()) {
|
||||||
|
stop(context, eventHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/* Copyright (C) 2022 José Rebelo
|
||||||
|
|
||||||
|
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.gps;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationListener;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mock location provider which keeps updating the location at a constant speed, starting from the
|
||||||
|
* last known location. Useful for local tests.
|
||||||
|
*/
|
||||||
|
public class MockLocationProvider extends AbstractLocationProvider {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MockLocationProvider.class);
|
||||||
|
|
||||||
|
private Location previousLocation = new CurrentPosition().getLastKnownLocation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval between location updates, in milliseconds.
|
||||||
|
*/
|
||||||
|
private final int interval = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Difference between location updates, in degrees.
|
||||||
|
*/
|
||||||
|
private final float coordDiff = 0.0002f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the handler is running.
|
||||||
|
*/
|
||||||
|
private boolean running = false;
|
||||||
|
|
||||||
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
private final Runnable locationUpdateRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Location newLocation = new Location(previousLocation);
|
||||||
|
newLocation.setLatitude(previousLocation.getLatitude() + coordDiff);
|
||||||
|
newLocation.setTime(System.currentTimeMillis());
|
||||||
|
|
||||||
|
getLocationListener().onLocationChanged(newLocation);
|
||||||
|
|
||||||
|
previousLocation = newLocation;
|
||||||
|
|
||||||
|
if (running) {
|
||||||
|
handler.postDelayed(this, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public MockLocationProvider(LocationListener locationListener) {
|
||||||
|
super(locationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void start(final Context context) {
|
||||||
|
LOG.info("Starting mock location provider");
|
||||||
|
|
||||||
|
running = true;
|
||||||
|
handler.postDelayed(locationUpdateRunnable, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void stop(final Context context) {
|
||||||
|
LOG.info("Stopping mock location provider");
|
||||||
|
|
||||||
|
running = false;
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/* Copyright (C) 2022 José Rebelo
|
||||||
|
|
||||||
|
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.gps;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationListener;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A location provider that uses the phone GPS, using {@link LocationManager}.
|
||||||
|
*/
|
||||||
|
public class PhoneGpsLocationProvider extends AbstractLocationProvider {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(PhoneGpsLocationProvider.class);
|
||||||
|
|
||||||
|
private static final int INTERVAL_MIN_TIME = 1000;
|
||||||
|
private static final int INTERVAL_MIN_DISTANCE = 0;
|
||||||
|
|
||||||
|
public PhoneGpsLocationProvider(LocationListener locationListener) {
|
||||||
|
super(locationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void start(final Context context) {
|
||||||
|
LOG.info("Starting phone gps location provider");
|
||||||
|
|
||||||
|
final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
locationManager.removeUpdates(getLocationListener());
|
||||||
|
locationManager.requestLocationUpdates(
|
||||||
|
LocationManager.GPS_PROVIDER,
|
||||||
|
INTERVAL_MIN_TIME,
|
||||||
|
INTERVAL_MIN_DISTANCE,
|
||||||
|
getLocationListener(),
|
||||||
|
Looper.getMainLooper()
|
||||||
|
);
|
||||||
|
|
||||||
|
final Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
|
||||||
|
LOG.debug("Last known location: {}", lastKnownLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void stop(final Context context) {
|
||||||
|
LOG.info("Stopping phone gps location provider");
|
||||||
|
|
||||||
|
final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
locationManager.removeUpdates(getLocationListener());
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ import android.app.Service;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
@ -471,4 +472,11 @@ public class GBDeviceService implements DeviceService {
|
|||||||
Intent intent = createIntent().setAction(ACTION_POWER_OFF);
|
Intent intent = createIntent().setAction(ACTION_POWER_OFF);
|
||||||
invokeService(intent);
|
invokeService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetGpsLocation(Location location) {
|
||||||
|
Intent intent = createIntent().setAction(ACTION_SET_GPS_LOCATION);
|
||||||
|
intent.putExtra(EXTRA_GPS_LOCATION, location);
|
||||||
|
invokeService(intent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,7 @@ public interface DeviceService extends EventHandler {
|
|||||||
String ACTION_SEND_WEATHER = PREFIX + ".action.send_weather";
|
String ACTION_SEND_WEATHER = PREFIX + ".action.send_weather";
|
||||||
String ACTION_TEST_NEW_FUNCTION = PREFIX + ".action.test_new_function";
|
String ACTION_TEST_NEW_FUNCTION = PREFIX + ".action.test_new_function";
|
||||||
String ACTION_SET_FM_FREQUENCY = PREFIX + ".action.set_fm_frequency";
|
String ACTION_SET_FM_FREQUENCY = PREFIX + ".action.set_fm_frequency";
|
||||||
|
String ACTION_SET_GPS_LOCATION = PREFIX + ".action.set_gps_location";
|
||||||
String ACTION_SET_LED_COLOR = PREFIX + ".action.set_led_color";
|
String ACTION_SET_LED_COLOR = PREFIX + ".action.set_led_color";
|
||||||
String ACTION_POWER_OFF = PREFIX + ".action.power_off";
|
String ACTION_POWER_OFF = PREFIX + ".action.power_off";
|
||||||
String EXTRA_NOTIFICATION_BODY = "notification_body";
|
String EXTRA_NOTIFICATION_BODY = "notification_body";
|
||||||
@ -122,6 +123,7 @@ public interface DeviceService extends EventHandler {
|
|||||||
String EXTRA_RECORDED_DATA_TYPES = "data_types";
|
String EXTRA_RECORDED_DATA_TYPES = "data_types";
|
||||||
String EXTRA_FM_FREQUENCY = "fm_frequency";
|
String EXTRA_FM_FREQUENCY = "fm_frequency";
|
||||||
String EXTRA_LED_COLOR = "led_color";
|
String EXTRA_LED_COLOR = "led_color";
|
||||||
|
String EXTRA_GPS_LOCATION = "gps_location";
|
||||||
String EXTRA_RESET_FLAGS = "reset_flags";
|
String EXTRA_RESET_FLAGS = "reset_flags";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,6 +30,7 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@ -86,104 +87,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.LanguageUtils;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TRANSLITERATION_ENABLED;
|
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TRANSLITERATION_ENABLED;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.*;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_REORDER;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETE_CALENDAREVENT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETE_NOTIFICATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_STEPS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_RECORDED_DATA;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_HEARTRATE_TEST;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_POWER_OFF;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_READ_CONFIGURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RESET;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_CONFIGURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_WEATHER;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETCANNEDMESSAGES;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICSTATE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_CONSTANT_VIBRATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_FM_FREQUENCY;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_LED_COLOR;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_PHONE_VOLUME;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_REMINDERS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_WORLD_CLOCKS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG_ID;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOOLEAN_ENABLE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DESCRIPTION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_ID;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_LOCATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TIMESTAMP;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TITLE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TYPE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_DISPLAYNAME;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_DNDSUPPRESSED;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES_TYPE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FM_FREQUENCY;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_INTERVAL_SECONDS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_LED_COLOR;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_DURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_POSITION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_RATE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_REPEAT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_SHUFFLE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_STATE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKCOUNT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKNR;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ACTIONS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_BODY;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_DNDSUPPRESSED;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_FLAGS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ICONID;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ID;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PEBBLE_COLOR;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PHONENUMBER;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCEAPPID;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCENAME;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SUBJECT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TYPE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PHONE_VOLUME;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RECORDED_DATA_TYPES;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_REMINDERS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RESET_FLAGS;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WORLD_CLOCKS;
|
|
||||||
|
|
||||||
public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
|
public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
|
||||||
@ -657,6 +561,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
|||||||
mDeviceSupport.onSetFmFrequency(frequency);
|
mDeviceSupport.onSetFmFrequency(frequency);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ACTION_SET_GPS_LOCATION:
|
||||||
|
final Location location = intent.getParcelableExtra(EXTRA_GPS_LOCATION);
|
||||||
|
mDeviceSupport.onSetGpsLocation(location);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ package nodomain.freeyourgadget.gadgetbridge.service;
|
|||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -438,4 +439,12 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
|||||||
}
|
}
|
||||||
delegate.onPowerOff();
|
delegate.onPowerOff();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetGpsLocation(Location location) {
|
||||||
|
if (checkBusy("set gps location")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delegate.onSetGpsLocation(location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
|||||||
import android.bluetooth.BluetoothGattDescriptor;
|
import android.bluetooth.BluetoothGattDescriptor;
|
||||||
import android.bluetooth.BluetoothGattService;
|
import android.bluetooth.BluetoothGattService;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
@ -379,6 +380,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetGpsLocation(Location location) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSetReminders(ArrayList<? extends Reminder> reminders) {
|
public void onSetReminders(ArrayList<? extends Reminder> reminders) {
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ public class HuamiDeviceEvent {
|
|||||||
public static final byte TICK_30MIN = 0x0e; // unsure
|
public static final byte TICK_30MIN = 0x0e; // unsure
|
||||||
public static final byte FIND_PHONE_STOP = 0x0f;
|
public static final byte FIND_PHONE_STOP = 0x0f;
|
||||||
public static final byte MTU_REQUEST = 0x16;
|
public static final byte MTU_REQUEST = 0x16;
|
||||||
|
public static final byte WORKOUT_STARTING = 0x14;
|
||||||
public static final byte ALARM_CHANGED = 0x1a;
|
public static final byte ALARM_CHANGED = 0x1a;
|
||||||
public static final byte MUSIC_CONTROL = (byte) 0xfe;
|
public static final byte MUSIC_CONTROL = (byte) 0xfe;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
/* Copyright (C) 2022 José Rebelo
|
||||||
|
|
||||||
|
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.service.devices.huami;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The phone GPS status, to signal the band.
|
||||||
|
*/
|
||||||
|
public enum HuamiPhoneGpsStatus {
|
||||||
|
ACQUIRED(0x01),
|
||||||
|
SEARCHING(0x02),
|
||||||
|
DISABLED(0x04),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final byte code;
|
||||||
|
|
||||||
|
HuamiPhoneGpsStatus(final int code) {
|
||||||
|
this.code = (byte) code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HuamiPhoneGpsStatus fromCode(final byte code) {
|
||||||
|
for (final HuamiPhoneGpsStatus type : values()) {
|
||||||
|
if (type.getCode() == code) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.location.Location;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@ -102,6 +103,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
@ -458,6 +460,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIO), enable);
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIO), enable);
|
||||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIODATA), enable);
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIODATA), enable);
|
||||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
|
||||||
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_WORKOUT), enable);
|
||||||
if (characteristicChunked2021Read != null) {
|
if (characteristicChunked2021Read != null) {
|
||||||
builder.notify(characteristicChunked2021Read, enable);
|
builder.notify(characteristicChunked2021Read, enable);
|
||||||
}
|
}
|
||||||
@ -1848,19 +1851,169 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
requestMTU(mtu);
|
requestMTU(mtu);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
break;
|
||||||
|
case HuamiDeviceEvent.WORKOUT_STARTING:
|
||||||
|
final HuamiWorkoutTrackActivityType activityType = HuamiWorkoutTrackActivityType.fromCode(value[3]);
|
||||||
|
this.workoutNeedsGps = (value[2] == 1);
|
||||||
|
|
||||||
|
if (activityType == null) {
|
||||||
|
LOG.warn("Unknown workout activity type {}", String.format("0x%x", value[3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Workout starting on band: {}, needs gps = {}", activityType, workoutNeedsGps);
|
||||||
|
|
||||||
|
final boolean sendGpsToBand = HuamiCoordinator.getWorkoutSendGpsToBand(getDevice().getAddress());
|
||||||
|
|
||||||
|
if (workoutNeedsGps) {
|
||||||
|
if (sendGpsToBand) {
|
||||||
|
lastPhoneGpsSent = 0;
|
||||||
|
sendPhoneGpsStatus(HuamiPhoneGpsStatus.SEARCHING);
|
||||||
|
GBLocationManager.start(getContext(), this);
|
||||||
|
} else {
|
||||||
|
sendPhoneGpsStatus(HuamiPhoneGpsStatus.DISABLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG.warn("unhandled event " + value[0]);
|
LOG.warn("unhandled event {}", String.format("0x%x", value[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestMTU(int mtu) {
|
/**
|
||||||
if (GBApplication.isRunningLollipopOrLater()) {
|
* Track whether the currently selected workout needs gps (received in {@link #handleDeviceEvent}, so we can start the activity tracking
|
||||||
new TransactionBuilder("requestMtu")
|
* if needed in {@link #handleDeviceWorkoutEvent}, since in there we don't know what's the current workout.
|
||||||
.requestMtu(mtu)
|
*/
|
||||||
.queue(getQueue());
|
private boolean workoutNeedsGps = false;
|
||||||
mMTU = mtu;
|
|
||||||
|
/**
|
||||||
|
* Track the last time we actually sent a gps location. We need to signal that GPS as re-acquired if the last update was too long ago.
|
||||||
|
*/
|
||||||
|
private long lastPhoneGpsSent = 0;
|
||||||
|
|
||||||
|
private void handleDeviceWorkoutEvent(byte[] value) {
|
||||||
|
if (value == null || value.length == 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (value[0]) {
|
||||||
|
case 0x11:
|
||||||
|
final HuamiWorkoutStatus status = HuamiWorkoutStatus.fromCode(value[1]);
|
||||||
|
if (status == null) {
|
||||||
|
LOG.warn("Unknown workout status {}", String.format("0x%x", value[1]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Got workout status {}", status);
|
||||||
|
|
||||||
|
final boolean sendGpsToBand = HuamiCoordinator.getWorkoutSendGpsToBand(getDevice().getAddress());
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case Start:
|
||||||
|
break;
|
||||||
|
case End:
|
||||||
|
GBLocationManager.stop(getContext(), this);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.warn("Unhandled workout event {}", String.format("0x%x", value[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetGpsLocation(final Location location) {
|
||||||
|
if (characteristicChunked == null || location == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean sendGpsToBand = HuamiCoordinator.getWorkoutSendGpsToBand(getDevice().getAddress());
|
||||||
|
|
||||||
|
if (!sendGpsToBand) {
|
||||||
|
LOG.warn("Sending GPS to band is disabled, ignoring location update");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = 0x40000;
|
||||||
|
int length = 1 + 4 + 31;
|
||||||
|
|
||||||
|
boolean newGpsLock = System.currentTimeMillis() - lastPhoneGpsSent > 5000;
|
||||||
|
lastPhoneGpsSent = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (newGpsLock) {
|
||||||
|
flags |= 0x01;
|
||||||
|
length += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(length);
|
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buf.put((byte) 0x06);
|
||||||
|
buf.putInt(flags);
|
||||||
|
|
||||||
|
if (newGpsLock) {
|
||||||
|
buf.put((byte) 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.putInt((int) (location.getLongitude() * 3000000.0));
|
||||||
|
buf.putInt((int) (location.getLatitude() * 3000000.0));
|
||||||
|
buf.putInt((int) location.getSpeed() * 10);
|
||||||
|
|
||||||
|
buf.putInt((int) (location.getAltitude() * 100));
|
||||||
|
buf.putLong(location.getTime());
|
||||||
|
|
||||||
|
// Seems to always be ff ?
|
||||||
|
buf.putInt(0xffffffff);
|
||||||
|
|
||||||
|
// Not sure what this is, maybe bearing? It changes while moving, but
|
||||||
|
// doesn't seem to be needed on the Mi Band 5
|
||||||
|
buf.putShort((short) 0x00);
|
||||||
|
|
||||||
|
// Seems to always be 0 ?
|
||||||
|
buf.put((byte) 0x00);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final TransactionBuilder builder = performInitialized("send phone gps location");
|
||||||
|
writeToChunked(builder, 6, buf.array());
|
||||||
|
builder.queue(getQueue());
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Unable to send location", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("sendLocationToBand: {}", location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPhoneGpsStatus(final HuamiPhoneGpsStatus status) {
|
||||||
|
int flags = 0x01;
|
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(1 + 4 + 1);
|
||||||
|
|
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buf.put((byte) 0x06);
|
||||||
|
buf.putInt(flags);
|
||||||
|
|
||||||
|
buf.put(status.getCode());
|
||||||
|
|
||||||
|
try {
|
||||||
|
final TransactionBuilder builder = performInitialized("send phone gps status");
|
||||||
|
writeToChunked(builder, 6, buf.array());
|
||||||
|
builder.queue(getQueue());
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Unable to send location", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("sendPhoneGpsStatus: {}", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestMTU(int mtu) {
|
||||||
|
if (!GBApplication.isRunningLollipopOrLater()) {
|
||||||
|
LOG.warn("Requesting MTU is only supported in Lollipop or later");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new TransactionBuilder("requestMtu")
|
||||||
|
.requestMtu(mtu)
|
||||||
|
.queue(getQueue());
|
||||||
|
mMTU = mtu;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acknowledgeFindPhone() {
|
private void acknowledgeFindPhone() {
|
||||||
@ -1985,6 +2138,9 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
} else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
|
} else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
|
||||||
handleDeviceEvent(characteristic.getValue());
|
handleDeviceEvent(characteristic.getValue());
|
||||||
return true;
|
return true;
|
||||||
|
} else if (HuamiService.UUID_CHARACTERISTIC_WORKOUT.equals(characteristicUUID)) {
|
||||||
|
handleDeviceWorkoutEvent(characteristic.getValue());
|
||||||
|
return true;
|
||||||
} else if (HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
|
} else if (HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
|
||||||
handleRealtimeSteps(characteristic.getValue());
|
handleRealtimeSteps(characteristic.getValue());
|
||||||
return true;
|
return true;
|
||||||
@ -2026,6 +2182,9 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
} else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
|
} else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
|
||||||
handleDeviceEvent(characteristic.getValue());
|
handleDeviceEvent(characteristic.getValue());
|
||||||
return true;
|
return true;
|
||||||
|
} else if (HuamiService.UUID_CHARACTERISTIC_WORKOUT.equals(characteristicUUID)) {
|
||||||
|
handleDeviceWorkoutEvent(characteristic.getValue());
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Unhandled characteristic read: " + characteristicUUID);
|
LOG.info("Unhandled characteristic read: " + characteristicUUID);
|
||||||
logMessageContent(characteristic.getValue());
|
logMessageContent(characteristic.getValue());
|
||||||
@ -3204,7 +3363,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
int pos = 2;
|
int pos = 2;
|
||||||
|
|
||||||
for (final String workoutType : enabledActivityTypes) {
|
for (final String workoutType : enabledActivityTypes) {
|
||||||
command[pos++] = HuamiWorkoutActivityType.fromPrefValue(workoutType).getCode();
|
command[pos++] = HuamiWorkoutScreenActivityType.fromPrefValue(workoutType).getCode();
|
||||||
command[pos++] = 0x00;
|
command[pos++] = 0x00;
|
||||||
command[pos++] = 0x01;
|
command[pos++] = 0x01;
|
||||||
}
|
}
|
||||||
@ -3212,7 +3371,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
// Send all the remaining disabled workout types
|
// Send all the remaining disabled workout types
|
||||||
for (final String workoutType : allActivityTypes) {
|
for (final String workoutType : allActivityTypes) {
|
||||||
if (!enabledActivityTypes.contains(workoutType)) {
|
if (!enabledActivityTypes.contains(workoutType)) {
|
||||||
command[pos++] = HuamiWorkoutActivityType.fromPrefValue(workoutType).getCode();
|
command[pos++] = HuamiWorkoutScreenActivityType.fromPrefValue(workoutType).getCode();
|
||||||
command[pos++] = 0x00;
|
command[pos++] = 0x00;
|
||||||
command[pos++] = 0x00;
|
command[pos++] = 0x00;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
|
|||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public enum HuamiWorkoutActivityType {
|
/**
|
||||||
|
* The workout types, to configure the workouts screen on the band.
|
||||||
|
*/
|
||||||
|
public enum HuamiWorkoutScreenActivityType {
|
||||||
OutdoorRunning(0x01),
|
OutdoorRunning(0x01),
|
||||||
Walking(0x06),
|
Walking(0x06),
|
||||||
Treadmill(0x08),
|
Treadmill(0x08),
|
||||||
@ -33,7 +36,7 @@ public enum HuamiWorkoutActivityType {
|
|||||||
|
|
||||||
private final byte code;
|
private final byte code;
|
||||||
|
|
||||||
HuamiWorkoutActivityType(final int code) {
|
HuamiWorkoutScreenActivityType(final int code) {
|
||||||
this.code = (byte) code;
|
this.code = (byte) code;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,12 +44,12 @@ public enum HuamiWorkoutActivityType {
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HuamiWorkoutActivityType fromPrefValue(final String prefValue) {
|
public static HuamiWorkoutScreenActivityType fromPrefValue(final String prefValue) {
|
||||||
for (HuamiWorkoutActivityType type : values()) {
|
for (final HuamiWorkoutScreenActivityType type : values()) {
|
||||||
if (type.name().toLowerCase(Locale.ROOT).equals(prefValue.replace("_", "").toLowerCase(Locale.ROOT))) {
|
if (type.name().toLowerCase(Locale.ROOT).equals(prefValue.replace("_", "").toLowerCase(Locale.ROOT))) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new RuntimeException("No matching HuamiWorkoutActivityType for pref value: " + prefValue);
|
throw new RuntimeException("No matching HuamiWorkoutScreenActivityType for pref value: " + prefValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/* Copyright (C) 2022 José Rebelo
|
||||||
|
|
||||||
|
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.service.devices.huami;
|
||||||
|
|
||||||
|
public enum HuamiWorkoutStatus {
|
||||||
|
Start(0x02),
|
||||||
|
Pause(0x03),
|
||||||
|
Resume(0x04),
|
||||||
|
End(0x05),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final byte code;
|
||||||
|
|
||||||
|
HuamiWorkoutStatus(final int code) {
|
||||||
|
this.code = (byte) code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HuamiWorkoutStatus fromCode(final byte code) {
|
||||||
|
for (final HuamiWorkoutStatus type : values()) {
|
||||||
|
if (type.getCode() == code) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/* Copyright (C) 2022 José Rebelo
|
||||||
|
|
||||||
|
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.service.devices.huami;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workout types, used to start / when workout tracking starts on the band.
|
||||||
|
*/
|
||||||
|
public enum HuamiWorkoutTrackActivityType {
|
||||||
|
OutdoorRunning(0x01),
|
||||||
|
Walking(0x04),
|
||||||
|
Treadmill(0x02),
|
||||||
|
OutdoorCycling(0x03),
|
||||||
|
IndoorCycling(0x09),
|
||||||
|
Elliptical(0x06),
|
||||||
|
PoolSwimming(0x05),
|
||||||
|
Freestyle(0x0b),
|
||||||
|
JumpRope(0x08),
|
||||||
|
RowingMachine(0x07),
|
||||||
|
Yoga(0x0a);
|
||||||
|
|
||||||
|
private final byte code;
|
||||||
|
|
||||||
|
HuamiWorkoutTrackActivityType(final int code) {
|
||||||
|
this.code = (byte) code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HuamiWorkoutTrackActivityType fromCode(final byte code) {
|
||||||
|
for (final HuamiWorkoutTrackActivityType type : values()) {
|
||||||
|
if (type.getCode() == code) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,8 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service.serial;
|
package nodomain.freeyourgadget.gadgetbridge.service.serial;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -289,4 +291,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
|
|||||||
byte[] bytes = gbDeviceProtocol.encodeWorldClocks(clocks);
|
byte[] bytes = gbDeviceProtocol.encodeWorldClocks(clocks);
|
||||||
sendToDevice(bytes);
|
sendToDevice(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetGpsLocation(Location location) {
|
||||||
|
byte[] bytes = gbDeviceProtocol.encodeGpsLocation(location);
|
||||||
|
sendToDevice(bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service.serial;
|
package nodomain.freeyourgadget.gadgetbridge.service.serial;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -163,4 +165,8 @@ public abstract class GBDeviceProtocol {
|
|||||||
public byte[] encodeFmFrequency(float frequency) {
|
public byte[] encodeFmFrequency(float frequency) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] encodeGpsLocation(Location location) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ public class GB {
|
|||||||
public static final String NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID = "gadgetbridge_high_priority";
|
public static final String NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID = "gadgetbridge_high_priority";
|
||||||
public static final String NOTIFICATION_CHANNEL_ID_TRANSFER = "gadgetbridge transfer";
|
public static final String NOTIFICATION_CHANNEL_ID_TRANSFER = "gadgetbridge transfer";
|
||||||
public static final String NOTIFICATION_CHANNEL_ID_LOW_BATTERY = "low_battery";
|
public static final String NOTIFICATION_CHANNEL_ID_LOW_BATTERY = "low_battery";
|
||||||
|
public static final String NOTIFICATION_CHANNEL_ID_GPS = "gps";
|
||||||
|
|
||||||
public static final int NOTIFICATION_ID = 1;
|
public static final int NOTIFICATION_ID = 1;
|
||||||
public static final int NOTIFICATION_ID_INSTALL = 2;
|
public static final int NOTIFICATION_ID_INSTALL = 2;
|
||||||
@ -72,6 +73,7 @@ public class GB {
|
|||||||
public static final int NOTIFICATION_ID_TRANSFER = 4;
|
public static final int NOTIFICATION_ID_TRANSFER = 4;
|
||||||
public static final int NOTIFICATION_ID_EXPORT_FAILED = 5;
|
public static final int NOTIFICATION_ID_EXPORT_FAILED = 5;
|
||||||
public static final int NOTIFICATION_ID_PHONE_FIND = 6;
|
public static final int NOTIFICATION_ID_PHONE_FIND = 6;
|
||||||
|
public static final int NOTIFICATION_ID_GPS = 7;
|
||||||
public static final int NOTIFICATION_ID_ERROR = 42;
|
public static final int NOTIFICATION_ID_ERROR = 42;
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GB.class);
|
private static final Logger LOG = LoggerFactory.getLogger(GB.class);
|
||||||
@ -122,6 +124,12 @@ public class GB {
|
|||||||
context.getString(R.string.notification_channel_low_battery_name),
|
context.getString(R.string.notification_channel_low_battery_name),
|
||||||
NotificationManager.IMPORTANCE_DEFAULT);
|
NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
notificationManager.createNotificationChannel(channelLowBattery);
|
notificationManager.createNotificationChannel(channelLowBattery);
|
||||||
|
|
||||||
|
NotificationChannel channelGps = new NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_ID_GPS,
|
||||||
|
context.getString(R.string.notification_channel_gps),
|
||||||
|
NotificationManager.IMPORTANCE_MIN);
|
||||||
|
notificationManager.createNotificationChannel(channelGps);
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationChannelsCreated = true;
|
notificationChannelsCreated = true;
|
||||||
@ -440,6 +448,27 @@ public class GB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void createGpsNotification(Context context, int numDevices) {
|
||||||
|
Intent notificationIntent = new Intent(context, ControlCenterv2.class);
|
||||||
|
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||||
|
|
||||||
|
NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_GPS)
|
||||||
|
.setTicker(context.getString(R.string.notification_gps_title))
|
||||||
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
.setContentTitle(context.getString(R.string.notification_gps_title))
|
||||||
|
.setContentText(context.getString(R.string.notification_gps_text, numDevices))
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setSmallIcon(R.drawable.ic_gps_location)
|
||||||
|
.setOngoing(true);
|
||||||
|
|
||||||
|
notify(NOTIFICATION_ID_GPS, nb.build(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeGpsNotification(Context context) {
|
||||||
|
removeNotification(NOTIFICATION_ID_GPS, context);
|
||||||
|
}
|
||||||
|
|
||||||
private static Notification createInstallNotification(String text, boolean ongoing,
|
private static Notification createInstallNotification(String text, boolean ongoing,
|
||||||
int percentage, Context context) {
|
int percentage, Context context) {
|
||||||
Intent notificationIntent = new Intent(context, ControlCenterv2.class);
|
Intent notificationIntent = new Intent(context, ControlCenterv2.class);
|
||||||
|
5
app/src/main/res/drawable/ic_gps_location.xml
Normal file
5
app/src/main/res/drawable/ic_gps_location.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#7E7E7E"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
|
||||||
|
</vector>
|
@ -256,6 +256,14 @@
|
|||||||
android:text="Show Fit.App.Track. Status"
|
android:text="Show Fit.App.Track. Status"
|
||||||
grid:layout_columnSpan="2"
|
grid:layout_columnSpan="2"
|
||||||
grid:layout_gravity="fill_horizontal" />
|
grid:layout_gravity="fill_horizontal" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/stopPhoneGpsLocationListener"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/pref_device_action_phone_gps_location_listener_stop"
|
||||||
|
grid:layout_columnSpan="2"
|
||||||
|
grid:layout_gravity="fill_horizontal" />
|
||||||
</androidx.gridlayout.widget.GridLayout>
|
</androidx.gridlayout.widget.GridLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -370,6 +370,8 @@
|
|||||||
<string name="prefs_hr_alarm_activity">Heart rate alarm during sports activity</string>
|
<string name="prefs_hr_alarm_activity">Heart rate alarm during sports activity</string>
|
||||||
<string name="prefs_hr_alarm_low">Low limit</string>
|
<string name="prefs_hr_alarm_low">Low limit</string>
|
||||||
<string name="prefs_hr_alarm_high">High limit</string>
|
<string name="prefs_hr_alarm_high">High limit</string>
|
||||||
|
<string name="pref_workout_send_gps_title">Send GPS during workout</string>
|
||||||
|
<string name="pref_workout_send_gps_summary">Send the current GPS location to the band during a workout</string>
|
||||||
<!-- Auto export preferences -->
|
<!-- Auto export preferences -->
|
||||||
<string name="pref_header_auto_export">Auto export</string>
|
<string name="pref_header_auto_export">Auto export</string>
|
||||||
<string name="pref_title_auto_export_enabled">Auto export enabled</string>
|
<string name="pref_title_auto_export_enabled">Auto export enabled</string>
|
||||||
@ -1029,6 +1031,9 @@
|
|||||||
<string name="notification_channel_high_priority_name">High-priority</string>
|
<string name="notification_channel_high_priority_name">High-priority</string>
|
||||||
<string name="notification_channel_transfer_name">Data transfer</string>
|
<string name="notification_channel_transfer_name">Data transfer</string>
|
||||||
<string name="notification_channel_low_battery_name">Low battery</string>
|
<string name="notification_channel_low_battery_name">Low battery</string>
|
||||||
|
<string name="notification_channel_gps">GPS tracking</string>
|
||||||
|
<string name="notification_gps_title">Gadgetbridge GPS</string>
|
||||||
|
<string name="notification_gps_text">Sending GPS location to %1$d devices</string>
|
||||||
<string name="devicetype_amazfit_gts">Amazfit GTS</string>
|
<string name="devicetype_amazfit_gts">Amazfit GTS</string>
|
||||||
<string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
|
<string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
|
||||||
<string name="devicetype_sg2">Lemfo SG2</string>
|
<string name="devicetype_sg2">Lemfo SG2</string>
|
||||||
@ -1624,6 +1629,7 @@
|
|||||||
<string name="pref_device_action_fitness_app_control_start">Fitness App Tracking Start</string>
|
<string name="pref_device_action_fitness_app_control_start">Fitness App Tracking Start</string>
|
||||||
<string name="pref_device_action_fitness_app_control_stop">Fitness App Tracking Stop</string>
|
<string name="pref_device_action_fitness_app_control_stop">Fitness App Tracking Stop</string>
|
||||||
<string name="pref_device_action_fitness_app_control_toggle">Toggle Fitness App Tracking</string>
|
<string name="pref_device_action_fitness_app_control_toggle">Toggle Fitness App Tracking</string>
|
||||||
|
<string name="pref_device_action_phone_gps_location_listener_stop">GPS Location Listener Stop</string>
|
||||||
<!-- Translators: the ### indicate number of digits, keep intact -->
|
<!-- Translators: the ### indicate number of digits, keep intact -->
|
||||||
<string name="distance_format_meters">###m</string>
|
<string name="distance_format_meters">###m</string>
|
||||||
<!-- Translators: the ### indicate number of digits, keep intact -->
|
<!-- Translators: the ### indicate number of digits, keep intact -->
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<SwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:icon="@drawable/ic_gps_location"
|
||||||
|
android:key="workout_send_gps_to_band"
|
||||||
|
android:summary="@string/pref_workout_send_gps_summary"
|
||||||
|
android:title="@string/pref_workout_send_gps_title" />
|
||||||
|
</androidx.preference.PreferenceScreen>
|
Loading…
x
Reference in New Issue
Block a user