From b9472f2727f090584ec10561e7e42034cc485f3e Mon Sep 17 00:00:00 2001 From: Jack Farrelly Date: Thu, 22 Jan 2026 18:50:57 +0100 Subject: [PATCH 1/4] Selectable location providers --- .../client/config/ConfigRepository.java | 26 ++++ .../client/grpc/BeaconClient.java | 3 +- .../client/grpc/BeaconRequest.java | 17 ++- .../locationhistory/client/grpc/Requests.java | 11 +- .../client/location/LocationService.java | 69 ++++++--- .../client/ui/LocationProviderAdapter.java | 133 ++++++++++++++++++ .../client/ui/LocationProviderItem.java | 11 ++ .../client/ui/SettingsFragment.java | 32 ++++- .../client/ui/SettingsViewModel.java | 53 +++++++ .../client/worker/BeaconContext.java | 2 +- .../src/main/res/drawable/ic_drag_handle.xml | 10 ++ .../src/main/res/layout/fragment_settings.xml | 54 ++++++- .../res/layout/item_location_provider.xml | 41 ++++++ client/app/src/main/res/values/strings.xml | 4 + server/.idea/scala_settings.xml | 1 + 15 files changed, 435 insertions(+), 32 deletions(-) create mode 100644 client/app/src/main/java/com/jackpf/locationhistory/client/ui/LocationProviderAdapter.java create mode 100644 client/app/src/main/java/com/jackpf/locationhistory/client/ui/LocationProviderItem.java create mode 100644 client/app/src/main/res/drawable/ic_drag_handle.xml create mode 100644 client/app/src/main/res/layout/item_location_provider.xml diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/config/ConfigRepository.java b/client/app/src/main/java/com/jackpf/locationhistory/client/config/ConfigRepository.java index 21ce356d..d95fc879 100644 --- a/client/app/src/main/java/com/jackpf/locationhistory/client/config/ConfigRepository.java +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/config/ConfigRepository.java @@ -4,6 +4,9 @@ import android.content.SharedPreferences; import android.provider.Settings; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.TimeUnit; public class ConfigRepository { @@ -25,6 +28,8 @@ public class ConfigRepository { public static final String LAST_RUN_TIMESTAMP_KEY = "last-run-timestamp"; + public static final String ENABLED_LOCATION_PROVIDERS_KEY = "enabled-location-providers"; + /** * How long do we stay in high accuracy mode after a trigger */ @@ -117,4 +122,25 @@ public boolean inHighAccuracyMode() { public void setLastRunTimestamp(long lastRunTime) { prefs.edit().putLong(LAST_RUN_TIMESTAMP_KEY, lastRunTime).apply(); } + + /** + * Get the list of enabled location providers in order. + * Returns an empty list if not set. + */ + public List getEnabledLocationProviders() { + String providersString = prefs.getString(ENABLED_LOCATION_PROVIDERS_KEY, ""); + if (providersString.isEmpty()) { + return new ArrayList<>(); + } + return new ArrayList<>(Arrays.asList(providersString.split(","))); + } + + /** + * Set the list of enabled location providers in order. + * The list is stored as a comma-separated string. + */ + public void setEnabledLocationProviders(List providers) { + String providersString = String.join(",", providers); + prefs.edit().putString(ENABLED_LOCATION_PROVIDERS_KEY, providersString).apply(); + } } diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/BeaconClient.java b/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/BeaconClient.java index 8034db48..3a6db3b4 100644 --- a/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/BeaconClient.java +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/BeaconClient.java @@ -96,7 +96,8 @@ public ListenableFuture sendLocation(String deviceId, Beaco beaconRequest.getLat(), beaconRequest.getLon(), (double) beaconRequest.getAccuracy(), - beaconRequest.getTimestamp() + beaconRequest.getTimestamp(), + beaconRequest.getMetadata() ); ListenableFuture future = createStub().setLocation(request); Futures.addCallback(future, callback, threadExecutor); diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/BeaconRequest.java b/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/BeaconRequest.java index 505078c2..8e8ba95a 100644 --- a/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/BeaconRequest.java +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/BeaconRequest.java @@ -2,21 +2,28 @@ import android.location.Location; +import java.util.HashMap; +import java.util.Map; + import lombok.Value; @Value public class BeaconRequest { - long timestamp; - double lat; - double lon; - float accuracy; + private long timestamp; + private double lat; + private double lon; + private float accuracy; + private Map metadata; public static BeaconRequest fromLocation(Location location) { return new BeaconRequest( System.currentTimeMillis(), location.getLatitude(), location.getLongitude(), - location.getAccuracy() + location.getAccuracy(), + new HashMap() {{ + put("provider", location.getProvider()); + }} ); } } diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/Requests.java b/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/Requests.java index 7b0b58b1..356c6cea 100644 --- a/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/Requests.java +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/grpc/Requests.java @@ -9,6 +9,8 @@ import com.jackpf.locationhistory.RegisterPushHandlerRequest; import com.jackpf.locationhistory.SetLocationRequest; +import java.util.Map; + public class Requests { private static Device device(String deviceId, String deviceName) { return Device.newBuilder() @@ -20,12 +22,14 @@ private static Device device(String deviceId, String deviceName) { private static Location location( Double lat, Double lon, - Double accuracy + Double accuracy, + Map metadata ) { return Location.newBuilder() .setLat(lat) .setLon(lon) .setAccuracy(accuracy) + .putAllMetadata(metadata) .build(); } @@ -54,12 +58,13 @@ public static SetLocationRequest setLocationRequest( Double lat, Double lon, Double accuracy, - Long timestamp + Long timestamp, + Map metadata ) { return SetLocationRequest .newBuilder() .setDeviceId(deviceId) - .setLocation(location(lat, lon, accuracy)) + .setLocation(location(lat, lon, accuracy, metadata)) .setTimestamp(timestamp) .build(); } diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/location/LocationService.java b/client/app/src/main/java/com/jackpf/locationhistory/client/location/LocationService.java index 7a8d6972..a41fe14c 100644 --- a/client/app/src/main/java/com/jackpf/locationhistory/client/location/LocationService.java +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/location/LocationService.java @@ -15,9 +15,11 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; public class LocationService { private final LocationManager locationManager; @@ -36,6 +38,10 @@ public class LocationService { * Network should be relatively fast (~5s for radio wakeup or near instant if using WiFi location) */ private static final int NETWORK_TIMEOUT = 10_000; + /** + * If we don't know what we're calling + */ + private static final int DEFAULT_TIMEOUT = 30_000; /** * Timeout doesn't apply to cache requests */ @@ -43,6 +49,7 @@ public class LocationService { @Getter @AllArgsConstructor + @ToString private static class ProviderRequest { private String source; private int timeout; @@ -74,6 +81,16 @@ public static LocationService create(Context context, ); } + public List getAvailableSources() { + return filterEnabledSources(locationManager.getAllProviders()); + } + + private List filterEnabledSources(List sources) { + return sources.stream() + .filter(locationManager::isProviderEnabled) + .collect(Collectors.toList()); + } + private void callSequentialProviders(Iterator providers, Consumer consumer) { if (!providers.hasNext()) { consumer.accept(null); // All providers invalid/failed @@ -123,49 +140,69 @@ private LocationData chooseBestLocation(List locations) { return best; } + private List createRequests(List sources, LocationProvider provider) { + List requests = new ArrayList<>(); + + for (String source : filterEnabledSources(sources)) { + int timeout = timeoutForSource(source); + requests.add(new ProviderRequest(source, timeout, provider)); + } + + return requests; + } + + private int timeoutForSource(String source) { + switch (source) { + case LocationManager.GPS_PROVIDER: + return GPS_TIMEOUT; + case LocationManager.NETWORK_PROVIDER: + return NETWORK_TIMEOUT; + default: + return DEFAULT_TIMEOUT; + } + } + /** * @param accuracy Defines priority of accuracy vs battery use (GPS v.s. network v.s. cached location sources) * @param consumer Callback for location data * @throws SecurityException If we're missing location permissions */ - public void getLocation(RequestedAccuracy accuracy, Consumer consumer) throws SecurityException { + public void getLocation(RequestedAccuracy accuracy, + List sources, + Consumer consumer) throws SecurityException { if (!permissionsManager.hasLocationPermissions()) { throw new SecurityException("No location permissions"); } - List providers = new ArrayList<>(); if (optimisedProvider.isSupported()) { /* Optimised provider will automatically use the location cache if available * and return a fresh (< ~30s) location if available */ log.d("Using optimised location manager"); - ProviderRequest gpsRequest = new ProviderRequest(LocationManager.GPS_PROVIDER, GPS_TIMEOUT, optimisedProvider); - ProviderRequest networkRequest = new ProviderRequest(LocationManager.NETWORK_PROVIDER, NETWORK_TIMEOUT, optimisedProvider); + List requests = createRequests(sources, optimisedProvider); + log.d("Requesting location from providers: %s", Arrays.toString(requests.toArray())); if (accuracy == RequestedAccuracy.HIGH) { - providers.addAll(Arrays.asList(gpsRequest, networkRequest)); - callParallelProviders(providers.iterator(), this::chooseBestLocation, consumer); + callParallelProviders(requests.iterator(), this::chooseBestLocation, consumer); } else { - providers.addAll(Arrays.asList(networkRequest, gpsRequest)); - callSequentialProviders(providers.iterator(), consumer); + callSequentialProviders(requests.iterator(), consumer); } } else { /* The legacy provider will directly request location from the hardware, * so we've gotta implement our own cache checks */ log.d("Using legacy location manager"); - ProviderRequest highGps = new ProviderRequest(LocationManager.GPS_PROVIDER, GPS_TIMEOUT, legacyHighAccuracyProvider); - ProviderRequest highNetwork = new ProviderRequest(LocationManager.NETWORK_PROVIDER, NETWORK_TIMEOUT, legacyHighAccuracyProvider); - ProviderRequest cachedGps = new ProviderRequest(LocationManager.GPS_PROVIDER, CACHE_TIMEOUT, legacyCachedProvider); - ProviderRequest cachedNetwork = new ProviderRequest(LocationManager.NETWORK_PROVIDER, CACHE_TIMEOUT, legacyCachedProvider); + List cachedRequests = createRequests(sources, legacyCachedProvider); + List highAccuracyRequests = createRequests(sources, legacyHighAccuracyProvider); + List allRequests = Stream.concat(cachedRequests.stream(), highAccuracyRequests.stream()) + .collect(Collectors.toList()); + log.d("Requesting location from providers: %s", Arrays.toString(allRequests.toArray())); if (accuracy == RequestedAccuracy.HIGH) { - providers.addAll(Arrays.asList(highGps, highNetwork, cachedGps, cachedNetwork)); - callParallelProviders(providers.iterator(), this::chooseBestLocation, consumer); + callParallelProviders(allRequests.iterator(), this::chooseBestLocation, consumer); } else { - providers.addAll(Arrays.asList(cachedGps, cachedNetwork, highNetwork, highGps)); - callSequentialProviders(providers.iterator(), consumer); + callSequentialProviders(allRequests.iterator(), consumer); } } } diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/ui/LocationProviderAdapter.java b/client/app/src/main/java/com/jackpf/locationhistory/client/ui/LocationProviderAdapter.java new file mode 100644 index 00000000..58dce80f --- /dev/null +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/ui/LocationProviderAdapter.java @@ -0,0 +1,133 @@ +package com.jackpf.locationhistory.client.ui; + +import android.annotation.SuppressLint; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + +import com.jackpf.locationhistory.client.R; + +import java.util.Collections; +import java.util.List; + +import lombok.Setter; + +public class LocationProviderAdapter extends RecyclerView.Adapter { + + private final List providers; + @Setter + private ItemTouchHelper itemTouchHelper; + + public LocationProviderAdapter(List providers) { + this.providers = providers; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_location_provider, parent, false); + return new ViewHolder(view); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + LocationProviderItem item = providers.get(position); + + holder.providerName.setText(item.getProviderName()); + holder.providerCheckbox.setChecked(item.isEnabled()); + + holder.providerCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> { + item.setEnabled(isChecked); + }); + + holder.dragHandle.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && itemTouchHelper != null) { + itemTouchHelper.startDrag(holder); + } + return false; + }); + } + + @Override + public int getItemCount() { + return providers.size(); + } + + public List getProviders() { + return providers; + } + + public void onItemMove(int fromPosition, int toPosition) { + if (fromPosition < toPosition) { + for (int i = fromPosition; i < toPosition; i++) { + Collections.swap(providers, i, i + 1); + } + } else { + for (int i = fromPosition; i > toPosition; i--) { + Collections.swap(providers, i, i - 1); + } + } + notifyItemMoved(fromPosition, toPosition); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + final ImageView dragHandle; + final CheckBox providerCheckbox; + final TextView providerName; + + ViewHolder(View itemView) { + super(itemView); + dragHandle = itemView.findViewById(R.id.dragHandle); + providerCheckbox = itemView.findViewById(R.id.providerCheckbox); + providerName = itemView.findViewById(R.id.providerName); + } + } + + public static class DragCallback extends ItemTouchHelper.Callback { + private final LocationProviderAdapter adapter; + + public DragCallback(LocationProviderAdapter adapter) { + this.adapter = adapter; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; // We use the drag handle instead + } + + @Override + public boolean isItemViewSwipeEnabled() { + return false; + } + + @Override + public int getMovementFlags(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder) { + int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + return makeMovementFlags(dragFlags, 0); + } + + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { + adapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + // Not used + } + } +} diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/ui/LocationProviderItem.java b/client/app/src/main/java/com/jackpf/locationhistory/client/ui/LocationProviderItem.java new file mode 100644 index 00000000..0e0fbd14 --- /dev/null +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/ui/LocationProviderItem.java @@ -0,0 +1,11 @@ +package com.jackpf.locationhistory.client.ui; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class LocationProviderItem { + private String providerName; + private boolean enabled; +} diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/ui/SettingsFragment.java b/client/app/src/main/java/com/jackpf/locationhistory/client/ui/SettingsFragment.java index 86a4bfdc..31b8445e 100644 --- a/client/app/src/main/java/com/jackpf/locationhistory/client/ui/SettingsFragment.java +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/ui/SettingsFragment.java @@ -8,6 +8,8 @@ import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.jackpf.locationhistory.client.R; @@ -24,6 +26,7 @@ public class SettingsFragment extends Fragment { private FragmentSettingsBinding binding; private SettingsViewModel viewModel; + private LocationProviderAdapter providerAdapter; @Nullable private SSLPrompt sslPrompt; @@ -43,6 +46,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat sslPrompt = new SSLPrompt(requireActivity()); setupInputs(); + setupLocationProviders(); setupUnifiedPushListener(); observeEvents(); } @@ -73,11 +77,29 @@ private void setupInputs() { binding.serverPortInput.getText().toString() )); - binding.saveButton.setOnClickListener(v -> viewModel.saveSettings( - binding.serverHostInput.getText().toString(), - binding.serverPortInput.getText().toString(), - binding.updateFrequencyInput.getText().toString() - )); + binding.saveButton.setOnClickListener(v -> { + viewModel.saveSettings( + binding.serverHostInput.getText().toString(), + binding.serverPortInput.getText().toString(), + binding.updateFrequencyInput.getText().toString() + ); + if (providerAdapter != null) { + viewModel.saveEnabledLocationProviders(providerAdapter.getProviders()); + } + }); + } + + private void setupLocationProviders() { + List providerItems = viewModel.getLocationProviderItems(); + providerAdapter = new LocationProviderAdapter(providerItems); + + binding.providersRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + binding.providersRecyclerView.setAdapter(providerAdapter); + + ItemTouchHelper.Callback callback = new LocationProviderAdapter.DragCallback(providerAdapter); + ItemTouchHelper touchHelper = new ItemTouchHelper(callback); + touchHelper.attachToRecyclerView(binding.providersRecyclerView); + providerAdapter.setItemTouchHelper(touchHelper); } private void setupUnifiedPushListener() { diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/ui/SettingsViewModel.java b/client/app/src/main/java/com/jackpf/locationhistory/client/ui/SettingsViewModel.java index e05b4ae4..2a14eaf3 100644 --- a/client/app/src/main/java/com/jackpf/locationhistory/client/ui/SettingsViewModel.java +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/ui/SettingsViewModel.java @@ -16,12 +16,17 @@ import com.jackpf.locationhistory.client.client.util.GrpcFutureWrapper; import com.jackpf.locationhistory.client.config.ConfigRepository; import com.jackpf.locationhistory.client.grpc.BeaconClient; +import com.jackpf.locationhistory.client.location.LocationService; import com.jackpf.locationhistory.client.push.UnifiedPushContext; import com.jackpf.locationhistory.client.util.Logger; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class SettingsViewModel extends AndroidViewModel { private final Logger log = new Logger(this); @@ -29,6 +34,7 @@ public class SettingsViewModel extends AndroidViewModel { private final ConfigRepository configRepository; private final TrustedCertStorage trustedCertStorage; private final UnifiedPushContext unifiedPushContext; + private final LocationService locationService; private final SingleLiveEvent events = new SingleLiveEvent<>(); @@ -37,6 +43,10 @@ public SettingsViewModel(@NonNull Application application) { this.configRepository = new ConfigRepository(application); this.trustedCertStorage = new TrustedCertStorage(application); this.unifiedPushContext = new UnifiedPushContext(application); + this.locationService = LocationService.create( + application, + com.jackpf.locationhistory.client.AppExecutors.getInstance().background() + ); } public LiveData getEvents() { @@ -111,4 +121,47 @@ public void registerUnifiedPush(String distributor) { log.d("Registering with distributor: %s", distributor); unifiedPushContext.register(distributor); } + + /** + * Get the list of location provider items for the settings UI. + * Providers are returned in priority order based on saved preferences. + * Providers not in saved preferences are added at the end (disabled by default). + */ + public List getLocationProviderItems() { + List availableProviders = locationService.getAvailableSources(); + List enabledProviders = configRepository.getEnabledLocationProviders(); + Set enabledSet = new HashSet<>(enabledProviders); + Set availableSet = new HashSet<>(availableProviders); + + List items = new ArrayList<>(); + + // First add enabled providers in their saved order (if still available) + for (String provider : enabledProviders) { + if (availableSet.contains(provider)) { + items.add(new LocationProviderItem(provider, true)); + } + } + + // Then add remaining available providers (not enabled) at the end + for (String provider : availableProviders) { + if (!enabledSet.contains(provider)) { + items.add(new LocationProviderItem(provider, false)); + } + } + + return items; + } + + /** + * Save the enabled location providers in order. + */ + public void saveEnabledLocationProviders(List items) { + List enabledProviders = items.stream() + .filter(LocationProviderItem::isEnabled) + .map(LocationProviderItem::getProviderName) + .collect(Collectors.toList()); + + configRepository.setEnabledLocationProviders(enabledProviders); + log.d("Saved enabled providers: %s", enabledProviders); + } } diff --git a/client/app/src/main/java/com/jackpf/locationhistory/client/worker/BeaconContext.java b/client/app/src/main/java/com/jackpf/locationhistory/client/worker/BeaconContext.java index f7bb62c2..bb2bda25 100644 --- a/client/app/src/main/java/com/jackpf/locationhistory/client/worker/BeaconContext.java +++ b/client/app/src/main/java/com/jackpf/locationhistory/client/worker/BeaconContext.java @@ -56,7 +56,7 @@ public ListenableFuture onDeviceStateReady() { } public void getLocation(RequestedAccuracy accuracy, Consumer consumer) throws SecurityException { - locationService.getLocation(accuracy, consumer); + locationService.getLocation(accuracy, configRepository.getEnabledLocationProviders(), consumer); } public ListenableFuture setLocation(LocationData locationData) { diff --git a/client/app/src/main/res/drawable/ic_drag_handle.xml b/client/app/src/main/res/drawable/ic_drag_handle.xml new file mode 100644 index 00000000..b65f1da2 --- /dev/null +++ b/client/app/src/main/res/drawable/ic_drag_handle.xml @@ -0,0 +1,10 @@ + + + + diff --git a/client/app/src/main/res/layout/fragment_settings.xml b/client/app/src/main/res/layout/fragment_settings.xml index 8938772c..d002dd38 100644 --- a/client/app/src/main/res/layout/fragment_settings.xml +++ b/client/app/src/main/res/layout/fragment_settings.xml @@ -18,7 +18,7 @@ android:layout_marginStart="4dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/topLink" - app:layout_constraintBottom_toBottomOf="@id/pushDesc" /> + app:layout_constraintBottom_toBottomOf="@id/providersRecyclerView" /> + + + + + + + + + + \ No newline at end of file diff --git a/client/app/src/main/res/layout/item_location_provider.xml b/client/app/src/main/res/layout/item_location_provider.xml new file mode 100644 index 00000000..a9f3cd16 --- /dev/null +++ b/client/app/src/main/res/layout/item_location_provider.xml @@ -0,0 +1,41 @@ + + + + + + + + + + diff --git a/client/app/src/main/res/values/strings.xml b/client/app/src/main/res/values/strings.xml index e7882e2b..ef33eb3b 100644 --- a/client/app/src/main/res/values/strings.xml +++ b/client/app/src/main/res/values/strings.xml @@ -74,4 +74,8 @@ Alarm Triggered Alarm triggered via Location History + Location Providers + Enable and reorder location providers. Drag to change priority. Note that the order will affect battery life (e.g. prioritising GPS over Network). + Drag to reorder + \ No newline at end of file diff --git a/server/.idea/scala_settings.xml b/server/.idea/scala_settings.xml index a981266a..9267278e 100644 --- a/server/.idea/scala_settings.xml +++ b/server/.idea/scala_settings.xml @@ -4,5 +4,6 @@