foreground-location 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ForegroundLocation.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +931 -0
- package/android/build.gradle +74 -0
- package/android/src/main/AndroidManifest.xml +30 -0
- package/android/src/main/java/in/xconcepts/foreground/location/APIService.java +449 -0
- package/android/src/main/java/in/xconcepts/foreground/location/ForeGroundLocation.java +58 -0
- package/android/src/main/java/in/xconcepts/foreground/location/ForeGroundLocationPlugin.java +650 -0
- package/android/src/main/java/in/xconcepts/foreground/location/LocationForegroundService.java +526 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/esm/definitions.d.ts +240 -0
- package/dist/esm/definitions.js +26 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +16 -0
- package/dist/esm/web.js +97 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +138 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +141 -0
- package/dist/plugin.js.map +1 -0
- package/docs/CHANGES-SUMMARY.md +69 -0
- package/docs/setup-and-examples.md +2851 -0
- package/ios/Sources/ForeGroundLocationPlugin/ForeGroundLocation.swift +75 -0
- package/ios/Sources/ForeGroundLocationPlugin/ForeGroundLocationPlugin.swift +125 -0
- package/ios/Tests/ForeGroundLocationPluginTests/ForeGroundLocationPluginTests.swift +36 -0
- package/package.json +82 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
package in.xconcepts.foreground.location;
|
|
2
|
+
|
|
3
|
+
import android.app.Notification;
|
|
4
|
+
import android.app.NotificationChannel;
|
|
5
|
+
import android.app.NotificationManager;
|
|
6
|
+
import android.app.PendingIntent;
|
|
7
|
+
import android.app.Service;
|
|
8
|
+
import android.content.Context;
|
|
9
|
+
import android.content.Intent;
|
|
10
|
+
import android.content.pm.PackageManager;
|
|
11
|
+
import android.location.Location;
|
|
12
|
+
import android.os.Binder;
|
|
13
|
+
import android.os.Build;
|
|
14
|
+
import android.os.IBinder;
|
|
15
|
+
import android.os.Looper;
|
|
16
|
+
import android.util.Log;
|
|
17
|
+
|
|
18
|
+
import androidx.annotation.NonNull;
|
|
19
|
+
import androidx.core.app.NotificationCompat;
|
|
20
|
+
import androidx.core.content.ContextCompat;
|
|
21
|
+
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
22
|
+
|
|
23
|
+
import com.google.android.gms.location.FusedLocationProviderClient;
|
|
24
|
+
import com.google.android.gms.location.LocationAvailability;
|
|
25
|
+
import com.google.android.gms.location.LocationCallback;
|
|
26
|
+
import com.google.android.gms.location.LocationRequest;
|
|
27
|
+
import com.google.android.gms.location.LocationResult;
|
|
28
|
+
import com.google.android.gms.location.LocationServices;
|
|
29
|
+
import com.google.android.gms.location.Priority;
|
|
30
|
+
|
|
31
|
+
import java.text.SimpleDateFormat;
|
|
32
|
+
import java.util.Date;
|
|
33
|
+
import java.util.HashMap;
|
|
34
|
+
import java.util.Iterator;
|
|
35
|
+
import java.util.Locale;
|
|
36
|
+
import java.util.Map;
|
|
37
|
+
import java.util.TimeZone;
|
|
38
|
+
|
|
39
|
+
import org.json.JSONException;
|
|
40
|
+
import org.json.JSONObject;
|
|
41
|
+
|
|
42
|
+
public class LocationForegroundService extends Service {
|
|
43
|
+
private static final String TAG = "LocationForegroundService";
|
|
44
|
+
private static final int NOTIFICATION_ID = 1001;
|
|
45
|
+
private static final String CHANNEL_ID = "location_service_channel";
|
|
46
|
+
|
|
47
|
+
public static final String ACTION_LOCATION_UPDATE = "in.xconcepts.foreground.location.LOCATION_UPDATE";
|
|
48
|
+
public static final String ACTION_SERVICE_STATUS = "in.xconcepts.foreground.location.SERVICE_STATUS";
|
|
49
|
+
public static final String EXTRA_LOCATION_DATA = "location_data";
|
|
50
|
+
public static final String EXTRA_SERVICE_STATUS = "service_status";
|
|
51
|
+
public static final String EXTRA_ERROR_MESSAGE = "error_message";
|
|
52
|
+
|
|
53
|
+
private FusedLocationProviderClient fusedLocationClient;
|
|
54
|
+
private LocationCallback locationCallback;
|
|
55
|
+
private LocationRequest locationRequest;
|
|
56
|
+
private NotificationManager notificationManager;
|
|
57
|
+
private boolean isLocationUpdatesActive = false;
|
|
58
|
+
|
|
59
|
+
// Service configuration
|
|
60
|
+
private String notificationTitle = "Location Tracking";
|
|
61
|
+
private String notificationText = "Tracking your location in the background";
|
|
62
|
+
private String notificationIcon = null; // Custom icon name from app
|
|
63
|
+
private long updateInterval = 60000; // 1 minute
|
|
64
|
+
private long fastestInterval = 30000; // 30 seconds
|
|
65
|
+
private int priority = Priority.PRIORITY_HIGH_ACCURACY;
|
|
66
|
+
private long distanceFilter = 0; // Distance filter in meters (0 = no filter)
|
|
67
|
+
|
|
68
|
+
// API Service Integration
|
|
69
|
+
private APIService apiService;
|
|
70
|
+
private boolean enableApiService = false;
|
|
71
|
+
|
|
72
|
+
// API Configuration
|
|
73
|
+
private String apiUrl;
|
|
74
|
+
private String apiType;
|
|
75
|
+
private Map<String, String> apiHeaders;
|
|
76
|
+
private JSONObject apiAdditionalParams;
|
|
77
|
+
private int apiIntervalMinutes = 5;
|
|
78
|
+
|
|
79
|
+
private final IBinder binder = new LocationServiceBinder();
|
|
80
|
+
|
|
81
|
+
public class LocationServiceBinder extends Binder {
|
|
82
|
+
LocationForegroundService getService() {
|
|
83
|
+
return LocationForegroundService.this;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Override
|
|
88
|
+
public void onCreate() {
|
|
89
|
+
super.onCreate();
|
|
90
|
+
Log.d(TAG, "LocationForegroundService created");
|
|
91
|
+
|
|
92
|
+
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
|
|
93
|
+
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
|
94
|
+
apiService = new APIService(this); // Initialize API service
|
|
95
|
+
|
|
96
|
+
createNotificationChannel();
|
|
97
|
+
setupLocationCallback();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Override
|
|
101
|
+
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
102
|
+
Log.d(TAG, "LocationForegroundService started");
|
|
103
|
+
|
|
104
|
+
if (intent != null) {
|
|
105
|
+
extractConfiguration(intent);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
startForeground(NOTIFICATION_ID, createNotification());
|
|
109
|
+
startLocationUpdates();
|
|
110
|
+
|
|
111
|
+
// Restart service if killed by system
|
|
112
|
+
return START_STICKY;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Override
|
|
116
|
+
public IBinder onBind(Intent intent) {
|
|
117
|
+
return binder;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@Override
|
|
121
|
+
public void onDestroy() {
|
|
122
|
+
Log.d(TAG, "LocationForegroundService destroyed");
|
|
123
|
+
stopLocationUpdates();
|
|
124
|
+
|
|
125
|
+
// Cleanup API service
|
|
126
|
+
if (apiService != null) {
|
|
127
|
+
apiService.shutdown();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
super.onDestroy();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private void extractConfiguration(Intent intent) {
|
|
134
|
+
if (intent.hasExtra("notificationTitle")) {
|
|
135
|
+
notificationTitle = intent.getStringExtra("notificationTitle");
|
|
136
|
+
}
|
|
137
|
+
if (intent.hasExtra("notificationText")) {
|
|
138
|
+
notificationText = intent.getStringExtra("notificationText");
|
|
139
|
+
}
|
|
140
|
+
if (intent.hasExtra("notificationIcon")) {
|
|
141
|
+
notificationIcon = intent.getStringExtra("notificationIcon");
|
|
142
|
+
}
|
|
143
|
+
if (intent.hasExtra("updateInterval")) {
|
|
144
|
+
updateInterval = intent.getLongExtra("updateInterval", 60000);
|
|
145
|
+
}
|
|
146
|
+
if (intent.hasExtra("fastestInterval")) {
|
|
147
|
+
fastestInterval = intent.getLongExtra("fastestInterval", 30000);
|
|
148
|
+
}
|
|
149
|
+
if (intent.hasExtra("priority")) {
|
|
150
|
+
String priorityStr = intent.getStringExtra("priority");
|
|
151
|
+
priority = convertPriority(priorityStr);
|
|
152
|
+
}
|
|
153
|
+
if (intent.hasExtra("distanceFilter")) {
|
|
154
|
+
distanceFilter = intent.getLongExtra("distanceFilter", 0L);
|
|
155
|
+
Log.d(TAG, "Distance filter set to: " + distanceFilter + " meters");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Extract API configuration
|
|
159
|
+
if (intent.hasExtra("apiUrl")) {
|
|
160
|
+
apiUrl = intent.getStringExtra("apiUrl");
|
|
161
|
+
enableApiService = apiUrl != null && !apiUrl.isEmpty();
|
|
162
|
+
Log.d(TAG, "API Service " + (enableApiService ? "enabled" : "disabled"));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (enableApiService) {
|
|
166
|
+
if (intent.hasExtra("apiType")) {
|
|
167
|
+
apiType = intent.getStringExtra("apiType");
|
|
168
|
+
}
|
|
169
|
+
if (intent.hasExtra("apiHeaders")) {
|
|
170
|
+
try {
|
|
171
|
+
String headersJson = intent.getStringExtra("apiHeaders");
|
|
172
|
+
if (headersJson != null) {
|
|
173
|
+
JSONObject headersObj = new JSONObject(headersJson);
|
|
174
|
+
apiHeaders = new HashMap<>();
|
|
175
|
+
Iterator<String> keys = headersObj.keys();
|
|
176
|
+
while (keys.hasNext()) {
|
|
177
|
+
String key = keys.next();
|
|
178
|
+
apiHeaders.put(key, headersObj.getString(key));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch (JSONException e) {
|
|
182
|
+
Log.e(TAG, "Error parsing API headers", e);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (intent.hasExtra("apiAdditionalParams")) {
|
|
186
|
+
try {
|
|
187
|
+
String paramsJson = intent.getStringExtra("apiAdditionalParams");
|
|
188
|
+
if (paramsJson != null && !paramsJson.isEmpty()) {
|
|
189
|
+
apiAdditionalParams = new JSONObject(paramsJson);
|
|
190
|
+
}
|
|
191
|
+
} catch (JSONException e) {
|
|
192
|
+
Log.e(TAG, "Error parsing API additional params", e);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (intent.hasExtra("apiInterval")) {
|
|
196
|
+
apiIntervalMinutes = intent.getIntExtra("apiInterval", 5);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Configure API service
|
|
200
|
+
configureApiService();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private int convertPriority(String priorityStr) {
|
|
205
|
+
if (priorityStr == null) return Priority.PRIORITY_HIGH_ACCURACY;
|
|
206
|
+
|
|
207
|
+
switch (priorityStr) {
|
|
208
|
+
case "HIGH_ACCURACY":
|
|
209
|
+
return Priority.PRIORITY_HIGH_ACCURACY;
|
|
210
|
+
case "BALANCED_POWER":
|
|
211
|
+
return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
|
|
212
|
+
case "LOW_POWER":
|
|
213
|
+
return Priority.PRIORITY_LOW_POWER;
|
|
214
|
+
case "NO_POWER":
|
|
215
|
+
return Priority.PRIORITY_PASSIVE;
|
|
216
|
+
default:
|
|
217
|
+
return Priority.PRIORITY_HIGH_ACCURACY;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private void configureApiService() {
|
|
222
|
+
if (enableApiService && apiUrl != null && !apiUrl.isEmpty()) {
|
|
223
|
+
apiService.configure(apiUrl, apiType, apiHeaders, apiAdditionalParams, apiIntervalMinutes);
|
|
224
|
+
Log.d(TAG, "API Service configured successfully");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private void createNotificationChannel() {
|
|
229
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
230
|
+
NotificationChannel channel = new NotificationChannel(
|
|
231
|
+
CHANNEL_ID,
|
|
232
|
+
"Location Service",
|
|
233
|
+
NotificationManager.IMPORTANCE_LOW
|
|
234
|
+
);
|
|
235
|
+
channel.setDescription("Continuous location tracking");
|
|
236
|
+
channel.setShowBadge(false);
|
|
237
|
+
notificationManager.createNotificationChannel(channel);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private Notification createNotification() {
|
|
242
|
+
Intent notificationIntent = new Intent();
|
|
243
|
+
PendingIntent pendingIntent = PendingIntent.getActivity(
|
|
244
|
+
this, 0, notificationIntent,
|
|
245
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
|
249
|
+
.setContentTitle(notificationTitle)
|
|
250
|
+
.setContentText(notificationText)
|
|
251
|
+
.setSmallIcon(getNotificationIcon())
|
|
252
|
+
.setContentIntent(pendingIntent)
|
|
253
|
+
.setOngoing(true)
|
|
254
|
+
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
255
|
+
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
256
|
+
.build();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private int getNotificationIcon() {
|
|
260
|
+
// Try custom icon first if provided
|
|
261
|
+
if (notificationIcon != null && !notificationIcon.isEmpty()) {
|
|
262
|
+
int customIconResId = getResources().getIdentifier(
|
|
263
|
+
notificationIcon,
|
|
264
|
+
"drawable",
|
|
265
|
+
getPackageName()
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (customIconResId != 0) {
|
|
269
|
+
Log.d(TAG, "Using custom notification icon: " + notificationIcon);
|
|
270
|
+
return customIconResId;
|
|
271
|
+
} else {
|
|
272
|
+
Log.w(TAG, "Custom icon not found: " + notificationIcon + ", falling back to app icon");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Try to get application icon
|
|
277
|
+
try {
|
|
278
|
+
android.content.pm.ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
|
|
279
|
+
getPackageName(),
|
|
280
|
+
android.content.pm.PackageManager.GET_META_DATA
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (appInfo.icon != 0) {
|
|
284
|
+
Log.d(TAG, "Using application icon for notification");
|
|
285
|
+
return appInfo.icon;
|
|
286
|
+
}
|
|
287
|
+
} catch (android.content.pm.PackageManager.NameNotFoundException e) {
|
|
288
|
+
Log.e(TAG, "Could not get application info", e);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Final fallback to system location icon
|
|
292
|
+
Log.d(TAG, "Using system default location icon");
|
|
293
|
+
return android.R.drawable.ic_menu_mylocation;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private void setupLocationCallback() {
|
|
297
|
+
locationCallback = new LocationCallback() {
|
|
298
|
+
@Override
|
|
299
|
+
public void onLocationResult(@NonNull LocationResult locationResult) {
|
|
300
|
+
for (Location location : locationResult.getLocations()) {
|
|
301
|
+
processLocationUpdate(location);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@Override
|
|
306
|
+
public void onLocationAvailability(@NonNull LocationAvailability locationAvailability) {
|
|
307
|
+
Log.d(TAG, "Location availability: " + locationAvailability.isLocationAvailable());
|
|
308
|
+
if (!locationAvailability.isLocationAvailable()) {
|
|
309
|
+
broadcastServiceStatus(false, "Location not available");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private void startLocationUpdates() {
|
|
316
|
+
if (!hasLocationPermissions()) {
|
|
317
|
+
Log.e(TAG, "Location permissions not granted");
|
|
318
|
+
broadcastServiceStatus(false, "Location permissions not granted");
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
createLocationRequest();
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
fusedLocationClient.requestLocationUpdates(
|
|
326
|
+
locationRequest,
|
|
327
|
+
locationCallback,
|
|
328
|
+
Looper.getMainLooper()
|
|
329
|
+
);
|
|
330
|
+
isLocationUpdatesActive = true;
|
|
331
|
+
|
|
332
|
+
// Start API service if configured
|
|
333
|
+
if (enableApiService) {
|
|
334
|
+
apiService.startApiService();
|
|
335
|
+
Log.d(TAG, "API Service started");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
broadcastServiceStatus(true, null);
|
|
339
|
+
Log.d(TAG, "Location updates started");
|
|
340
|
+
} catch (SecurityException e) {
|
|
341
|
+
Log.e(TAG, "Security exception when requesting location updates", e);
|
|
342
|
+
broadcastServiceStatus(false, "Security exception: " + e.getMessage());
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private void stopLocationUpdates() {
|
|
347
|
+
if (fusedLocationClient != null && locationCallback != null) {
|
|
348
|
+
fusedLocationClient.removeLocationUpdates(locationCallback);
|
|
349
|
+
isLocationUpdatesActive = false;
|
|
350
|
+
|
|
351
|
+
// Stop API service
|
|
352
|
+
if (apiService != null) {
|
|
353
|
+
apiService.stopApiService();
|
|
354
|
+
Log.d(TAG, "API Service stopped");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
broadcastServiceStatus(false, null);
|
|
358
|
+
Log.d(TAG, "Location updates stopped");
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private void createLocationRequest() {
|
|
363
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
364
|
+
// Use new Builder API (API 31+)
|
|
365
|
+
LocationRequest.Builder builder = new LocationRequest.Builder(priority, updateInterval)
|
|
366
|
+
.setWaitForAccurateLocation(false)
|
|
367
|
+
.setMinUpdateIntervalMillis(fastestInterval)
|
|
368
|
+
.setMaxUpdateDelayMillis(updateInterval * 2);
|
|
369
|
+
|
|
370
|
+
// Add distance filter if specified
|
|
371
|
+
if (distanceFilter > 0) {
|
|
372
|
+
builder.setMinUpdateDistanceMeters((float) distanceFilter);
|
|
373
|
+
Log.d(TAG, "Distance filter applied: " + distanceFilter + " meters");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
locationRequest = builder.build();
|
|
377
|
+
} else {
|
|
378
|
+
// Use legacy API (API 23+)
|
|
379
|
+
locationRequest = LocationRequest.create()
|
|
380
|
+
.setPriority(priority)
|
|
381
|
+
.setInterval(updateInterval)
|
|
382
|
+
.setFastestInterval(fastestInterval)
|
|
383
|
+
.setMaxWaitTime(updateInterval * 2);
|
|
384
|
+
|
|
385
|
+
// Add distance filter for legacy API
|
|
386
|
+
if (distanceFilter > 0) {
|
|
387
|
+
locationRequest.setSmallestDisplacement((float) distanceFilter);
|
|
388
|
+
Log.d(TAG, "Distance filter applied (legacy): " + distanceFilter + " meters");
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private boolean hasLocationPermissions() {
|
|
394
|
+
return ContextCompat.checkSelfPermission(this,
|
|
395
|
+
android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private void processLocationUpdate(Location location) {
|
|
399
|
+
Log.d(TAG, "Location update: " + location.getLatitude() + ", " + location.getLongitude());
|
|
400
|
+
|
|
401
|
+
// Create ISO 8601 timestamp
|
|
402
|
+
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
|
403
|
+
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
404
|
+
String timestamp = sdf.format(new Date(location.getTime()));
|
|
405
|
+
|
|
406
|
+
// ALWAYS broadcast to Ionic app (regardless of API configuration)
|
|
407
|
+
Intent intent = new Intent(ACTION_LOCATION_UPDATE);
|
|
408
|
+
intent.putExtra("latitude", location.getLatitude());
|
|
409
|
+
intent.putExtra("longitude", location.getLongitude());
|
|
410
|
+
intent.putExtra("accuracy", (double) location.getAccuracy());
|
|
411
|
+
intent.putExtra("timestamp", timestamp);
|
|
412
|
+
|
|
413
|
+
if (location.hasAltitude()) {
|
|
414
|
+
intent.putExtra("altitude", location.getAltitude());
|
|
415
|
+
}
|
|
416
|
+
if (location.hasBearing()) {
|
|
417
|
+
intent.putExtra("bearing", (double) location.getBearing());
|
|
418
|
+
}
|
|
419
|
+
if (location.hasSpeed()) {
|
|
420
|
+
intent.putExtra("speed", (double) location.getSpeed());
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
|
424
|
+
|
|
425
|
+
// ADDITIONALLY send to API service if enabled
|
|
426
|
+
if (enableApiService && apiService != null) {
|
|
427
|
+
try {
|
|
428
|
+
JSONObject locationData = new JSONObject();
|
|
429
|
+
locationData.put("latitude", location.getLatitude());
|
|
430
|
+
locationData.put("longitude", location.getLongitude());
|
|
431
|
+
locationData.put("accuracy", (double) location.getAccuracy());
|
|
432
|
+
locationData.put("timestamp", timestamp);
|
|
433
|
+
|
|
434
|
+
if (location.hasAltitude()) {
|
|
435
|
+
locationData.put("altitude", location.getAltitude());
|
|
436
|
+
}
|
|
437
|
+
if (location.hasBearing()) {
|
|
438
|
+
locationData.put("bearing", (double) location.getBearing());
|
|
439
|
+
}
|
|
440
|
+
if (location.hasSpeed()) {
|
|
441
|
+
locationData.put("speed", (double) location.getSpeed());
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
apiService.addLocationData(locationData);
|
|
445
|
+
} catch (JSONException e) {
|
|
446
|
+
Log.e(TAG, "Error creating location data JSON for API", e);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Update notification with current location
|
|
451
|
+
updateNotificationWithLocation(location);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private void updateNotificationWithLocation(Location location) {
|
|
455
|
+
// String updatedText = String.format(Locale.US,
|
|
456
|
+
// "Lat: %.6f, Lng: %.6f",
|
|
457
|
+
// location.getLatitude(),
|
|
458
|
+
// location.getLongitude()
|
|
459
|
+
// );
|
|
460
|
+
|
|
461
|
+
Notification updatedNotification = new NotificationCompat.Builder(this, CHANNEL_ID)
|
|
462
|
+
.setContentTitle(notificationTitle)
|
|
463
|
+
.setContentText(notificationText)
|
|
464
|
+
.setSmallIcon(getNotificationIcon())
|
|
465
|
+
.setOngoing(true)
|
|
466
|
+
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
467
|
+
.build();
|
|
468
|
+
|
|
469
|
+
notificationManager.notify(NOTIFICATION_ID, updatedNotification);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
private void broadcastServiceStatus(boolean isRunning, String error) {
|
|
473
|
+
Intent intent = new Intent(ACTION_SERVICE_STATUS);
|
|
474
|
+
intent.putExtra(EXTRA_SERVICE_STATUS, isRunning);
|
|
475
|
+
if (error != null) {
|
|
476
|
+
intent.putExtra(EXTRA_ERROR_MESSAGE, error);
|
|
477
|
+
}
|
|
478
|
+
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Public methods for service control
|
|
482
|
+
public boolean isLocationUpdatesActive() {
|
|
483
|
+
return isLocationUpdatesActive;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
public int getApiBufferSize() {
|
|
487
|
+
return apiService != null ? apiService.getBufferSize() : 0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
public void clearApiBuffers() {
|
|
491
|
+
if (apiService != null) {
|
|
492
|
+
apiService.clearBuffers();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
public boolean isApiServiceEnabled() {
|
|
497
|
+
return enableApiService;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
public boolean isApiHealthy() {
|
|
501
|
+
return apiService != null ? apiService.isApiHealthy() : false;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
public void resetApiCircuitBreaker() {
|
|
505
|
+
if (apiService != null) {
|
|
506
|
+
apiService.resetCircuitBreaker();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
public void updateServiceConfiguration(String title, String text, String icon, long interval, long fastest, int priority, long distanceFilter) {
|
|
511
|
+
this.notificationTitle = title;
|
|
512
|
+
this.notificationText = text;
|
|
513
|
+
this.notificationIcon = icon;
|
|
514
|
+
this.updateInterval = interval;
|
|
515
|
+
this.fastestInterval = fastest;
|
|
516
|
+
this.priority = priority;
|
|
517
|
+
this.distanceFilter = distanceFilter;
|
|
518
|
+
|
|
519
|
+
// Restart location updates with new configuration
|
|
520
|
+
stopLocationUpdates();
|
|
521
|
+
startLocationUpdates();
|
|
522
|
+
|
|
523
|
+
// Update notification
|
|
524
|
+
notificationManager.notify(NOTIFICATION_ID, createNotification());
|
|
525
|
+
}
|
|
526
|
+
}
|
|
File without changes
|