generated from Grigo/AndroidTemplate
added sleepmode
This commit is contained in:
@@ -6,6 +6,12 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
@@ -30,6 +36,10 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name=".LoraForegroundService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="location|dataSync" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -18,6 +18,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class CommandPoller {
|
||||
@@ -32,6 +34,11 @@ public class CommandPoller {
|
||||
private final TrackRecorder trackRecorder;
|
||||
private final PeerStatsCache peerStatsCache;
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "CommandPoller");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||
|
||||
@@ -75,35 +82,29 @@ public class CommandPoller {
|
||||
if (!running.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
scheduleCommandPoll();
|
||||
schedulePairedPoll();
|
||||
scheduler.scheduleWithFixedDelay(
|
||||
this::pollCommandsSafe, 0, COMMAND_POLL_MS, TimeUnit.MILLISECONDS);
|
||||
scheduler.scheduleWithFixedDelay(
|
||||
this::pollPairedSafe, 0, PAIRED_POLL_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running.set(false);
|
||||
}
|
||||
|
||||
private void scheduleCommandPoll() {
|
||||
executor.execute(() -> {
|
||||
if (running.get()) {
|
||||
private void pollCommandsSafe() {
|
||||
if (!running.get()) {
|
||||
return;
|
||||
}
|
||||
pollCommands();
|
||||
}
|
||||
if (running.get()) {
|
||||
mainHandler.postDelayed(this::scheduleCommandPoll, COMMAND_POLL_MS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void schedulePairedPoll() {
|
||||
executor.execute(() -> {
|
||||
if (running.get()) {
|
||||
private void pollPairedSafe() {
|
||||
if (!running.get()) {
|
||||
return;
|
||||
}
|
||||
pollPairedSession();
|
||||
}
|
||||
if (running.get()) {
|
||||
mainHandler.postDelayed(this::schedulePairedPoll, PAIRED_POLL_MS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void pollCommands() {
|
||||
try {
|
||||
@@ -181,7 +182,7 @@ public class CommandPoller {
|
||||
}
|
||||
startedSessionId = session.id;
|
||||
pendingAckSessionId = session.id;
|
||||
mainHandler.post(trackRecorder::start);
|
||||
trackRecorder.start();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "paired poll failed", e);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.grigowashere.loratester;
|
||||
import android.app.Application;
|
||||
|
||||
import com.grigowashere.loratester.api.ServerApi;
|
||||
import com.grigowashere.loratester.location.LocationTracker;
|
||||
import com.grigowashere.loratester.net.NetworkMonitor;
|
||||
import com.grigowashere.loratester.track.TrackRecorder;
|
||||
|
||||
@@ -16,6 +17,7 @@ public class LoraApp extends Application {
|
||||
private NetworkMonitor networkMonitor;
|
||||
private PeerStatsCache peerStatsCache;
|
||||
private CommandPoller commandPoller;
|
||||
private LocationTracker locationTracker;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
@@ -76,6 +78,22 @@ public class LoraApp extends Application {
|
||||
return commandPoller;
|
||||
}
|
||||
|
||||
public synchronized void startLocationUpdates() {
|
||||
if (locationTracker == null) {
|
||||
locationTracker = new LocationTracker(this, (lat, lon, alt) -> {
|
||||
telemetryUploader.updateLocation(lat, lon);
|
||||
trackRecorder.updateLocation(lat, lon, alt);
|
||||
});
|
||||
}
|
||||
locationTracker.start();
|
||||
}
|
||||
|
||||
public synchronized void stopLocationUpdates() {
|
||||
if (locationTracker != null) {
|
||||
locationTracker.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshTrackRecorder() {
|
||||
if (commandPoller != null) {
|
||||
commandPoller.stop();
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.grigowashere.loratester;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.grigowashere.loratester.track.TrackRecorder;
|
||||
|
||||
public class LoraForegroundService extends Service {
|
||||
|
||||
private static final String CHANNEL_ID = "lora_background";
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private LoraApp app;
|
||||
|
||||
private final Runnable notificationTicker = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateNotification();
|
||||
handler.postDelayed(this, 5000L);
|
||||
}
|
||||
};
|
||||
|
||||
public static void ensureRunning(Context context) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
Intent intent = new Intent(appContext, LoraForegroundService.class);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
appContext.startForegroundService(intent);
|
||||
} else {
|
||||
appContext.startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
app = (LoraApp) getApplication();
|
||||
createNotificationChannel();
|
||||
acquireWakeLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Notification notification = buildNotification();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(
|
||||
NOTIFICATION_ID,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
|
||||
| ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
app.startLocationUpdates();
|
||||
handler.removeCallbacks(notificationTicker);
|
||||
handler.post(notificationTicker);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
handler.removeCallbacks(notificationTicker);
|
||||
releaseWakeLock();
|
||||
app.stopLocationUpdates();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(Intent rootIntent) {
|
||||
TelemetryUploader uploader = app.getTelemetryUploader();
|
||||
if (uploader != null) {
|
||||
uploader.stopTelnet();
|
||||
}
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
stopSelf();
|
||||
super.onTaskRemoved(rootIntent);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void acquireWakeLock() {
|
||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (pm == null) {
|
||||
return;
|
||||
}
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "LoraTester::Background");
|
||||
wakeLock.setReferenceCounted(false);
|
||||
wakeLock.acquire();
|
||||
}
|
||||
|
||||
private void releaseWakeLock() {
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
wakeLock = null;
|
||||
}
|
||||
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
getString(R.string.notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setDescription(getString(R.string.notification_channel_desc));
|
||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||
if (nm != null) {
|
||||
nm.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
private Notification buildNotification() {
|
||||
Intent open = new Intent(this, MainActivity.class);
|
||||
open.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent pending = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
open,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
TelemetryUploader uploader = app.getTelemetryUploader();
|
||||
TrackRecorder recorder = app.getTrackRecorder();
|
||||
SettingsRepository settings = app.getSettingsRepository();
|
||||
|
||||
boolean telnetOn = settings.isTelnetEnabled();
|
||||
boolean telnetConnected = uploader != null && uploader.isTelnetConnected();
|
||||
boolean recording = recorder != null && recorder.isRecording();
|
||||
int points = recorder != null ? recorder.getPointCount() : 0;
|
||||
|
||||
String telnetLine = telnetOn
|
||||
? getString(telnetConnected ? R.string.telnet_connected : R.string.telnet_disconnected)
|
||||
: getString(R.string.telnet_disabled_short);
|
||||
String trackLine = recording
|
||||
? getString(R.string.notification_track_recording, points)
|
||||
: getString(R.string.notification_track_idle);
|
||||
|
||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_stat_service)
|
||||
.setContentTitle(getString(R.string.notification_title))
|
||||
.setContentText(telnetLine + " · " + trackLine)
|
||||
.setSubText(getString(R.string.notification_subtitle))
|
||||
.setContentIntent(pending)
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void updateNotification() {
|
||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||
if (nm != null) {
|
||||
nm.notify(NOTIFICATION_ID, buildNotification());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.grigowashere.loratester;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -17,19 +23,30 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import com.grigowashere.loratester.location.LocationTracker;
|
||||
import com.grigowashere.loratester.track.TrackRecorder;
|
||||
import com.grigowashere.loratester.ui.MainPagerAdapter;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private TelemetryUploader telemetryUploader;
|
||||
private LocationTracker locationTracker;
|
||||
private LoraApp app;
|
||||
private SettingsRepository settings;
|
||||
private boolean backgroundLocationRequested;
|
||||
|
||||
private final ActivityResultLauncher<String[]> locationPermissionLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.RequestMultiplePermissions(),
|
||||
result -> startLocationIfPermitted()
|
||||
result -> onForegroundLocationReady()
|
||||
);
|
||||
|
||||
private final ActivityResultLauncher<String> backgroundLocationLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
granted -> startBackgroundWork()
|
||||
);
|
||||
|
||||
private final ActivityResultLauncher<String> notificationPermissionLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
granted -> startBackgroundWork()
|
||||
);
|
||||
|
||||
@Override
|
||||
@@ -43,9 +60,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
return insets;
|
||||
});
|
||||
|
||||
LoraApp app = (LoraApp) getApplication();
|
||||
telemetryUploader = app.getTelemetryUploader();
|
||||
SettingsRepository settings = app.getSettingsRepository();
|
||||
app = (LoraApp) getApplication();
|
||||
settings = app.getSettingsRepository();
|
||||
|
||||
ViewPager2 pager = findViewById(R.id.viewPager);
|
||||
TabLayout tabs = findViewById(R.id.tabLayout);
|
||||
@@ -62,41 +78,92 @@ public class MainActivity extends AppCompatActivity {
|
||||
tab.setText(titleRes);
|
||||
}).attach();
|
||||
|
||||
TrackRecorder trackRecorder = app.getTrackRecorder();
|
||||
locationTracker = new LocationTracker(this, (lat, lon, alt) -> {
|
||||
telemetryUploader.updateLocation(lat, lon);
|
||||
trackRecorder.updateLocation(lat, lon, alt);
|
||||
});
|
||||
|
||||
requestLocationPermission();
|
||||
requestStartupPermissions();
|
||||
if (settings.isTelnetEnabled()) {
|
||||
telemetryUploader.startTelnet();
|
||||
app.getTelemetryUploader().startTelnet();
|
||||
}
|
||||
}
|
||||
|
||||
private void requestLocationPermission() {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
startLocationIfPermitted();
|
||||
} else {
|
||||
private void requestStartupPermissions() {
|
||||
if (hasForegroundLocation()) {
|
||||
onForegroundLocationReady();
|
||||
return;
|
||||
}
|
||||
locationPermissionLauncher.launch(new String[]{
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
});
|
||||
}
|
||||
|
||||
private void onForegroundLocationReady() {
|
||||
if (!hasForegroundLocation()) {
|
||||
Toast.makeText(this, R.string.background_location_required, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
requestNotificationPermissionIfNeeded();
|
||||
requestBackgroundLocationIfNeeded();
|
||||
startBackgroundWork();
|
||||
}
|
||||
|
||||
private void startLocationIfPermitted() {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
private void requestNotificationPermissionIfNeeded() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
return;
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
locationTracker.start();
|
||||
return;
|
||||
}
|
||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
|
||||
}
|
||||
|
||||
private void requestBackgroundLocationIfNeeded() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
return;
|
||||
}
|
||||
if (hasBackgroundLocation()) {
|
||||
return;
|
||||
}
|
||||
if (backgroundLocationRequested) {
|
||||
return;
|
||||
}
|
||||
backgroundLocationRequested = true;
|
||||
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
|
||||
Toast.makeText(this, R.string.background_location_rationale, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
backgroundLocationLauncher.launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
|
||||
}
|
||||
|
||||
private void startBackgroundWork() {
|
||||
LoraForegroundService.ensureRunning(this);
|
||||
if (settings.isTelnetEnabled()) {
|
||||
app.getTelemetryUploader().startTelnet();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
locationTracker.stop();
|
||||
telemetryUploader.stopTelnet();
|
||||
super.onDestroy();
|
||||
private boolean hasForegroundLocation() {
|
||||
return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private boolean hasBackgroundLocation() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
return hasForegroundLocation();
|
||||
}
|
||||
return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public static void openBatteryOptimizationSettings(@NonNull android.content.Context context) {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + context.getPackageName()));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static boolean isIgnoringBatteryOptimizations(@NonNull android.content.Context context) {
|
||||
PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE);
|
||||
if (pm == null) {
|
||||
return true;
|
||||
}
|
||||
return pm.isIgnoringBatteryOptimizations(context.getPackageName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,11 @@ public class LocationTracker {
|
||||
}
|
||||
LocationRequest request = new LocationRequest.Builder(
|
||||
Priority.PRIORITY_HIGH_ACCURACY, 10_000L
|
||||
).setMinUpdateIntervalMillis(5_000L).build();
|
||||
)
|
||||
.setMinUpdateIntervalMillis(5_000L)
|
||||
.setMaxUpdateDelayMillis(15_000L)
|
||||
.setWaitForAccurateLocation(false)
|
||||
.build();
|
||||
|
||||
callback = new LocationCallback() {
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,8 @@ import androidx.fragment.app.Fragment;
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.grigowashere.loratester.LoraApp;
|
||||
import com.grigowashere.loratester.LoraForegroundService;
|
||||
import com.grigowashere.loratester.MainActivity;
|
||||
import com.grigowashere.loratester.R;
|
||||
import com.grigowashere.loratester.SettingsRepository;
|
||||
import com.grigowashere.loratester.TelemetryUploader;
|
||||
@@ -44,6 +46,7 @@ public class SettingsFragment extends Fragment {
|
||||
TextInputEditText editRange = view.findViewById(R.id.editRangeRegex);
|
||||
TextInputEditText editDeviceLabel = view.findViewById(R.id.editDeviceLabel);
|
||||
SwitchMaterial switchTelnet = view.findViewById(R.id.switchTelnet);
|
||||
Button batteryBtn = view.findViewById(R.id.btnBatteryOptimization);
|
||||
TextView deviceIdLabel = view.findViewById(R.id.deviceIdLabel);
|
||||
Button save = view.findViewById(R.id.btnSaveSettings);
|
||||
|
||||
@@ -59,6 +62,14 @@ public class SettingsFragment extends Fragment {
|
||||
switchTelnet.setChecked(settings.isTelnetEnabled());
|
||||
deviceIdLabel.setText(getString(R.string.device_id_label, settings.getOrCreateDeviceId()));
|
||||
|
||||
batteryBtn.setOnClickListener(v -> {
|
||||
if (MainActivity.isIgnoringBatteryOptimizations(requireContext())) {
|
||||
Toast.makeText(requireContext(), R.string.battery_optimization_done, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
MainActivity.openBatteryOptimizationSettings(requireContext());
|
||||
});
|
||||
|
||||
save.setOnClickListener(v -> {
|
||||
settings.setServerUrl(textOf(editServer, SettingsRepository.DEFAULT_SERVER));
|
||||
settings.setTelnetHost(textOf(editHost, SettingsRepository.DEFAULT_TELNET_HOST));
|
||||
@@ -78,6 +89,7 @@ public class SettingsFragment extends Fragment {
|
||||
} else {
|
||||
uploader.stopTelnet();
|
||||
}
|
||||
LoraForegroundService.ensureRunning(requireContext());
|
||||
Toast.makeText(requireContext(), R.string.saved, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z" />
|
||||
</vector>
|
||||
@@ -93,6 +93,20 @@
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/telnet_enabled" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/battery_optimization_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBatteryOptimization"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/battery_optimization" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/deviceIdLabel"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -113,4 +113,16 @@
|
||||
<string name="map_heatmap_legend_level">уровень</string>
|
||||
<string name="map_heatmap_legend_low">низина</string>
|
||||
<string name="chat_self_label">Вы</string>
|
||||
<string name="notification_channel_name">Фоновая работа</string>
|
||||
<string name="notification_channel_desc">Telnet, GPS и запись трека при свёрнутом приложении</string>
|
||||
<string name="notification_title">LoraTester активен</string>
|
||||
<string name="notification_subtitle">Работа в фоне</string>
|
||||
<string name="notification_track_recording">трек: %1$d точек</string>
|
||||
<string name="notification_track_idle">трек: нет</string>
|
||||
<string name="telnet_disabled_short">telnet: выкл</string>
|
||||
<string name="background_location_rationale">Для GPS при выключенном экране разрешите геолокацию «Всегда»</string>
|
||||
<string name="background_location_required">Нужен доступ к геолокации для трека и карты</string>
|
||||
<string name="battery_optimization">Без ограничений батареи</string>
|
||||
<string name="battery_optimization_hint">Рекомендуется на MIUI / ColorOS / EMUI для стабильного telnet</string>
|
||||
<string name="battery_optimization_done">Ограничения батареи уже отключены</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user