mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 09:33:06 +02:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b2126550e | |||
| 0e9f9b4db7 | |||
| 4dc524bf4f | |||
| 7455e591e1 | |||
| 9e4b1e0706 | |||
| 03dd0c2308 | |||
| 8eed8e9d8d | |||
| 94540606ef | |||
| 490141db2a | |||
| 892c56389b | |||
| a141bd77ad | |||
| e48b6054df | |||
| bb23bac7ec | |||
| fa362de67a | |||
| 76cba3f611 | |||
| 7ccb59beae | |||
| a70118b2dc | |||
| 738cbe150f | |||
| 7f40f67dc4 | |||
| f1b7adb6a0 | |||
| 1bd75754e4 | |||
| eaad33c23b | |||
| 9a0f927182 | |||
| 59aeec4fbc | |||
| df8306c2f8 | |||
| 5c6a4a71a4 | |||
| abbc61d419 | |||
| e2f598b282 | |||
| 41da2f5b53 | |||
| 8d7a8719bc | |||
| 8f5388edce | |||
| 81a78820cc | |||
| 2fe452c77c | |||
| 826cf0d468 | |||
| 6cd627e832 | |||
| 0ee4a4e72f | |||
| 150cbee95b | |||
| 47ceb297c9 | |||
| aed4af5736 | |||
| 333f7956b5 | |||
| d08552a07b | |||
| 330ca4a720 | |||
| 85171cdc46 | |||
| 5c1cb97c02 | |||
| 98a009e7c7 | |||
| 8f15382409 | |||
| 24212a226e | |||
| f80aaf00ec |
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
- name: Archive
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: OpenTracks-APK.apk
|
||||
path: ./build/outputs/apk/nightly/*.apk
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
java-version: '17'
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2.28.0
|
||||
uses: reactivecircus/android-emulator-runner@v2.29.0
|
||||
with:
|
||||
arch: x86_64
|
||||
api-level: 34
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Archive
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: code-coverage-report
|
||||
path: build/reports/androidTests/
|
||||
|
||||
+4
-3
@@ -64,8 +64,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 5554
|
||||
versionName "v4.9.6"
|
||||
versionCode 5602
|
||||
versionName "v4.10.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -131,8 +131,9 @@ dependencies {
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.core:core:1.12.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation 'androidx.mediarouter:mediarouter:1.6.0'
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
v4.9.6: OpenTracks
|
||||
|
||||
Oprava chyby:
|
||||
- Export: existující soubory jsou nyní kompletně přepisovány
|
||||
|
||||
Vývojář:
|
||||
- Předěláno zpracování barometrických dat
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.9.7: OpenTracks
|
||||
|
||||
Oprava chyby:
|
||||
- zabránění pádu při ukončení nahrávání (vzniknuvší ve verzi v4.9.6)
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.9.8: OpenTracks
|
||||
|
||||
Oprava chyby:
|
||||
- Senzor kadence znovu funguje (chyba vznikla ve verzi v4.9.6)
|
||||
@@ -0,0 +1,6 @@
|
||||
v4.9.9: OpenTracks
|
||||
|
||||
Oprava chyby:
|
||||
- Pád při používání senzoru BLE na konci záznamu (závod)
|
||||
Důvod: subsystém senzorů byl spuštěn dvakrát a pouze druhý z nich byl vypnut.
|
||||
- Při nahrávání jsou znovu zobrazeny názvy senzorů BLE
|
||||
@@ -0,0 +1,5 @@
|
||||
v4.9.7: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- prevent crash when stopping a recording(introduced in v4.9.6)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.9.8: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Cycling cadence sensor works again (introduced in v4.9.6)
|
||||
@@ -0,0 +1,6 @@
|
||||
v4.9.9: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Crash when using a BLE sensor on recording end (race condition)
|
||||
Reason: sensor subsystem was started twice and only the 2nd one was shutdown.
|
||||
- While recording, BLE sensor names are shown again
|
||||
@@ -0,0 +1,15 @@
|
||||
v4.10.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Support gain/loss via BLE Environmental Sensing (barometer)
|
||||
- Internal barometer can be turned off (e.g., if broken)
|
||||
- Material3 for preference switches
|
||||
- SplashScreen supports day/night mode
|
||||
- Bluetooth sensor settings: show selected sensor name (if cached by Android)
|
||||
|
||||
Bugfix:
|
||||
- Interval UI was misaligned
|
||||
|
||||
Developer:
|
||||
- Cleanup TrackRecordingServiceConnection
|
||||
- TrackRecordingService always starts as ForegroundService
|
||||
@@ -0,0 +1,7 @@
|
||||
v4.9.6: OpenTracks
|
||||
|
||||
Bugfix :
|
||||
- Exporteren: bestaande bestanden worden nu volledig overschreven
|
||||
|
||||
Ontwikkelaar:
|
||||
- Barometrische gegevensverwerking aangepast
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.9.7: OpenTracks
|
||||
|
||||
Bugfix :
|
||||
- voorkoming van crash bij het stoppen van een opname (geïntroduceerd in v4.9.6)
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.9.8: OpenTracks
|
||||
|
||||
Bugfix :
|
||||
- Fietscadanssensor werkt weer (geïntroduceerd in v4.9.6)
|
||||
@@ -0,0 +1,6 @@
|
||||
v4.9.9: OpenTracks
|
||||
|
||||
Bugfix :
|
||||
- Crash bij gebruik van een BLE-sensor aan het einde van opname(raceconditie)
|
||||
Reden: het subsysteem sensor is tweemaal gestart en alleen het tweede is uitgeschakeld.
|
||||
- Tijdens het opnemen worden namen van BLE-sensoren opnieuw weergegeven
|
||||
+1
-1
@@ -586,7 +586,7 @@ public class ExportImportTest {
|
||||
Mockito.when(barometer.getValue()).thenReturn(new AltitudeGainLoss(altitudeGain, altitudeGain));
|
||||
sensorDataSet.add(barometer);
|
||||
} else {
|
||||
sensorDataSet.add(new AggregatorBarometer(""));
|
||||
sensorDataSet.add(new AggregatorBarometer("test", null));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ public class AggregatorBarometerTest {
|
||||
@Test
|
||||
public void getAltitudeGainLoss_downhill() {
|
||||
// given
|
||||
AggregatorBarometer subject = new AggregatorBarometer("");
|
||||
AggregatorBarometer subject = new AggregatorBarometer("test", null);
|
||||
|
||||
// then
|
||||
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f});
|
||||
|
||||
@@ -75,7 +75,7 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/ThemeCustom">
|
||||
android:theme="@style/OpenTracksTheme">
|
||||
|
||||
<activity
|
||||
android:name=".publicapi.StartRecording"
|
||||
|
||||
@@ -28,7 +28,6 @@ import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.TrackEditBinding;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
/**
|
||||
@@ -44,7 +43,6 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
private static final String ICON_VALUE_KEY = "icon_value_key";
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private Track track;
|
||||
private ActivityType activityType;
|
||||
@@ -55,7 +53,6 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||
if (trackId == null) {
|
||||
Log.e(TAG, "invalid trackId");
|
||||
@@ -114,18 +111,6 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -73,7 +74,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||
|
||||
// The following are set in onCreate
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackRecordingServiceConnection recordingStatusConnection;
|
||||
private TrackListAdapter adapter;
|
||||
|
||||
private TrackListBinding viewBinding;
|
||||
@@ -138,13 +139,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
.observe(TrackListActivity.this, this::onGpsStatusChanged);
|
||||
|
||||
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||
|
||||
if (service.getGpsStatusObservable().getValue().isGpsStarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO Not cool to do this in a callback that might be called more than once!
|
||||
service.tryStartSensors();
|
||||
};
|
||||
|
||||
@Override
|
||||
@@ -154,7 +148,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
requestRequiredPermissions();
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
recordingStatusConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
|
||||
viewBinding.aggregatedStatsButton.setOnClickListener((view) -> startActivity(IntentUtils.newIntent(this, AggregatedStatisticsActivity.class)));
|
||||
viewBinding.sensorStartButton.setOnClickListener((view) -> {
|
||||
@@ -163,9 +157,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
} else {
|
||||
if (gpsStatusValue.isGpsStarted()) {
|
||||
trackRecordingServiceConnection.unbindAndStop(this);
|
||||
recordingStatusConnection.stopService(this);
|
||||
} else {
|
||||
trackRecordingServiceConnection.startAndBindWithCallback(this);
|
||||
TrackRecordingServiceConnection.execute(this, (service, connection) -> service.tryStartSensors());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -182,16 +176,15 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
|
||||
// Not Recording -> Recording
|
||||
Log.i(TAG, "Starting recording");
|
||||
updateGpsMenuItem(false, true);
|
||||
new TrackRecordingServiceConnection((service, connection) -> {
|
||||
TrackRecordingServiceConnection.execute(this, (service, connection) -> {
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||
newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
|
||||
connection.unbind(this);
|
||||
}).startAndBind(this, true);
|
||||
});
|
||||
});
|
||||
viewBinding.trackListFabAction.setOnLongClickListener((view) -> {
|
||||
if (!recordingStatus.isRecording()) {
|
||||
@@ -201,7 +194,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
// Recording -> Stop
|
||||
ActivityUtils.vibrate(this, 1000);
|
||||
updateGpsMenuItem(false, false);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
||||
recordingStatusConnection.stopRecording(TrackListActivity.this);
|
||||
viewBinding.trackListFabAction.setImageResource(R.drawable.ic_baseline_record_24);
|
||||
viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.red_dark));
|
||||
return true;
|
||||
@@ -220,7 +213,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
super.onStart();
|
||||
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
recordingStatusConnection.bind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -240,14 +233,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
super.onStop();
|
||||
|
||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
recordingStatusConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
viewBinding = null;
|
||||
trackRecordingServiceConnection = null;
|
||||
recordingStatusConnection = null;
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_resume_track) {
|
||||
new TrackRecordingServiceConnection((service, connection) -> {
|
||||
TrackRecordingServiceConnection.execute(this, (service, connection) -> {
|
||||
service.resumeTrack(trackId);
|
||||
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||
@@ -209,9 +209,8 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
|
||||
connection.unbind(this);
|
||||
finish();
|
||||
}).startAndBind(this, true);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
trackRecordingServiceConnection.bind(this);
|
||||
trackDataHub.start();
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
trackDataHub.setRecordingStatus(recordingStatus);
|
||||
}
|
||||
|
||||
trackRecordingServiceConnection.startAndBindWithCallback(this);
|
||||
trackRecordingServiceConnection.bind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -136,7 +136,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
}
|
||||
|
||||
private void resumeTrackAndFinish() {
|
||||
new TrackRecordingServiceConnection((service, connection) -> {
|
||||
TrackRecordingServiceConnection.execute(this, (service, connection) -> {
|
||||
service.resumeTrack(trackId);
|
||||
|
||||
Intent newIntent = IntentUtils.newIntent(TrackStoppedActivity.this, TrackRecordingActivity.class)
|
||||
@@ -144,9 +144,8 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
|
||||
connection.unbind(this);
|
||||
finish();
|
||||
}).startAndBind(this, true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -88,7 +88,7 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
trackRecordingServiceConnection.bind(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,8 +37,7 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
|
||||
|
||||
if (PreferencesUtils.isPublicAPIenabled()) {
|
||||
Log.i(TAG, "Received and trying to execute requested action.");
|
||||
new TrackRecordingServiceConnection(serviceConnectedCallback)
|
||||
.startAndBind(this, isStartServiceForeground());
|
||||
TrackRecordingServiceConnection.execute(this, serviceConnectedCallback);
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show();
|
||||
Log.w(TAG, "Public API is disabled; ignoring request.");
|
||||
@@ -46,10 +45,6 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isStartServiceForeground() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void execute(TrackRecordingService service);
|
||||
|
||||
protected abstract boolean isPostExecuteStopService();
|
||||
|
||||
@@ -62,9 +62,4 @@ public class StartRecording extends AbstractAPIActivity {
|
||||
protected boolean isPostExecuteStopService() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isStartServiceForeground() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
@@ -33,38 +34,40 @@ import androidx.annotation.NonNull;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
|
||||
/**
|
||||
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
public class BluetoothConnectionManager {
|
||||
public class BluetoothConnectionManager implements Driver {
|
||||
|
||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
||||
|
||||
private final SensorManager.SensorDataChangedObserver observer;
|
||||
|
||||
private final SensorHandlerInterface sensorHandler;
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||
switch (newState) {
|
||||
case BluetoothProfile.STATE_CONNECTING ->
|
||||
Log.i(TAG, "Connecting to sensor: " + gatt.getDevice());
|
||||
Log.i(TAG, gatt.getDevice() + ": connecting to sensor");
|
||||
case BluetoothProfile.STATE_CONNECTED -> {
|
||||
Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services.");
|
||||
Log.i(TAG, gatt.getDevice() + ": connected to sensor; discovering services");
|
||||
gatt.discoverServices();
|
||||
}
|
||||
case BluetoothProfile.STATE_DISCONNECTING ->
|
||||
Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice());
|
||||
Log.i(TAG, gatt.getDevice() + ": disconnecting from sensor: ");
|
||||
case BluetoothProfile.STATE_DISCONNECTED -> {
|
||||
//This is also triggered, if no connection was established (ca. 30s)
|
||||
Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect");
|
||||
Log.i(TAG, gatt.getDevice() + ": disconnected from sensor: trying to reconnect");
|
||||
if (gatt.connect()) {
|
||||
Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice());
|
||||
Log.e(TAG, gatt.getDevice() + ": could not trigger reconnect for sensor");
|
||||
}
|
||||
clearData();
|
||||
}
|
||||
@@ -84,13 +87,13 @@ public class BluetoothConnectionManager {
|
||||
}
|
||||
|
||||
if (gattService == null) {
|
||||
Log.e(TAG, "Could not get gattService for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement);
|
||||
Log.e(TAG, gatt.getDevice() + ": could not get gattService for serviceUUID=" + serviceMeasurement);
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.measurementUUID());
|
||||
if (characteristic == null) {
|
||||
Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
|
||||
Log.e(TAG, gatt.getDevice() + ": could not get BluetoothCharacteristic for serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
|
||||
return;
|
||||
}
|
||||
gatt.setCharacteristicNotification(characteristic, true);
|
||||
@@ -102,21 +105,25 @@ public class BluetoothConnectionManager {
|
||||
return;
|
||||
}
|
||||
|
||||
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||
gatt.writeDescriptor(descriptor);
|
||||
|
||||
if (!descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
|
||||
Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID could not be set to ENABLE_NOTIFICATION_VALUE");
|
||||
}
|
||||
if (!gatt.writeDescriptor(descriptor)) {
|
||||
Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID descriptor could not be written");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||
UUID serviceUUID = characteristic.getService().getUuid();
|
||||
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress() + " with service " + serviceUUID + " and characteristics " + characteristic.getUuid());
|
||||
BluetoothDevice device = gatt.getDevice();
|
||||
Log.d(TAG, device + ": Received data with service " + serviceUUID + " and characteristics " + characteristic.getUuid());
|
||||
Optional<ServiceMeasurementUUID> serviceMeasurementUUID = sensorHandler.getServices()
|
||||
.stream()
|
||||
.filter(s -> s.serviceUUID().equals(characteristic.getService().getUuid()))
|
||||
.findFirst();
|
||||
if (serviceMeasurementUUID.isEmpty()) {
|
||||
Log.e(TAG, "Unknown service UUID; not supported?");
|
||||
Log.e(TAG, device + ": Unknown service UUID; not supported?");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,43 +131,86 @@ public class BluetoothConnectionManager {
|
||||
}
|
||||
};
|
||||
|
||||
BluetoothConnectionManager(SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) {
|
||||
BluetoothConnectionManager(BluetoothAdapter bluetoothAdapter, SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) {
|
||||
this.bluetoothAdapter = bluetoothAdapter;
|
||||
this.observer = observer;
|
||||
this.sensorHandler = sensorHandler;
|
||||
}
|
||||
|
||||
synchronized void connect(Context context, Handler handler, @NonNull BluetoothDevice device) {
|
||||
if (bluetoothGatt != null) {
|
||||
@Override
|
||||
public synchronized void connect(Context context, Handler handler, @NonNull String address) {
|
||||
if (!isBluetoothEnabled()) {
|
||||
Log.w(TAG, "Bluetooth not enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SensorType.NONE.getPreferenceValue().equals(address)) {
|
||||
Log.w(TAG, "NONE: going to disconnect");
|
||||
if (isConnected()) {
|
||||
disconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConnected()) {
|
||||
Log.w(TAG, "Already connected; ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Connecting to: " + device);
|
||||
if (isSameBluetoothDevice(address)) {
|
||||
return;
|
||||
} else {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
BluetoothDevice device;
|
||||
try {
|
||||
device = bluetoothAdapter.getRemoteDevice(address);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, address + ": Unable to get remote device for", e);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, device + ": trying to connect");
|
||||
|
||||
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler);
|
||||
|
||||
observer.onConnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
|
||||
observer.onConnect(sensorHandler.createEmptySensorData(device.getAddress(), device.getName()));
|
||||
}
|
||||
|
||||
private synchronized void clearData() {
|
||||
observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
|
||||
observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress(), bluetoothGatt.getDevice().getName()));
|
||||
}
|
||||
|
||||
synchronized void disconnect() {
|
||||
if (bluetoothGatt == null) {
|
||||
Log.w(TAG, "Cannot disconnect if not connected.");
|
||||
@Override
|
||||
public synchronized void disconnect() {
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "Not connected; no need to re-connect.");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, bluetoothGatt.getDevice() + ": start disconnect");
|
||||
bluetoothGatt.disconnect();
|
||||
bluetoothGatt.close();
|
||||
clearData();
|
||||
Log.i(TAG, bluetoothGatt.getDevice() + ": disconnect finished");
|
||||
bluetoothGatt = null;
|
||||
}
|
||||
|
||||
synchronized boolean isSameBluetoothDevice(String address) {
|
||||
private synchronized boolean isSameBluetoothDevice(String address) {
|
||||
if (bluetoothGatt == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return address.equals(bluetoothGatt.getDevice().getAddress());
|
||||
}
|
||||
|
||||
|
||||
private boolean isBluetoothEnabled() {
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return bluetoothGatt != null;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-3
@@ -1,12 +1,15 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
|
||||
public class BluetoothHandlerBarometricPressure implements SensorHandlerInterface {
|
||||
@@ -22,15 +25,23 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac
|
||||
}
|
||||
|
||||
@Override
|
||||
public Aggregator<?, ?> createEmptySensorData(String address) {
|
||||
return null; //TODO
|
||||
public Aggregator<?, ?> createEmptySensorData(String address, String name) {
|
||||
return new AggregatorBarometer(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
//TODO
|
||||
AtmosphericPressure value = parseEnvironmentalSensing(characteristic);
|
||||
if (value == null) return;
|
||||
Log.i("ZXCV", "VALUE: " + value);
|
||||
observer.onChange(new Raw<>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoding:
|
||||
* org.bluetooth.service.environmental_sensing.xml
|
||||
* org.bluetooth.characteristic.pressure.xml
|
||||
*/
|
||||
public static AtmosphericPressure parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] raw = characteristic.getValue();
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ public class BluetoothHandlerCyclingCadence implements SensorHandlerInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorCyclingCadence createEmptySensorData(String address) {
|
||||
return new AggregatorCyclingCadence(address);
|
||||
public AggregatorCyclingCadence createEmptySensorData(String address, String name) {
|
||||
return new AggregatorCyclingCadence(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorCyclingDistanceSpeed createEmptySensorData(String address) {
|
||||
return new AggregatorCyclingDistanceSpeed(address);
|
||||
public AggregatorCyclingDistanceSpeed createEmptySensorData(String address, String name) {
|
||||
return new AggregatorCyclingDistanceSpeed(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ public class BluetoothHandlerManagerCyclingPower implements SensorHandlerInterfa
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorCyclingPower createEmptySensorData(String address) {
|
||||
return new AggregatorCyclingPower(address);
|
||||
public AggregatorCyclingPower createEmptySensorData(String address, String name) {
|
||||
return new AggregatorCyclingPower(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -36,8 +36,8 @@ public class BluetoothHandlerManagerHeartRate implements SensorHandlerInterface
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorHeartRate createEmptySensorData(String address) {
|
||||
return new AggregatorHeartRate(address);
|
||||
public AggregatorHeartRate createEmptySensorData(String address, String name) {
|
||||
return new AggregatorHeartRate(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -29,8 +29,8 @@ public class BluetoothHandlerRunningSpeedAndCadence implements SensorHandlerInte
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatorRunning createEmptySensorData(String address) {
|
||||
return new AggregatorRunning(address);
|
||||
public AggregatorRunning createEmptySensorData(String address, String name) {
|
||||
return new AggregatorRunning(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -30,29 +28,20 @@ import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
/**
|
||||
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
|
||||
* <p>
|
||||
* Note: should only be instantiated once.
|
||||
* <p>
|
||||
* TODO: listen for Bluetooth enabled/disabled events.
|
||||
* <p>
|
||||
* TODO: In case, a cycling (Cadence and Speed) sensor reports both values, testing is required.
|
||||
* We establish two GATT separate GATT connections (as if two different sensors were used).
|
||||
* However, it is not clear if this is allowed.
|
||||
* Even if this works, it is not clear what happens if a user (while recording) changes one of the sensors in the settings as this will trigger a disconnect of one GATT.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class BluetoothRemoteSensorManager implements SensorConnector, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
|
||||
|
||||
public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(50); //TODO
|
||||
public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5);
|
||||
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private final Context context;
|
||||
private final Handler handler;
|
||||
private boolean started = false;
|
||||
@@ -66,14 +55,13 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref
|
||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @Nullable SensorManager.SensorDataChangedObserver observer) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
||||
|
||||
this.heartRate = new BluetoothConnectionManager(observer, new BluetoothHandlerManagerHeartRate());
|
||||
this.cyclingCadence = new BluetoothConnectionManager(observer, new BluetoothHandlerCyclingDistanceSpeed());
|
||||
this.cyclingSpeed = new BluetoothConnectionManager(observer, new BluetoothHandlerCyclingDistanceSpeed());
|
||||
this.cyclingPower = new BluetoothConnectionManager(observer, new BluetoothHandlerManagerCyclingPower());
|
||||
this.runningSpeedAndCadence = new BluetoothConnectionManager(observer, new BluetoothHandlerRunningSpeedAndCadence());
|
||||
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
||||
this.heartRate = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerManagerHeartRate());
|
||||
this.cyclingCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingCadence());
|
||||
this.cyclingSpeed = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingDistanceSpeed());
|
||||
this.cyclingPower = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerManagerCyclingPower());
|
||||
this.runningSpeedAndCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerRunningSpeedAndCadence());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,39 +83,8 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref
|
||||
started = false;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||
}
|
||||
|
||||
private synchronized void connect(BluetoothConnectionManager connectionManager, String address) {
|
||||
if (!isEnabled()) {
|
||||
Log.w(TAG, "Bluetooth not enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isBluetoothSensorAddressNone(address)) {
|
||||
Log.w(TAG, "No Bluetooth address.");
|
||||
connectionManager.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there is an ongoing connection; if yes, check if the address changed.
|
||||
if (connectionManager.isSameBluetoothDevice(address)) {
|
||||
return;
|
||||
} else {
|
||||
connectionManager.disconnect();
|
||||
}
|
||||
if (!PermissionRequester.BLUETOOTH.hasPermission(context)) {
|
||||
Log.w(TAG, "BLUETOOTH_SCAN and/or BLUETOOTH_CONNECT not granted; not connecting.");
|
||||
}
|
||||
|
||||
Log.i(TAG, "Connecting to bluetooth address: " + address);
|
||||
try {
|
||||
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
|
||||
connectionManager.connect(context, handler, device);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Unable to get remote device for: " + address, e);
|
||||
}
|
||||
connectionManager.connect(context, handler, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
||||
@@ -16,31 +17,61 @@ public class GainManager implements SensorConnector {
|
||||
|
||||
private static final String TAG = GainManager.class.getSimpleName();
|
||||
|
||||
private final BarometerInternal driver;
|
||||
|
||||
private final SensorManager.SensorDataChangedObserver listener;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
connect();
|
||||
};
|
||||
|
||||
private Context context;
|
||||
private Handler handler;
|
||||
private Driver driver;
|
||||
|
||||
public GainManager(SensorManager.SensorDataChangedObserver listener) {
|
||||
this.listener = listener;
|
||||
driver = new BarometerInternal();
|
||||
}
|
||||
|
||||
public void start(Context context, Handler handler) {
|
||||
driver.connect(context, handler, this);
|
||||
|
||||
if (driver.isConnected()) {
|
||||
listener.onConnect(new AggregatorBarometer("internal"));
|
||||
}
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
Log.d(TAG, "Stop");
|
||||
this.context = null;
|
||||
this.handler = null;
|
||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
driver.disconnect(context);
|
||||
listener.onDisconnect(new AggregatorBarometer("internal"));
|
||||
onDisconnect();
|
||||
}
|
||||
|
||||
public void onSensorValueChanged(AtmosphericPressure currentSensorValue) {
|
||||
listener.onChange(new Raw<>(currentSensorValue));
|
||||
private void connect() {
|
||||
onDisconnect();
|
||||
|
||||
String address = PreferencesUtils.getBarometerSensorAddress();
|
||||
switch (PreferencesUtils.getSensorType(address)) {
|
||||
case NONE -> driver = null;
|
||||
case INTERNAL -> driver = new BarometerInternal(listener);
|
||||
case REMOTE -> driver =
|
||||
new BluetoothConnectionManager(
|
||||
BluetoothUtils.getAdapter(context),
|
||||
listener,
|
||||
new BluetoothHandlerBarometricPressure()
|
||||
);
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
if (driver != null) {
|
||||
driver.connect(context, handler, address);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDisconnect() {
|
||||
if (driver == null) return;
|
||||
|
||||
driver.disconnect();
|
||||
listener.onDisconnect(new AggregatorBarometer("GainManager", null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
||||
}
|
||||
|
||||
public void start(Context context, Handler handler) {
|
||||
if (gpsManager != null) {
|
||||
throw new RuntimeException("SensorManager cannot be started twice; stop first.");
|
||||
}
|
||||
|
||||
gpsManager = new GPSManager(observer); //TODO Pass listener
|
||||
altitudeSumManager = new GainManager(listener);
|
||||
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, listener);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
public enum SensorType {
|
||||
NONE("NONE"),
|
||||
INTERNAL("INTERNAL"),
|
||||
REMOTE("*");
|
||||
|
||||
private final String preferenceValue;
|
||||
|
||||
SensorType(String preferenceValue) {
|
||||
this.preferenceValue = preferenceValue;
|
||||
}
|
||||
|
||||
public String getPreferenceValue() {
|
||||
return preferenceValue;
|
||||
}
|
||||
}
|
||||
@@ -4,61 +4,79 @@ import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.GainManager;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
|
||||
public class BarometerInternal implements SensorEventListener {
|
||||
public class BarometerInternal implements Driver {
|
||||
|
||||
private static final String TAG = BarometerInternal.class.getSimpleName();
|
||||
|
||||
private static final int SAMPLING_PERIOD = (int) TimeUnit.SECONDS.toMicros(5);
|
||||
|
||||
private GainManager observer;
|
||||
private final SensorManager.SensorDataChangedObserver listener;
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "Not connected to sensor, cannot process data.");
|
||||
return;
|
||||
private Context context;
|
||||
|
||||
private final SensorEventListener sensorEventListener = new SensorEventListener() {
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "Not connected to sensor, cannot process data.");
|
||||
return;
|
||||
}
|
||||
|
||||
listener.onChange(new Raw<>(AtmosphericPressure.ofHPA(event.values[0])));
|
||||
}
|
||||
|
||||
observer.onSensorValueChanged(AtmosphericPressure.ofHPA(event.values[0]));
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
Log.w(TAG, "Sensor accuracy changes are (currently) ignored.");
|
||||
}
|
||||
};
|
||||
|
||||
public BarometerInternal(@NonNull SensorManager.SensorDataChangedObserver listener) {
|
||||
this.listener = listener;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
Log.w(TAG, "Sensor accuracy changes are (currently) ignored.");
|
||||
}
|
||||
public void connect(Context context, Handler handler, String addressIgnored) {
|
||||
|
||||
public void connect(Context context, Handler handler, GainManager observer) {
|
||||
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
if (pressureSensor == null) {
|
||||
Log.w(TAG, "No pressure sensor available.");
|
||||
this.observer = null;
|
||||
}
|
||||
|
||||
if (sensorManager.registerListener(this, pressureSensor, SAMPLING_PERIOD, handler)) {
|
||||
this.observer = observer;
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect(context);
|
||||
}
|
||||
if (sensorManager.registerListener(sensorEventListener, pressureSensor, SAMPLING_PERIOD, handler)) {
|
||||
this.context = context;
|
||||
listener.onConnect(new AggregatorBarometer("internal", null));
|
||||
return;
|
||||
}
|
||||
|
||||
public void disconnect(Context context) {
|
||||
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
sensorManager.unregisterListener(this);
|
||||
observer = null;
|
||||
disconnect();
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return observer != null;
|
||||
return context != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (!isConnected()) return;
|
||||
|
||||
android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
sensorManager.unregisterListener(sensorEventListener);
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
public interface Driver {
|
||||
void connect(Context context, Handler handler, String address);
|
||||
|
||||
boolean isConnected();
|
||||
|
||||
void disconnect();
|
||||
}
|
||||
@@ -11,8 +11,8 @@ public class AggregatorBarometer extends Aggregator<AtmosphericPressure, Altitud
|
||||
|
||||
private AtmosphericPressure lastAcceptedSensorValue;
|
||||
|
||||
public AggregatorBarometer(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
public AggregatorBarometer(String sensorAddress, String sensorName) {
|
||||
super(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-4
@@ -14,10 +14,6 @@ public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCycling
|
||||
|
||||
private final String TAG = AggregatorCyclingCadence.class.getSimpleName();
|
||||
|
||||
public AggregatorCyclingCadence(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
}
|
||||
|
||||
public AggregatorCyclingCadence(String sensorAddress, String sensorName) {
|
||||
super(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
-4
@@ -17,10 +17,6 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerC
|
||||
|
||||
private Distance wheelCircumference;
|
||||
|
||||
public AggregatorCyclingDistanceSpeed(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
}
|
||||
|
||||
public AggregatorCyclingDistanceSpeed(String sensorAddress, String sensorName) {
|
||||
super(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,6 @@ import de.dennisguse.opentracks.data.models.Power;
|
||||
|
||||
public class AggregatorCyclingPower extends Aggregator<Power, Power> {
|
||||
|
||||
public AggregatorCyclingPower(String address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public AggregatorCyclingPower(String name, String address) {
|
||||
super(name, address);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,6 @@ import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
|
||||
public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
|
||||
|
||||
public AggregatorHeartRate(String address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public AggregatorHeartRate(String name, String address) {
|
||||
super(name, address);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ public final class AggregatorRunning extends Aggregator<BluetoothHandlerRunningS
|
||||
|
||||
private static final String TAG = AggregatorRunning.class.getSimpleName();
|
||||
|
||||
public AggregatorRunning(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
}
|
||||
|
||||
public AggregatorRunning(String sensorAddress, String sensorName) {
|
||||
super(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ public final class SensorDataSet {
|
||||
}
|
||||
|
||||
private void set(@NonNull Aggregator<?, ?> type, @Nullable Aggregator<?, ?> sensorData) {
|
||||
Log.i(TAG, "Setting aggregator " + type.getClass().getCanonicalName());
|
||||
Log.i(TAG, "Setting aggregator " + type.getClass().getCanonicalName() + " to " + sensorData);
|
||||
|
||||
if (type instanceof AggregatorHeartRate) {
|
||||
heartRate = (AggregatorHeartRate) sensorData;
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ public interface SensorHandlerInterface {
|
||||
|
||||
List<ServiceMeasurementUUID> getServices();
|
||||
|
||||
Aggregator<?, ?> createEmptySensorData(String address);
|
||||
Aggregator<?, ?> createEmptySensorData(String address, String name);
|
||||
|
||||
void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
@@ -111,7 +112,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
recordingDataObservable = new MutableLiveData<>(NOT_RECORDING);
|
||||
|
||||
trackPointCreator = new TrackPointCreator(this);
|
||||
trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, this , handler);
|
||||
trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, this, handler);
|
||||
|
||||
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
|
||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||
@@ -125,6 +126,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
if (isRecording()) {
|
||||
endCurrentTrack();
|
||||
}
|
||||
if (isSensorStarted()) {
|
||||
stopSensors();
|
||||
}
|
||||
|
||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(this);
|
||||
|
||||
@@ -163,6 +167,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
Log.w(TAG, "Ignore startNewTrack. Already recording.");
|
||||
return null;
|
||||
}
|
||||
Log.i(TAG, "startNewTrack");
|
||||
|
||||
// Set recording status
|
||||
Track.Id trackId = trackRecordingManager.startNewTrack();
|
||||
@@ -177,6 +182,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
Log.w(TAG, "Cannot resume a non-existing track.");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "resumeTrack");
|
||||
|
||||
updateRecordingStatus(RecordingStatus.record(trackId));
|
||||
|
||||
@@ -193,27 +199,30 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
}
|
||||
|
||||
public void tryStartSensors() {
|
||||
if (isRecording()) return;
|
||||
if (isSensorStarted()) return;
|
||||
|
||||
Log.i(TAG, "tryStartSensors");
|
||||
|
||||
startSensors();
|
||||
}
|
||||
|
||||
private void startSensors() {
|
||||
private synchronized void startSensors() {
|
||||
if (isSensorStarted()) {
|
||||
Log.i(TAG, "sensors already started; skipping");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "startSensors");
|
||||
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
||||
trackPointCreator.start(this, handler);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
if (!PermissionRequester.RECORDING.hasPermission(this)) {
|
||||
Toast.makeText(this, R.string.permission_recording_failed, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
if (!PermissionRequester.RECORDING.hasPermission(this)) {
|
||||
Toast.makeText(this, R.string.permission_recording_failed, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
} else {
|
||||
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
|
||||
}
|
||||
|
||||
ServiceCompat.startForeground(this, TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
}
|
||||
|
||||
public void endCurrentTrack() {
|
||||
@@ -239,6 +248,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
stopForeground(true);
|
||||
notificationManager.cancelNotification();
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
gpsStatusObservable.postValue(STATUS_GPS_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -255,7 +265,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
|
||||
@Override
|
||||
public void newGpsStatus(GpsStatusValue gpsStatusValue) {
|
||||
Log.e(TAG, "newGpsStatus: " + gpsStatusValue.message);
|
||||
Log.i(TAG, "newGpsStatus: " + gpsStatusValue.message);
|
||||
|
||||
if (notificationManager == null) {
|
||||
|
||||
@@ -336,6 +346,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
return recordingStatus.isRecording();
|
||||
}
|
||||
|
||||
private boolean isSensorStarted() {
|
||||
return wakeLock != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
|
||||
voiceAnnouncementManager.onSharedPreferenceChanged(sharedPreferences, key);
|
||||
|
||||
+54
-102
@@ -42,7 +42,7 @@ import de.dennisguse.opentracks.data.models.Marker;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackRecordingServiceConnection implements ServiceConnection, DeathRecipient {
|
||||
public class TrackRecordingServiceConnection {
|
||||
|
||||
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
|
||||
|
||||
@@ -50,9 +50,29 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
|
||||
private TrackRecordingService trackRecordingService;
|
||||
|
||||
public TrackRecordingServiceConnection() {
|
||||
callback = null;
|
||||
}
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
Log.i(TAG, "Connected to the service: " + service);
|
||||
try {
|
||||
service.linkToDeath(deathRecipient, 0);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to bind a death recipient.", e);
|
||||
}
|
||||
setTrackRecordingService(((TrackRecordingService.Binder) service).getService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
Log.i(TAG, "Disconnected from the service.");
|
||||
setTrackRecordingService(null);
|
||||
}
|
||||
};
|
||||
|
||||
private final DeathRecipient deathRecipient = () -> {
|
||||
Log.d(TAG, "Service died.");
|
||||
setTrackRecordingService(null);
|
||||
};
|
||||
|
||||
public TrackRecordingServiceConnection(@NonNull Callback callback) {
|
||||
this.callback = callback;
|
||||
@@ -60,58 +80,13 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
|
||||
public void bind(@NonNull Context context) {
|
||||
if (trackRecordingService != null) {
|
||||
return;
|
||||
}
|
||||
context.bindService(new Intent(context, TrackRecordingService.class), this, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts and binds the service.
|
||||
*
|
||||
* @param foreground is the service expected to call `startForeground()`?
|
||||
*/
|
||||
public void startAndBind(Context context, boolean foreground) {
|
||||
if (trackRecordingService != null) {
|
||||
// Service is already started and bound.
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Starting the service.");
|
||||
if (foreground) {
|
||||
ContextCompat.startForegroundService(context, new Intent(context, TrackRecordingService.class));
|
||||
} else {
|
||||
context.startService(new Intent(context, TrackRecordingService.class));
|
||||
}
|
||||
|
||||
startConnection(context);
|
||||
}
|
||||
|
||||
//TODO There should be a better way to implement this.
|
||||
|
||||
/**
|
||||
* Triggers the onConnected() callback even if already connected.
|
||||
*/
|
||||
//TODO Check if this is actually needed as it is used to re-connect from Activities in onResume by using a LiveData; might be obsolete. If not, there should be a better way to implement this.
|
||||
@Deprecated
|
||||
public void startAndBindWithCallback(Context context) {
|
||||
if (trackRecordingService == null) {
|
||||
startAndBind(context, false);
|
||||
return;
|
||||
}
|
||||
if (callback != null) {
|
||||
callback.onConnected(trackRecordingService, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void startConnection(@NonNull Context context) {
|
||||
if (trackRecordingService != null) {
|
||||
// Service is already started and bound.
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Binding the service.");
|
||||
int flags = BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0;
|
||||
context.bindService(new Intent(context, TrackRecordingService.class), this, flags);
|
||||
context.bindService(new Intent(context, TrackRecordingService.class), serviceConnection, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,75 +95,45 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
//TODO This is often called for one-shot operations and should be refactored as unbinding is required.
|
||||
public void unbind(Context context) {
|
||||
try {
|
||||
context.unbindService(this);
|
||||
context.unbindService(serviceConnection);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Means not bound to the service. OK to ignore.
|
||||
}
|
||||
setTrackRecordingService(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds and stops the service.
|
||||
*/
|
||||
public void unbindAndStop(Context context) {
|
||||
unbind(context);
|
||||
public void stopService(Context context) {
|
||||
context.stopService(new Intent(context, TrackRecordingService.class));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TrackRecordingService getServiceIfBound() {
|
||||
return trackRecordingService;
|
||||
public void unbindAndStop(Context context) {
|
||||
unbind(context);
|
||||
stopService(context);
|
||||
}
|
||||
|
||||
private void setTrackRecordingService(TrackRecordingService value) {
|
||||
trackRecordingService = value;
|
||||
if (callback != null) {
|
||||
if (value != null) {
|
||||
callback.onConnected(value, this);
|
||||
} else {
|
||||
callback.onDisconnected();
|
||||
}
|
||||
if (value != null) {
|
||||
callback.onConnected(value, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
Log.i(TAG, "Connected to the service.");
|
||||
try {
|
||||
service.linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to bind a death recipient.", e);
|
||||
}
|
||||
setTrackRecordingService(((TrackRecordingService.Binder) service).getService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
Log.i(TAG, "Disconnected from the service.");
|
||||
setTrackRecordingService(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Log.d(TAG, "Service died.");
|
||||
setTrackRecordingService(null);
|
||||
}
|
||||
|
||||
//TODO Move to some other place; not needed here.
|
||||
@Nullable
|
||||
public Marker.Id addMarker(Context context, String name, String category, String description, String photoUrl) {
|
||||
TrackRecordingService trackRecordingService = getServiceIfBound();
|
||||
if (trackRecordingService == null) {
|
||||
Log.d(TAG, "Unable to add marker, no track recording service");
|
||||
} else {
|
||||
try {
|
||||
Marker.Id marker = trackRecordingService.insertMarker(name, category, description, photoUrl);
|
||||
if (marker != null) {
|
||||
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
|
||||
return marker;
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(TAG, "Unable to add marker.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Marker.Id marker = trackRecordingService.insertMarker(name, category, description, photoUrl);
|
||||
if (marker != null) {
|
||||
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
|
||||
return marker;
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(TAG, "Unable to add marker.", e);
|
||||
}
|
||||
|
||||
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
|
||||
@@ -196,7 +141,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
}
|
||||
|
||||
public void stopRecording(@NonNull Context context) {
|
||||
TrackRecordingService trackRecordingService = getServiceIfBound();
|
||||
if (trackRecordingService == null) {
|
||||
Log.e(TAG, "TrackRecordingService not connected.");
|
||||
} else {
|
||||
@@ -206,9 +150,17 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onConnected(TrackRecordingService service, TrackRecordingServiceConnection connection);
|
||||
void onConnected(TrackRecordingService service, TrackRecordingServiceConnection self);
|
||||
}
|
||||
|
||||
default void onDisconnected() {
|
||||
}
|
||||
public static void execute(Context context, Callback callback) {
|
||||
Callback withUnbind = (service, connection) -> {
|
||||
callback.onConnected(service, connection);
|
||||
connection.unbind(context);
|
||||
};
|
||||
new TrackRecordingServiceConnection(withUnbind)
|
||||
.bind(context);
|
||||
|
||||
ContextCompat.startForegroundService(context, new Intent(context, TrackRecordingService.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||
import de.dennisguse.opentracks.sensors.SensorType;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||
@@ -249,11 +250,18 @@ public class PreferencesUtils {
|
||||
}
|
||||
|
||||
private static String getBluetoothSensorAddressNone() {
|
||||
return resources.getString(R.string.sensor_type_value_none);
|
||||
return SensorType.NONE.getPreferenceValue();
|
||||
}
|
||||
|
||||
public static boolean isBluetoothSensorAddressNone(String currentValue) {
|
||||
return getBluetoothSensorAddressNone().equals(currentValue);
|
||||
public static SensorType getSensorType(String address) {
|
||||
return Arrays.stream(SensorType.values())
|
||||
.filter(it -> it.getPreferenceValue().equals(address))
|
||||
.findFirst()
|
||||
.orElse(SensorType.REMOTE);
|
||||
}
|
||||
|
||||
public static String getBarometerSensorAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_pressure_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static String getBluetoothHeartRateSensorAddress() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.dennisguse.opentracks.settings.bluetooth;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -7,10 +8,13 @@ import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
public class BluetoothLeAdapter extends BaseAdapter {
|
||||
|
||||
/**
|
||||
@@ -50,22 +54,30 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
return currentView;
|
||||
}
|
||||
|
||||
public void add(String name, String address) {
|
||||
Device device = new Device(name, address);
|
||||
/**
|
||||
* @return Data changed?
|
||||
*/
|
||||
public boolean add(String address, String name) {
|
||||
Device device = new Device(address, name);
|
||||
if (!devices.contains(device)) {
|
||||
devices.add(new Device(name, address));
|
||||
devices.add(device);
|
||||
return true;
|
||||
} else {
|
||||
for (Device currentDevice : devices) {
|
||||
if (currentDevice.getAddress().equals(address)) {
|
||||
currentDevice.setName(name);
|
||||
if (currentDevice.address.equals(address)) {
|
||||
currentDevice.name = name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void add(BluetoothDevice bluetoothDevice) {
|
||||
add(bluetoothDevice.getName(), bluetoothDevice.getAddress());
|
||||
public void addAll(List<BluetoothDevice> bluetoothDevices) {
|
||||
boolean dataSetChanged = bluetoothDevices.stream()
|
||||
.anyMatch(bluetoothDevice -> add(bluetoothDevice.getAddress(), bluetoothDevice.getName()));
|
||||
|
||||
if (dataSetChanged) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public Device get(int index) {
|
||||
@@ -73,19 +85,14 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
}
|
||||
|
||||
public static class Device {
|
||||
private String name;
|
||||
|
||||
@NonNull
|
||||
private final String address;
|
||||
private String name;
|
||||
|
||||
public Device(String name, String address) {
|
||||
this.name = name;
|
||||
Device(@NonNull String address, String name) {
|
||||
Objects.requireNonNull(address);
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getNameOrAddress() {
|
||||
return name != null ? name : getAddress();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -93,16 +100,21 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getNameOrAddress() {
|
||||
return name != null ? name : address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Device device)) return false;
|
||||
|
||||
return address.equals(device.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, address);
|
||||
return Objects.hash(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -5,6 +5,8 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerManagerCyclingPower;
|
||||
|
||||
public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreference {
|
||||
@@ -27,6 +29,6 @@ public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreferen
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerManagerCyclingPower.CYCLING_POWER);
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerManagerCyclingPower.CYCLING_POWER));
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -5,6 +5,8 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
||||
|
||||
public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreference {
|
||||
@@ -27,6 +29,6 @@ public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreferen
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE);
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE));
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package de.dennisguse.opentracks.settings.bluetooth;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorManager;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerBarometricPressure;
|
||||
|
||||
public class BluetoothLePressurePreference extends BluetoothLeSensorPreference {
|
||||
|
||||
public BluetoothLePressurePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public BluetoothLePressurePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public BluetoothLePressurePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public BluetoothLePressurePreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
SensorManager sensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
|
||||
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
boolean hasInternalSensor = pressureSensor != null;
|
||||
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog
|
||||
.newInstance(getKey(), List.of(BluetoothHandlerBarometricPressure.BAROMETRIC_PRESSURE), hasInternalSensor);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -5,6 +5,8 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
||||
|
||||
public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSensorPreference {
|
||||
@@ -27,6 +29,6 @@ public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSens
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE);
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE));
|
||||
}
|
||||
}
|
||||
|
||||
+44
-22
@@ -1,6 +1,8 @@
|
||||
package de.dennisguse.opentracks.settings.bluetooth;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.le.BluetoothLeScanner;
|
||||
import android.bluetooth.le.ScanCallback;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
@@ -21,12 +23,12 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||
import de.dennisguse.opentracks.sensors.SensorType;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
@@ -35,14 +37,18 @@ import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
* Preference to select a discoverable Bluetooth LE device.
|
||||
* Based upon ListPreference.
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
|
||||
private static final String TAG = BluetoothLeSensorPreference.class.getSimpleName();
|
||||
|
||||
private static final String ARG_BLE_SERVICE_UUIDS = "bluetoothUUID";
|
||||
private static final String ARG_INCLUDE_INTERNAL = "supportsInternal";
|
||||
|
||||
private static final int DEVICE_NONE_RESOURCEID = R.string.value_none;
|
||||
|
||||
private static final int SENSOR_INTERNAL_RESOURCEID = R.string.value_internal_sensor;
|
||||
|
||||
public BluetoothLeSensorPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
@@ -62,11 +68,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
private String value;
|
||||
private boolean valueSet = false;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
private void setValue(String value) {
|
||||
final boolean changed = !TextUtils.equals(this.value, value);
|
||||
if (changed || !valueSet) {
|
||||
this.value = value;
|
||||
@@ -85,11 +87,24 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
if (getValue() == null || PreferencesUtils.isBluetoothSensorAddressNone(getValue())) {
|
||||
if (value == null || SensorType.NONE.getPreferenceValue().equals(value)) {
|
||||
return getContext().getString(DEVICE_NONE_RESOURCEID);
|
||||
}
|
||||
if (SensorType.INTERNAL.getPreferenceValue().equals(value)) {
|
||||
return getContext().getString(SENSOR_INTERNAL_RESOURCEID);
|
||||
}
|
||||
|
||||
return getValue();
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(getContext());
|
||||
if (bluetoothAdapter == null) {
|
||||
Log.w(TAG, "No Bluetooth adapter present");
|
||||
return value;
|
||||
}
|
||||
|
||||
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(value);
|
||||
if (device != null && device.getName() != null) {
|
||||
return getContext().getString(R.string.bluetooth_sensor_summary, device.getAddress(), device.getName());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public abstract PreferenceDialogFragmentCompat createInstance();
|
||||
@@ -106,14 +121,12 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
@Override
|
||||
public void onScanResult(int callbackType, ScanResult result) {
|
||||
Log.d(TAG, "Found device " + result.getDevice().getName() + " " + result);
|
||||
listAdapter.add(result.getDevice());
|
||||
onBatchScanResults(List.of(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchScanResults(List<ScanResult> results) {
|
||||
for (ScanResult result : results) {
|
||||
onScanResult(-1, result);
|
||||
}
|
||||
listAdapter.addAll(results.stream().map(ScanResult::getDevice).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,18 +138,20 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
}
|
||||
};
|
||||
|
||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, ServiceMeasurementUUID sensorUUID) {
|
||||
return newInstance(preferenceKey, Collections.singletonList(sensorUUID));
|
||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<ServiceMeasurementUUID> sensorUUIDs) {
|
||||
return newInstance(preferenceKey, sensorUUIDs, false);
|
||||
}
|
||||
|
||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<ServiceMeasurementUUID> sensorUUIDs) {
|
||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<ServiceMeasurementUUID> sensorUUIDs, boolean includeInternalSensor) {
|
||||
final BluetoothLeSensorPreferenceDialog fragment = new BluetoothLeSensorPreferenceDialog();
|
||||
final Bundle b = new Bundle(1);
|
||||
final Bundle b = new Bundle(3);
|
||||
b.putString(ARG_KEY, preferenceKey);
|
||||
b.putParcelableArrayList(ARG_BLE_SERVICE_UUIDS, new ArrayList<>(sensorUUIDs.stream()
|
||||
.map(ServiceMeasurementUUID::serviceUUID)
|
||||
.map(ParcelUuid::new)
|
||||
.collect(Collectors.toList())));
|
||||
b.putBoolean(ARG_INCLUDE_INTERNAL, includeInternalSensor);
|
||||
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
@@ -166,6 +181,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
|
||||
private void startBluetoothScan() {
|
||||
List<ParcelUuid> serviceUUIDs = getArguments().getParcelableArrayList(ARG_BLE_SERVICE_UUIDS);
|
||||
boolean includeInternalSensor = getArguments().getBoolean(ARG_INCLUDE_INTERNAL);
|
||||
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(getContext());
|
||||
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
|
||||
@@ -187,15 +203,21 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
return;
|
||||
}
|
||||
|
||||
String deviceNone = getContext().getString(R.string.sensor_type_value_none);
|
||||
listAdapter.add(getContext().getString(DEVICE_NONE_RESOURCEID), deviceNone);
|
||||
listAdapter.add(SensorType.NONE.getPreferenceValue(), getContext().getString(DEVICE_NONE_RESOURCEID));
|
||||
selectedEntryIndex = 0;
|
||||
|
||||
BluetoothLeSensorPreference preference = (BluetoothLeSensorPreference) getPreference();
|
||||
String deviceSelected = preference.getValue();
|
||||
if (deviceSelected != null && !deviceNone.equals(deviceSelected)) {
|
||||
listAdapter.add(preference.getValue(), preference.getValue());
|
||||
selectedEntryIndex = 1;
|
||||
String deviceSelected = preference.value;
|
||||
if (includeInternalSensor) {
|
||||
listAdapter.add(SensorType.INTERNAL.getPreferenceValue(), getString(SENSOR_INTERNAL_RESOURCEID));
|
||||
if (SensorType.INTERNAL.getPreferenceValue().equals(deviceSelected)) {
|
||||
selectedEntryIndex = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceSelected != null && SensorType.REMOTE.equals(PreferencesUtils.getSensorType(deviceSelected))) {
|
||||
listAdapter.add(preference.value, preference.value);
|
||||
selectedEntryIndex = !includeInternalSensor ? 1 : 2;
|
||||
}
|
||||
|
||||
List<ScanFilter> scanFilter = null;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new IntervalStatisticsAdapter.ViewHolder(IntervalStatsListItemBinding.inflate(LayoutInflater.from(parent.getContext())));
|
||||
return new IntervalStatisticsAdapter.ViewHolder(IntervalStatsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,7 +32,7 @@ public class MarkerEditViewModel extends AndroidViewModel {
|
||||
private MutableLiveData<Marker> markerData;
|
||||
private boolean isNewMarker;
|
||||
private Uri photoOriginalUri;
|
||||
private final TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||
private final TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection((service, connection) -> {});
|
||||
|
||||
public MarkerEditViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
@@ -41,7 +41,7 @@ public class MarkerEditViewModel extends AndroidViewModel {
|
||||
public LiveData<Marker> getMarkerData(@NonNull Track.Id trackId, @Nullable Marker.Id markerId) {
|
||||
if (markerData == null) {
|
||||
markerData = new MutableLiveData<>();
|
||||
trackRecordingServiceConnection.startConnection(getApplication());
|
||||
trackRecordingServiceConnection.bind(getApplication());
|
||||
loadData(trackId, markerId);
|
||||
}
|
||||
return markerData;
|
||||
|
||||
@@ -119,7 +119,7 @@ public class MarkerListActivity extends AbstractActivity {
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
trackRecordingServiceConnection.bind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/switchWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
@@ -30,36 +30,36 @@ limitations under the License.
|
||||
\n* funguje zcela offline
|
||||
\n* zobrazit trasy na mapě (viz níže)</string>
|
||||
<string name="help_track_map_title">Jak ukázat trasu na mapě\?</string>
|
||||
<string name="help_track_map_content"> Použijte malou ikonu mapy.
|
||||
\n a) (živé aktualizace) Nainstalujte <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Poskytuje online nebo offline mapy prostřednictvím <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n Ve skutečnosti je OSMDashboard samostatná aplikace, která má přístup (dočasně) pouze k datům stop, které se budou zobrazovat.
|
||||
\n b) Prostřednictvím „normální“ mapové aplikace (např. <a href="http://osmand.net/">OsmAnd</a> a <a href="https://maps.me/">MAPS.ME</a>). Tyto aplikace musí podporovat formát souboru KMZ (pouze umístění a časová razítka). Ve skutečnosti musí aplikace podporovat <i>ACTION_VIEW</i> pomocí MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="help_track_map_content"> \u0020Použijte malou ikonu mapy.
|
||||
\n \u0020a) \u0020(živé aktualizace) Nainstalujte <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. \u0020Poskytuje online nebo offline mapy prostřednictvím <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n \u0020Ve skutečnosti je OSMDashboard samostatná aplikace, která má přístup (dočasně) pouze k datům stop, které se budou zobrazovat. \u0020
|
||||
\n \u0020b) \u0020Prostřednictvím normální mapové aplikace (např. <a href="http://osmand.net/">OsmAnd</a> a <a href="https://maps.me/">MAPS.ME</a>). \u0020Tyto aplikace musí podporovat formát souboru KMZ (pouze umístění a časová razítka). \u0020Ve skutečnosti musí aplikace podporovat <i>ACTION_VIEW</i> pomocí MIME <i>application/vnd.google-earth.kmz</i>. \u0020</string>
|
||||
<string name="help_storage_pictures_title">Jak ukládá OpenTracks obrázky pořízené jako značky na cestě\?</string>
|
||||
<string name="help_storage_pictures_content">Jsou uloženy v interním úložišti aplikace (tj. přístupné pouze v rámci OpenTracks).</string>
|
||||
<string name="help_track_export_title">Jak mohu exportovat/sdílet zaznamenané stopy\?</string>
|
||||
<string name="help_track_export_content"> Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. KMZ a KML obsahuje všechna data, GPX pouze polohu. Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. </string>
|
||||
<string name="help_track_export_content"> \u0020Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. \u0020KMZ a KML obsahuje všechna data, GPX pouze polohu. \u0020Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. \u0020</string>
|
||||
<string name="help_data_google_play_services_title">Vyžaduje/používá OpenTracks službu Google Play\?</string>
|
||||
<string name="help_recording_title">Jak OpenTracks zaznamenává data?</string>
|
||||
<string name="help_recording_content">OpenTracks používá GPS senzor zařízení k nepřetržitému zaznamenávání polohy. Pokud zařízení GPS hlásí nepřesná data (např. polohu, rychlost, nadmořskou výšku), OpenTracks s tím nemůže nic udělat.</string>
|
||||
<string name="help_data_storage_title">Jak OpenTracks ukládá data ze senzorů\?</string>
|
||||
<string name="help_data_storage_content"> Data senzoru se ukládají na zaznamenané místo. Pokud jde o snímač srdeční frekvence, znamená to, že na každé místo je uloženo pouze <i> jedno </i> měření srdeční frekvence. Pokud stojíte v klidu nebo jste uvnitř, nebudou zaznamenána žádná data ze senzorů (ale budou zobrazena). </string>
|
||||
<string name="help_data_storage_content"> \u0020Data senzoru se ukládají na zaznamenané místo. \u0020Pokud jde o snímač srdeční frekvence, znamená to, že na každé místo je uloženo pouze <i> jedno </i> měření srdeční frekvence. \u0020Pokud stojíte v klidu nebo jste uvnitř, nebudou zaznamenána žádná data ze senzorů (ale budou zobrazena). \u0020</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks nezaznamenává místa?</string>
|
||||
<string name="help_gps_troubleshooting_content"> Co můžete udělat:
|
||||
\n a) zkontrolujte zda je zapnuta GPS,
|
||||
\n b) zkuste zlepšit příjem GPS (např. vyjít ven),
|
||||
\n c) zkontrolujte, zda používáte aplikaci <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest </a> a
|
||||
\n d) deaktivujte úsporu energie.
|
||||
\n
|
||||
\n Pokud nic nepomůže, nahlaste to jako chybu. </string>
|
||||
<string name="help_gps_troubleshooting_content"> \u0020Co můžete udělat:
|
||||
\n \u0020a) zkontrolujte zda je zapnuta GPS,
|
||||
\n \u0020b) zkuste zlepšit příjem GPS (např. vyjít ven),
|
||||
\n \u0020c) zkontrolujte, zda používáte aplikaci <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest </a> a
|
||||
\n \u0020d) deaktivujte úsporu energie.
|
||||
\n \u0020
|
||||
\n \u0020Pokud nic nepomůže, nahlaste to jako chybu. \u0020</string>
|
||||
<string name="help_support_title">Jak mohu podpořit OpenTracks\?</string>
|
||||
<string name="help_support_content"> Můžete:
|
||||
\n a) dát vědět ostatním o OpenTracks
|
||||
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">navrhnout nové funkce</a>,
|
||||
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">nahlásit chybu</a>,
|
||||
\n d) pomoci při vývoji aplikace,
|
||||
\n e) zakoupit v <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Obchodě Google Play</a>,
|
||||
\n f) zaslat dar či
|
||||
\n g) udělat něco jiného, co si myslíte že pomůže OpenTracks. </string>
|
||||
<string name="help_support_content"> \u0020Můžete:
|
||||
\n \u0020a) dát vědět ostatním o OpenTracks
|
||||
\n \u0020b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">navrhnout nové funkce</a>,
|
||||
\n \u0020c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">nahlásit chybu</a>,
|
||||
\n \u0020d) pomoci při vývoji aplikace,
|
||||
\n \u0020e) zakoupit v <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Obchodě Google Play</a>,
|
||||
\n \u0020f) zaslat dar či
|
||||
\n \u0020g) udělat něco jiného, co si myslíte že pomůže OpenTracks. \u0020</string>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">letadlo</string>
|
||||
<string name="activity_type_atv">terénní auta</string>
|
||||
@@ -594,4 +594,5 @@ limitations under the License.
|
||||
<string name="settings_recording_title">Záznam</string>
|
||||
<string name="settings_ui_dynamic_colors_summary">Používat dynamické barvy definované uživatelem (pouze Android 12+; vyžaduje ruční restart)</string>
|
||||
<string name="permission_recording_failed">Aplikace OpenTrack vyžaduje oprávnění k přístupu k vaší poloze a zařízením poblíž.</string>
|
||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||
</resources>
|
||||
@@ -37,14 +37,14 @@ limitations under the License.
|
||||
<string name="help_storage_pictures_title">De que xeito garda OpenTracks as fotos tomadas como marcadores\?</string>
|
||||
<string name="help_storage_pictures_content">Gárdanse na almacenaxe interna da app (só accesible desde OpenTracks).</string>
|
||||
<string name="help_track_export_title">De que xeito exporto/importo as rutas gravadas\?</string>
|
||||
<string name="help_track_export_content"> As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. KMZ/KML inclúe todos os datos; GPX só localización. As rutas compartidas a través da <i>icona compartir</i> compártense como KMZ. </string>
|
||||
<string name="help_track_export_content"> \u0020As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. \u0020KMZ/KML inclúe todos os datos; GPX só localización. \u0020As rutas compartidas a través da <i>icona compartir</i> compártense como KMZ. \u0020</string>
|
||||
<string name="help_data_google_play_services_title">OpenTracks precisa/utiliza Google Play Service\?</string>
|
||||
<string name="help_recording_title">Como grava OpenTracks os datos\?</string>
|
||||
<string name="help_recording_content">
|
||||
OpenTracks usa o sensor GPS do dispositivo para gravar a localización de xeito contínuo.
|
||||
Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, elevación), OpenTracks non pode facer nada ao respecto.</string>
|
||||
<string name="help_data_storage_title">De que xeito garda OpenTracks os datos dos sensores\?</string>
|
||||
<string name="help_data_storage_content"> Os datos dos sensores gárdanse asociados á localización. Respecto do ritmo cardíaco, significa que só se garda <i>unha medida</i> asociada a unha localización. Se te detés ou estás en interiores, non se gardarán datos do sensor (mais serán mostrados). </string>
|
||||
<string name="help_data_storage_content"> \u0020Os datos dos sensores gárdanse asociados á localización. \u0020 Respecto do ritmo cardíaco, significa que só se garda <i>unha medida</i> asociada a unha localización. \u0020 Se te detés ou estás en interiores, non se gardarán datos do sensor (mais serán mostrados). \u0020</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks non grava as localizacións\?</string>
|
||||
<string name="help_gps_troubleshooting_content"> Podes intentar:
|
||||
\n a) mira que o GPS esté activado,
|
||||
@@ -596,4 +596,5 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="settings_recording_title">Ao gravar</string>
|
||||
<string name="settings_ui_dynamic_colors_summary">Usar Cores Dinámicas definidas pola usuaria (Só Android 12+; require reiniciar)</string>
|
||||
<string name="permission_recording_failed">OpenTracks precisa permiso para acceder á túa localización así como a dispositivos próximos.</string>
|
||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
--><resources>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">飛行機</string>
|
||||
<string name="activity_type_atv">全地形対応車(ATV)</string>
|
||||
@@ -195,7 +195,6 @@ limitations under the License.
|
||||
<string name="settings_stats_units_imperial">ヤード・ポンド法(マイル、フィート)</string>
|
||||
<string name="settings_stats_units_metric">メートル法(km、m)</string>
|
||||
<string name="settings_stats_units_title">計測単位</string>
|
||||
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file_body">次のトラックを見てみてください。\n\nトラックを表示するには、Google Earthか他のマッピングソフトウェアを使用してください。\n\n%1$s</string>
|
||||
<string name="share_track_subject">トラック共有のお知らせ</string>
|
||||
@@ -306,4 +305,10 @@ limitations under the License.
|
||||
<string name="waypoint_type_waterfall">滝</string>
|
||||
<string name="waypoint_type_water_fountain">噴水</string>
|
||||
<string name="waypoint_type_waypoint">地点</string>
|
||||
</resources>
|
||||
<string name="help_feature_content">"* GPS トラックを作成します
|
||||
\n * 心拍センサーを使用します (Bluetooth LE のみ)
|
||||
\n * 音声アナウンス(時間または距離)
|
||||
\n * マーカーの作成 (写真を含む)
|
||||
\n * 完全にオフラインで動作します
|
||||
\n * 地図上にトラックを表示します (下記を参照)"</string>
|
||||
</resources>
|
||||
+14
-1
@@ -1,19 +1,32 @@
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="ThemeCustom" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<style name="OpenTracksTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Material3 tech documentation; https://github.com/material-components/material-components-android/blob/master/docs/components/ -->
|
||||
|
||||
<item name="colorPrimary">@color/opentracks</item>
|
||||
<item name="colorSecondary">@color/opentracks</item>
|
||||
|
||||
<!-- Material3 https://github.com/material-components/material-components-android/issues/2828#issuecomment-1187563903 -->
|
||||
<item name="switchPreferenceCompatStyle">@style/MaterialSwitchPreferenceCompat</item>
|
||||
|
||||
<item name="android:statusBarColor">@color/oled_friendly</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
<item name="android:navigationBarColor">@color/oled_friendly</item>
|
||||
|
||||
|
||||
<item name="bottomAppBarStyle">@style/Widget.OpenTracks.BottomAppBar</item>
|
||||
<item name="floatingActionButtonStyle">@style/Widget.OpenTracks.FloatingActionButton</item>
|
||||
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
</style>
|
||||
|
||||
<style name="SplashTheme" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
|
||||
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_logo_color_splashscreen</item>
|
||||
<item name="postSplashScreenTheme">@style/OpenTracksTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.OpenTracks.BottomAppBar" parent="Widget.Material3.BottomAppBar">
|
||||
<item name="materialThemeOverlay">@style/ThemeOverlay.OpenTracks.BottomAppBar</item>
|
||||
</style>
|
||||
@@ -30,38 +30,38 @@ limitations under the License.
|
||||
\n * werkt volledig offline
|
||||
\n * toon tracks op een kaart (zie hieronder)</string>
|
||||
<string name="help_track_map_title">Hoe wordt een spoor op een kaart weergegeven\?</string>
|
||||
<string name="help_track_map_content"> Gebruik het kleine kaartpictogram.
|
||||
\n a) (live updates) Installeer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Het biedt online of offline kaarten via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n Eigenlijk is OSMDashboard een aparte app die alleen (tijdelijk) toegang heeft tot de gegevens van de track(s) die getoond gaan worden.
|
||||
\n b) Via een normale kaartapplicatie (bijv. <a href="http://osmand.net/">OsmAnd</a> en <a href="https://maps.me/">MAPS.ME</a>). Deze apps moeten het KMZ-bestandsformaat ondersteunen (alleen locaties en tijdstempels). In feite moet een app <i>ACTION_VIEW</i> ondersteunen met MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="help_track_map_content"> \u0020Gebruik het kleine kaartpictogram.
|
||||
\n \u0020a) \u0020(live updates) Installeer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Het biedt online of offline kaarten via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n \u0020Eigenlijk is OSMDashboard een aparte app die alleen (tijdelijk) toegang heeft tot de gegevens van de track(s) die getoond gaan worden.
|
||||
\n \u0020b) \u0020Via een normale kaartapplicatie (bijv. <a href="http://osmand.net/">OsmAnd</a> en <a href="https://maps.me/">MAPS.ME</a>). Deze apps moeten het KMZ-bestandsformaat ondersteunen (alleen locaties en tijdstempels). In feite moet een app <i>ACTION_VIEW</i> ondersteunen met MIME <i>application/vnd.google-earth.kmz</i>. \u0020</string>
|
||||
<string name="help_storage_pictures_title">Hoe slaat OpenTracks foto\'s op die als markeringen zijn genomen\?</string>
|
||||
<string name="help_storage_pictures_content">Deze worden opgeslagen in de interne opslag van de app (d.w.z. alleen toegankelijk binnen OpenTracks).</string>
|
||||
<string name="help_track_export_title">Hoe kan ik opgenomen tracks exporteren/delen?</string>
|
||||
<string name="help_track_export_content"> Tracks kunnen worden geëxporteerd vanuit de instellingen als KMZ, KML of GPX. KMZ/KML alle gegevens; GPX alleen locaties. Tracks die worden gedeeld via het <i>share-icoontje</i> worden gedeeld als KMZ </string>
|
||||
<string name="help_track_export_content"> \u0020Tracks kunnen worden geëxporteerd vanuit de instellingen als KMZ, KML of GPX. KMZ/KML alle gegevens; GPX alleen locaties. \u0020Tracks die worden gedeeld via het <i>share-icoontje</i> worden gedeeld als KMZ \u0020</string>
|
||||
<string name="help_data_google_play_services_title">Vereist/gebruikt OpenTracks Google Play Service\?</string>
|
||||
<string name="help_recording_title">Hoe legt OpenTracks gegevens vast?</string>
|
||||
<string name="help_recording_content">
|
||||
OpenTracks gebruikt de GPS-sensor van het apparaat om de locatie continu te registreren.
|
||||
Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid, hoogte), kan OpenTracks daar niets aan doen.</string>
|
||||
<string name="help_data_storage_title">Hoe slaat OpenTracks sensorgegevens op?</string>
|
||||
<string name="help_data_storage_content"> De sensorgegevens worden per opgenomen locatie opgeslagen. In termen van een hartslagsensor betekent dit dat slechts <i>een</i> hartslagmeting per locatie wordt opgeslagen. Als u stilstaat of binnenshuis bent, worden er geen sensorgegevens geregistreerd (maar wel getoond) </string>
|
||||
<string name="help_data_storage_content"> \u0020De sensorgegevens worden per opgenomen locatie opgeslagen. In termen van een hartslagsensor betekent dit dat slechts <i>een</i> hartslagmeting per locatie wordt opgeslagen. Als u stilstaat of binnenshuis bent, worden er geen sensorgegevens geregistreerd (maar wel getoond) \u0020</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks neemt geen locaties op?</string>
|
||||
<string name="help_gps_troubleshooting_content"> Wat u kunt doen:
|
||||
\n a) Controleer of GPS is ingeschakeld,
|
||||
\n b) verbeter de GPS-ontvangst (ga bijvoorbeeld naar buiten),
|
||||
\n c) controleren met <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, en
|
||||
\n d) schakel energiebesparing uit.
|
||||
\n
|
||||
\n Als alles niet helpt, rapporteer dan een mogelijke fout. </string>
|
||||
<string name="help_gps_troubleshooting_content"> \u0020Wat u kunt doen:
|
||||
\n \u0020a) Controleer of GPS is ingeschakeld,
|
||||
\n \u0020b) verbeter de GPS-ontvangst (ga bijvoorbeeld naar buiten),
|
||||
\n \u0020c) controleren met <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, en
|
||||
\n \u0020d) schakel energiebesparing uit.
|
||||
\n
|
||||
\n \u0020Als alles niet helpt, rapporteer dan een mogelijke fout. \u0020</string>
|
||||
<string name="help_support_title">Hoe kan ik OpenTracks ondersteunen?</string>
|
||||
<string name="help_support_content"> U kunt:
|
||||
\n a) anderen vertellen over OpenTracks,
|
||||
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">functies voorstellen</a>,
|
||||
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">bugs melden</a>,
|
||||
\n d) helpen bij de ontwikkeling van software,
|
||||
\n e) aanschaffen in <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
|
||||
\n f) doneren, en
|
||||
\n g) wat u ook denkt dat OpenTracks helpt. </string>
|
||||
<string name="help_support_content"> \u0020U kunt:
|
||||
\n \u0020a) anderen vertellen over OpenTracks,
|
||||
\n \u0020b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">functies voorstellen</a>,
|
||||
\n \u0020c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">bugs melden</a>,
|
||||
\n \u0020d) helpen bij de ontwikkeling van software,
|
||||
\n \u0020e) aanschaffen in <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
|
||||
\n \u0020f) doneren, en
|
||||
\n \u0020g) wat u ook denkt dat OpenTracks helpt. \u0020</string>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">vliegtuig</string>
|
||||
<string name="activity_type_atv">quad</string>
|
||||
@@ -595,4 +595,5 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="sensor_state_heart_rate_unit">/min</string>
|
||||
<string name="sensor_state_cadence_unit">/min</string>
|
||||
<string name="voice_per_kilometer">/km</string>
|
||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||
</resources>
|
||||
@@ -32,7 +32,9 @@
|
||||
<string name="settings_sensor_bluetooth_cycling_speed_key" translatable="false">bluetoothCyclingSpeedSensor</string>
|
||||
<string name="settings_sensor_bluetooth_cycling_power_key" translatable="false">bluetoothCyclingPowerSensor</string>
|
||||
<string name="settings_sensor_bluetooth_running_speed_and_cadence_key" translatable="false">bluetoothRunningSpeedAndCadenceSensor</string>
|
||||
<string name="settings_sensor_bluetooth_pressure_key" translatable="false">bluetoothPressureSensor</string>
|
||||
<string name="sensor_type_value_none" translatable="false">NONE</string>
|
||||
<string name="sensor_type_value_internal" translatable="false">INTERNAL</string>
|
||||
|
||||
<string name="settings_sensor_bluetooth_cycling_speed_wheel_circumference_key" translatable="false">bluetoothCyclingSpeedWheelCircumference</string>
|
||||
<string name="settings_sensor_bluetooth_cycling_speed_wheel_circumference_default" translatable="false">2135</string>
|
||||
|
||||
@@ -331,11 +331,13 @@ limitations under the License.
|
||||
<string name="sensor_state_heart_rate_unit">bpm</string>
|
||||
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
||||
<string name="sensor_state_power">Power</string>
|
||||
<string name="sensor_state_pressure">Barometer</string>
|
||||
<string name="sensor_state_power_avg">Avg Power</string>
|
||||
<string name="sensor_state_power_max">Max Power</string>
|
||||
<string name="sensor_state_power_unit">W</string>
|
||||
<string name="sensor_could_not_scan">Could not scan for Bluetooth devices (error %1$i).</string>
|
||||
<string name="bluetooth_disabled">Please enable Bluetooth.</string>
|
||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||
|
||||
<string name="settings_open_tracks_summary">All About OpenTracks Project</string>
|
||||
|
||||
@@ -558,6 +560,7 @@ limitations under the License.
|
||||
<string name="value_integer_second">%1$d s</string>
|
||||
<string name="value_none">None</string>
|
||||
<string name="value_off">Off</string>
|
||||
<string name="value_internal_sensor">Internal sensor</string>
|
||||
<string name="value_smallest_recommended">Smallest (recommended)</string>
|
||||
<string name="value_int_seconds">%1$d s (recommended)</string>
|
||||
<string name="voiceIdle">Becoming idle.</string>
|
||||
|
||||
@@ -93,4 +93,11 @@ limitations under the License.
|
||||
<item name="colorOnSurface">@android:color/holo_red_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.OpenTracks.FloatingActionButton" parent="Widget.MaterialComponents.FloatingActionButton">
|
||||
<item name="fabCustomSize">64dp</item>
|
||||
</style>
|
||||
|
||||
<style name="MaterialSwitchPreferenceCompat" parent="@style/Preference.SwitchPreferenceCompat.Material">
|
||||
<item name="widgetLayout">@layout/view_preference_switch</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2013 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="ThemeCustom" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<style name="OpenTracksTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/opentracks</item>
|
||||
<item name="colorSecondary">@color/opentracks</item>
|
||||
|
||||
<!-- Material3 https://github.com/material-components/material-components-android/issues/2828#issuecomment-1187563903 -->
|
||||
<item name="switchPreferenceCompatStyle">@style/MaterialSwitchPreferenceCompat</item>
|
||||
|
||||
<item name="android:statusBarColor">?attr/colorSurfaceBright</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:navigationBarColor">?attr/colorSurfaceContainer</item>
|
||||
@@ -35,18 +24,11 @@ limitations under the License.
|
||||
</style>
|
||||
|
||||
<style name="SplashTheme" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
|
||||
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_logo_color_splashscreen</item>
|
||||
<item name="postSplashScreenTheme">@style/ThemeCustom</item>
|
||||
<item name="postSplashScreenTheme">@style/OpenTracksTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.OpenTracks.BottomAppBar" parent="Widget.Material3.BottomAppBar">
|
||||
<item name="backgroundTint">?attr/colorSurfaceContainer</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.OpenTracks.FloatingActionButton" parent="Widget.MaterialComponents.FloatingActionButton">
|
||||
<item name="fabCustomSize">64dp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -15,6 +15,10 @@
|
||||
android:key="@string/settings_sensor_heart_rate_max_key"
|
||||
android:title="@string/settings_sensor_heart_rate_max"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLePressurePreference
|
||||
android:defaultValue="@string/sensor_type_value_none"
|
||||
android:key="@string/settings_sensor_bluetooth_pressure_key"
|
||||
android:title="@string/sensor_state_pressure" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_cycling_sensor">
|
||||
|
||||
Reference in New Issue
Block a user