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.
@@ -0,0 +1,650 @@
1
+ package in.xconcepts.foreground.location;
2
+
3
+ import android.Manifest;
4
+ import android.content.BroadcastReceiver;
5
+ import android.content.ComponentName;
6
+ import android.content.Context;
7
+ import android.content.Intent;
8
+ import android.content.IntentFilter;
9
+ import android.content.ServiceConnection;
10
+ import android.content.pm.PackageManager;
11
+ import android.location.Location;
12
+ import android.os.Build;
13
+ import android.os.IBinder;
14
+ import android.util.Log;
15
+
16
+ import androidx.core.content.ContextCompat;
17
+ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
18
+
19
+ import com.getcapacitor.JSObject;
20
+ import com.getcapacitor.PermissionState;
21
+ import com.getcapacitor.Plugin;
22
+ import com.getcapacitor.PluginCall;
23
+ import com.getcapacitor.PluginMethod;
24
+ import com.getcapacitor.annotation.CapacitorPlugin;
25
+ import com.getcapacitor.annotation.Permission;
26
+ import com.getcapacitor.annotation.PermissionCallback;
27
+
28
+ import com.google.android.gms.location.FusedLocationProviderClient;
29
+ import com.google.android.gms.location.LocationServices;
30
+
31
+ @CapacitorPlugin(
32
+ name = "ForeGroundLocation",
33
+ permissions = {
34
+ @Permission(
35
+ alias = "location",
36
+ strings = {
37
+ Manifest.permission.ACCESS_FINE_LOCATION,
38
+ Manifest.permission.ACCESS_COARSE_LOCATION
39
+ }
40
+ ),
41
+ @Permission(
42
+ alias = "backgroundLocation",
43
+ strings = { Manifest.permission.ACCESS_BACKGROUND_LOCATION }
44
+ ),
45
+ @Permission(
46
+ alias = "notifications",
47
+ strings = { Manifest.permission.POST_NOTIFICATIONS }
48
+ )
49
+ }
50
+ )
51
+ public class ForeGroundLocationPlugin extends Plugin {
52
+ private static final String TAG = "ForeGroundLocationPlugin";
53
+
54
+ private ForeGroundLocation implementation = new ForeGroundLocation();
55
+ private LocationForegroundService locationService;
56
+ private boolean isServiceBound = false;
57
+ private FusedLocationProviderClient fusedLocationClient;
58
+
59
+ // Broadcast receiver for location updates
60
+ private BroadcastReceiver locationUpdateReceiver = new BroadcastReceiver() {
61
+ @Override
62
+ public void onReceive(Context context, Intent intent) {
63
+ if (LocationForegroundService.ACTION_LOCATION_UPDATE.equals(intent.getAction())) {
64
+ JSObject locationData = new JSObject();
65
+ locationData.put("latitude", intent.getDoubleExtra("latitude", 0.0));
66
+ locationData.put("longitude", intent.getDoubleExtra("longitude", 0.0));
67
+ locationData.put("accuracy", intent.getDoubleExtra("accuracy", 0.0));
68
+ locationData.put("timestamp", intent.getStringExtra("timestamp"));
69
+
70
+ if (intent.hasExtra("altitude")) {
71
+ locationData.put("altitude", intent.getDoubleExtra("altitude", 0.0));
72
+ }
73
+ if (intent.hasExtra("bearing")) {
74
+ locationData.put("bearing", intent.getDoubleExtra("bearing", 0.0));
75
+ }
76
+ if (intent.hasExtra("speed")) {
77
+ locationData.put("speed", intent.getDoubleExtra("speed", 0.0));
78
+ }
79
+
80
+ notifyListeners("locationUpdate", locationData);
81
+ }
82
+ }
83
+ };
84
+
85
+ // Broadcast receiver for service status
86
+ private BroadcastReceiver serviceStatusReceiver = new BroadcastReceiver() {
87
+ @Override
88
+ public void onReceive(Context context, Intent intent) {
89
+ if (LocationForegroundService.ACTION_SERVICE_STATUS.equals(intent.getAction())) {
90
+ JSObject statusData = new JSObject();
91
+ statusData.put("isRunning", intent.getBooleanExtra(LocationForegroundService.EXTRA_SERVICE_STATUS, false));
92
+
93
+ String error = intent.getStringExtra(LocationForegroundService.EXTRA_ERROR_MESSAGE);
94
+ if (error != null) {
95
+ statusData.put("error", error);
96
+ }
97
+
98
+ notifyListeners("serviceStatusChanged", statusData);
99
+ }
100
+ }
101
+ };
102
+
103
+ // Service connection
104
+ private ServiceConnection serviceConnection = new ServiceConnection() {
105
+ @Override
106
+ public void onServiceConnected(ComponentName name, IBinder service) {
107
+ LocationForegroundService.LocationServiceBinder binder =
108
+ (LocationForegroundService.LocationServiceBinder) service;
109
+ locationService = binder.getService();
110
+ isServiceBound = true;
111
+ Log.d(TAG, "Service connected");
112
+ }
113
+
114
+ @Override
115
+ public void onServiceDisconnected(ComponentName name) {
116
+ locationService = null;
117
+ isServiceBound = false;
118
+ Log.d(TAG, "Service disconnected");
119
+ }
120
+ };
121
+
122
+ @Override
123
+ public void load() {
124
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(getContext());
125
+
126
+ // Unregister existing receivers first (safety)
127
+ try {
128
+ LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(locationUpdateReceiver);
129
+ LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(serviceStatusReceiver);
130
+ } catch (Exception e) {
131
+ // Ignore if not registered
132
+ Log.d(TAG, "No existing receivers to unregister");
133
+ }
134
+
135
+ // Register broadcast receivers
136
+ LocalBroadcastManager.getInstance(getContext()).registerReceiver(
137
+ locationUpdateReceiver,
138
+ new IntentFilter(LocationForegroundService.ACTION_LOCATION_UPDATE)
139
+ );
140
+ LocalBroadcastManager.getInstance(getContext()).registerReceiver(
141
+ serviceStatusReceiver,
142
+ new IntentFilter(LocationForegroundService.ACTION_SERVICE_STATUS)
143
+ );
144
+
145
+ Log.d(TAG, "Plugin loaded and receivers registered");
146
+ }
147
+
148
+ @Override
149
+ protected void handleOnDestroy() {
150
+ // Unregister broadcast receivers safely
151
+ try {
152
+ LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(locationUpdateReceiver);
153
+ } catch (Exception e) {
154
+ Log.w(TAG, "Error unregistering locationUpdateReceiver", e);
155
+ }
156
+
157
+ try {
158
+ LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(serviceStatusReceiver);
159
+ } catch (Exception e) {
160
+ Log.w(TAG, "Error unregistering serviceStatusReceiver", e);
161
+ }
162
+
163
+ // Unbind service safely
164
+ if (isServiceBound) {
165
+ try {
166
+ getContext().unbindService(serviceConnection);
167
+ isServiceBound = false;
168
+ Log.d(TAG, "Service unbound successfully");
169
+ } catch (Exception e) {
170
+ Log.w(TAG, "Error unbinding service", e);
171
+ }
172
+ }
173
+
174
+ // Clean up references
175
+ locationService = null;
176
+ fusedLocationClient = null;
177
+
178
+ super.handleOnDestroy();
179
+ }
180
+
181
+ @PluginMethod
182
+ public void checkPermissions(PluginCall call) {
183
+ JSObject result = new JSObject();
184
+
185
+ result.put("location", getLocationPermissionState());
186
+ result.put("backgroundLocation", getBackgroundLocationPermissionState());
187
+ result.put("notifications", getNotificationPermissionState());
188
+
189
+ call.resolve(result);
190
+ }
191
+
192
+ @PluginMethod
193
+ public void requestPermissions(PluginCall call) {
194
+ // First request basic location permissions
195
+ if (getLocationPermissionState() != PermissionState.GRANTED) {
196
+ requestPermissionForAlias("location", call, "locationPermissionCallback");
197
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
198
+ getBackgroundLocationPermissionState() != PermissionState.GRANTED) {
199
+ requestPermissionForAlias("backgroundLocation", call, "backgroundLocationPermissionCallback");
200
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
201
+ getNotificationPermissionState() != PermissionState.GRANTED) {
202
+ requestPermissionForAlias("notifications", call, "notificationPermissionCallback");
203
+ } else {
204
+ // All permissions already granted
205
+ checkPermissions(call);
206
+ }
207
+ }
208
+
209
+ @PermissionCallback
210
+ private void locationPermissionCallback(PluginCall call) {
211
+ if (getLocationPermissionState() == PermissionState.GRANTED) {
212
+ // Continue with background location if needed
213
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
214
+ requestPermissionForAlias("backgroundLocation", call, "backgroundLocationPermissionCallback");
215
+ } else {
216
+ checkPermissions(call);
217
+ }
218
+ } else {
219
+ call.reject("Location permission is required for this feature");
220
+ }
221
+ }
222
+
223
+ @PermissionCallback
224
+ private void backgroundLocationPermissionCallback(PluginCall call) {
225
+ // Continue with notification permission if needed
226
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
227
+ requestPermissionForAlias("notifications", call, "notificationPermissionCallback");
228
+ } else {
229
+ checkPermissions(call);
230
+ }
231
+ }
232
+
233
+ @PermissionCallback
234
+ private void notificationPermissionCallback(PluginCall call) {
235
+ checkPermissions(call);
236
+ }
237
+
238
+ /**
239
+ * Start foreground location service with notification configuration
240
+ *
241
+ * Expected format (standardized approach):
242
+ * {
243
+ * "notification": {
244
+ * "title": "Location Tracking Active", // REQUIRED
245
+ * "text": "Recording your route...", // REQUIRED
246
+ * "icon": "ic_location" // OPTIONAL - drawable resource name
247
+ * },
248
+ * "interval": 60000, // OPTIONAL - update interval in ms
249
+ * "fastestInterval": 30000, // OPTIONAL - fastest interval in ms
250
+ * "priority": "HIGH_ACCURACY", // OPTIONAL - location priority
251
+ * "distanceFilter": 0, // OPTIONAL - distance filter in meters
252
+ * "api": { // OPTIONAL - API service configuration
253
+ * "url": "https://api.example.com/locations", // REQUIRED if api block present
254
+ * "type": "POST", // OPTIONAL - HTTP method (default: POST)
255
+ * "header": { // OPTIONAL - HTTP headers
256
+ * "Content-Type": "application/json",
257
+ * "Authorization": "Bearer token"
258
+ * },
259
+ * "additionalParams": { // OPTIONAL - additional parameters to send
260
+ * "userId": "123",
261
+ * "sessionId": "abc"
262
+ * },
263
+ * "apiInterval": 5 // OPTIONAL - API call interval in minutes (default: 5)
264
+ * }
265
+ * }
266
+ *
267
+ * Error codes:
268
+ * - PERMISSION_DENIED: Location permission not granted
269
+ * - INVALID_NOTIFICATION: Missing or invalid notification configuration
270
+ * - INVALID_PARAMETERS: Invalid interval or priority values
271
+ */
272
+ @PluginMethod
273
+ public void startForegroundLocationService(PluginCall call) {
274
+ if (getLocationPermissionState() != PermissionState.GRANTED) {
275
+ call.reject("PERMISSION_DENIED", "Location permission is required");
276
+ return;
277
+ }
278
+
279
+ // Validate notification configuration
280
+ JSObject notification = call.getObject("notification");
281
+ if (notification == null) {
282
+ call.reject("INVALID_NOTIFICATION", "notification parameter is required");
283
+ return;
284
+ }
285
+
286
+ String title = notification.getString("title");
287
+ String text = notification.getString("text");
288
+
289
+ if (title == null || title.trim().isEmpty()) {
290
+ call.reject("INVALID_NOTIFICATION", "notification.title is required and cannot be empty");
291
+ return;
292
+ }
293
+
294
+ if (text == null || text.trim().isEmpty()) {
295
+ call.reject("INVALID_NOTIFICATION", "notification.text is required and cannot be empty");
296
+ return;
297
+ }
298
+
299
+ Intent serviceIntent = new Intent(getContext(), LocationForegroundService.class);
300
+
301
+ // Add notification configuration to intent
302
+ serviceIntent.putExtra("notificationTitle", title);
303
+ serviceIntent.putExtra("notificationText", text);
304
+ serviceIntent.putExtra("notificationIcon", notification.getString("icon")); // Can be null
305
+
306
+ // Get and validate interval values
307
+ long updateInterval = call.getLong("interval", 60000L); // Default 60 seconds
308
+ long fastestInterval = call.getLong("fastestInterval", 30000L); // Default 30 seconds
309
+
310
+ // Validate intervals
311
+ if (updateInterval < 1000L) {
312
+ call.reject("INVALID_PARAMETERS", "interval must be at least 1000ms");
313
+ return;
314
+ }
315
+
316
+ if (fastestInterval < 1000L) {
317
+ call.reject("INVALID_PARAMETERS", "fastestInterval must be at least 1000ms");
318
+ return;
319
+ }
320
+
321
+ if (fastestInterval > updateInterval) {
322
+ call.reject("INVALID_PARAMETERS", "fastestInterval cannot be greater than interval");
323
+ return;
324
+ }
325
+
326
+ String priority = call.getString("priority", "HIGH_ACCURACY");
327
+ if (!isValidPriority(priority)) {
328
+ call.reject("INVALID_PARAMETERS", "priority must be one of: HIGH_ACCURACY, BALANCED_POWER, LOW_POWER, NO_POWER");
329
+ return;
330
+ }
331
+
332
+ // Get and validate distance filter
333
+ long distanceFilter = call.getLong("distanceFilter", 0L); // Default 0 = no filter
334
+ if (distanceFilter < 0L) {
335
+ call.reject("INVALID_PARAMETERS", "distanceFilter must be 0 or greater (0 = no filter)");
336
+ return;
337
+ }
338
+
339
+ serviceIntent.putExtra("updateInterval", updateInterval);
340
+ serviceIntent.putExtra("fastestInterval", fastestInterval);
341
+ serviceIntent.putExtra("priority", priority);
342
+ serviceIntent.putExtra("distanceFilter", distanceFilter);
343
+
344
+ // Extract API configuration if provided
345
+ JSObject apiConfig = call.getObject("api");
346
+ if (apiConfig != null) {
347
+ String apiUrl = apiConfig.getString("url");
348
+ if (apiUrl != null && !apiUrl.trim().isEmpty()) {
349
+ serviceIntent.putExtra("apiUrl", apiUrl.trim());
350
+ serviceIntent.putExtra("apiType", apiConfig.getString("type", "POST"));
351
+
352
+ JSObject headers = apiConfig.getJSObject("header");
353
+ if (headers != null) {
354
+ serviceIntent.putExtra("apiHeaders", headers.toString());
355
+ }
356
+
357
+ JSObject additionalParams = apiConfig.getJSObject("additionalParams");
358
+ if (additionalParams != null) {
359
+ serviceIntent.putExtra("apiAdditionalParams", additionalParams.toString());
360
+ }
361
+
362
+ Integer apiInterval = apiConfig.getInteger("apiInterval", 5);
363
+ if (apiInterval != null && apiInterval > 0) {
364
+ serviceIntent.putExtra("apiInterval", apiInterval);
365
+ } else {
366
+ serviceIntent.putExtra("apiInterval", 5); // Default 5 minutes
367
+ }
368
+
369
+ Log.d(TAG, "API configuration added to service intent");
370
+ }
371
+ }
372
+
373
+ // Start foreground service
374
+ ContextCompat.startForegroundService(getContext(), serviceIntent);
375
+
376
+ // Bind to service
377
+ getContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
378
+
379
+ call.resolve();
380
+ }
381
+
382
+ @PluginMethod
383
+ public void stopForegroundLocationService(PluginCall call) {
384
+ Intent serviceIntent = new Intent(getContext(), LocationForegroundService.class);
385
+ getContext().stopService(serviceIntent);
386
+
387
+ if (isServiceBound) {
388
+ getContext().unbindService(serviceConnection);
389
+ isServiceBound = false;
390
+ }
391
+
392
+ call.resolve();
393
+ }
394
+
395
+ @PluginMethod
396
+ public void isServiceRunning(PluginCall call) {
397
+ JSObject result = new JSObject();
398
+ result.put("isRunning", isServiceBound && locationService != null &&
399
+ locationService.isLocationUpdatesActive());
400
+ call.resolve(result);
401
+ }
402
+
403
+ @PluginMethod
404
+ public void getCurrentLocation(PluginCall call) {
405
+ if (getLocationPermissionState() != PermissionState.GRANTED) {
406
+ call.reject("Location permission required");
407
+ return;
408
+ }
409
+
410
+ implementation.getCurrentLocation(getContext(), new ForeGroundLocation.LocationCallback() {
411
+ @Override
412
+ public void onLocationResult(Location location) {
413
+ JSObject result = new JSObject();
414
+ result.put("latitude", location.getLatitude());
415
+ result.put("longitude", location.getLongitude());
416
+ result.put("accuracy", (double) location.getAccuracy());
417
+ result.put("timestamp", implementation.formatTimestamp(location.getTime()));
418
+
419
+ if (location.hasAltitude()) {
420
+ result.put("altitude", location.getAltitude());
421
+ }
422
+ if (location.hasBearing()) {
423
+ result.put("bearing", (double) location.getBearing());
424
+ }
425
+ if (location.hasSpeed()) {
426
+ result.put("speed", (double) location.getSpeed());
427
+ }
428
+
429
+ call.resolve(result);
430
+ }
431
+
432
+ @Override
433
+ public void onLocationError(String error) {
434
+ call.reject("Location error: " + error);
435
+ }
436
+ });
437
+ }
438
+
439
+ @PluginMethod
440
+ public void updateLocationSettings(PluginCall call) {
441
+ if (!isServiceBound || locationService == null) {
442
+ call.reject("SERVICE_NOT_RUNNING", "Location service is not running");
443
+ return;
444
+ }
445
+
446
+ // Get notification configuration directly from call
447
+ JSObject notification = call.getObject("notification");
448
+
449
+ // Enhanced validation for notification configuration in updates
450
+ String title = "Location Tracking"; // Default
451
+ String text = "Tracking location"; // Default
452
+ String icon = null;
453
+
454
+ if (notification != null) {
455
+ // Use provided values if valid, otherwise keep defaults
456
+ String providedTitle = notification.getString("title");
457
+ String providedText = notification.getString("text");
458
+
459
+ if (providedTitle != null && !providedTitle.trim().isEmpty()) {
460
+ title = providedTitle;
461
+ }
462
+
463
+ if (providedText != null && !providedText.trim().isEmpty()) {
464
+ text = providedText;
465
+ }
466
+
467
+ icon = notification.getString("icon");
468
+ }
469
+
470
+ Log.d(TAG, "Updating service configuration - Title: " + title + ", Text: " + text);
471
+
472
+ // Get interval values directly from call
473
+ long interval = call.getLong("interval", 60000L); // Default 60 seconds
474
+ long fastestInterval = call.getLong("fastestInterval", 30000L); // Default 30 seconds
475
+
476
+ // Validate intervals
477
+ if (interval < 1000L) {
478
+ call.reject("INVALID_PARAMETERS", "interval must be at least 1000ms");
479
+ return;
480
+ }
481
+
482
+ if (fastestInterval < 1000L) {
483
+ call.reject("INVALID_PARAMETERS", "fastestInterval must be at least 1000ms");
484
+ return;
485
+ }
486
+
487
+ String priorityStr = call.getString("priority", "HIGH_ACCURACY");
488
+
489
+ if (!isValidPriority(priorityStr)) {
490
+ call.reject("INVALID_PARAMETERS", "priority must be one of: HIGH_ACCURACY, BALANCED_POWER, LOW_POWER, NO_POWER");
491
+ return;
492
+ }
493
+
494
+ int priority = convertPriority(priorityStr);
495
+
496
+ // Get distance filter parameter
497
+ long distanceFilter = call.getLong("distanceFilter", 0L); // Default 0 = no filter
498
+ if (distanceFilter < 0L) {
499
+ call.reject("INVALID_PARAMETERS", "distanceFilter must be 0 or greater (0 = no filter)");
500
+ return;
501
+ }
502
+
503
+ locationService.updateServiceConfiguration(title, text, icon, interval, fastestInterval, priority, distanceFilter);
504
+ call.resolve();
505
+ }
506
+
507
+ @PluginMethod
508
+ public void getApiServiceStatus(PluginCall call) {
509
+ if (!isServiceBound || locationService == null) {
510
+ call.reject("SERVICE_NOT_RUNNING", "Location service is not running");
511
+ return;
512
+ }
513
+
514
+ JSObject result = new JSObject();
515
+ result.put("isEnabled", locationService.isApiServiceEnabled());
516
+ result.put("bufferSize", locationService.getApiBufferSize());
517
+ result.put("isHealthy", locationService.isApiHealthy());
518
+
519
+ call.resolve(result);
520
+ }
521
+
522
+ @PluginMethod
523
+ public void clearApiBuffers(PluginCall call) {
524
+ if (!isServiceBound || locationService == null) {
525
+ call.reject("SERVICE_NOT_RUNNING", "Location service is not running");
526
+ return;
527
+ }
528
+
529
+ locationService.clearApiBuffers();
530
+ call.resolve();
531
+ }
532
+
533
+ @PluginMethod
534
+ public void resetApiCircuitBreaker(PluginCall call) {
535
+ if (!isServiceBound || locationService == null) {
536
+ call.reject("SERVICE_NOT_RUNNING", "Location service is not running");
537
+ return;
538
+ }
539
+
540
+ locationService.resetApiCircuitBreaker();
541
+ call.resolve();
542
+ }
543
+
544
+ // Helper methods
545
+
546
+ // Helper method to debug notification configuration
547
+ private void logNotificationConfig(JSObject notification) {
548
+ if (notification == null) {
549
+ Log.d(TAG, "Notification config is null");
550
+ return;
551
+ }
552
+
553
+ Log.d(TAG, "Notification config received:");
554
+ Log.d(TAG, " - Length: " + notification.length());
555
+ Log.d(TAG, " - Keys: " + notification.keys());
556
+
557
+ if (notification.has("title")) {
558
+ Log.d(TAG, " - Title: '" + notification.getString("title") + "'");
559
+ } else {
560
+ Log.d(TAG, " - Title: NOT PROVIDED");
561
+ }
562
+
563
+ if (notification.has("text")) {
564
+ Log.d(TAG, " - Text: '" + notification.getString("text") + "'");
565
+ } else {
566
+ Log.d(TAG, " - Text: NOT PROVIDED");
567
+ }
568
+
569
+ if (notification.has("icon")) {
570
+ Log.d(TAG, " - Icon: '" + notification.getString("icon") + "'");
571
+ } else {
572
+ Log.d(TAG, " - Icon: NOT PROVIDED");
573
+ }
574
+ }
575
+
576
+ // Helper method to safely extract and validate notification configuration
577
+ private boolean validateAndExtractNotification(PluginCall call) {
578
+ JSObject notification = call.getObject("notification");
579
+
580
+ // Debug logging for troubleshooting
581
+ Log.d(TAG, "validateAndExtractNotification called");
582
+ Log.d(TAG, "Call data received: " + call.getData().toString());
583
+ logNotificationConfig(notification);
584
+
585
+ // Enhanced validation for notification configuration
586
+ if (notification == null) {
587
+ call.reject("INVALID_NOTIFICATION", "Notification configuration is required. Please provide notification object with title and text properties.");
588
+ return false;
589
+ }
590
+
591
+ // Validate required notification properties
592
+ String title = notification.getString("title");
593
+ String text = notification.getString("text");
594
+
595
+ if (title == null || title.trim().isEmpty()) {
596
+ call.reject("INVALID_NOTIFICATION", "Notification title is required in notification configuration.");
597
+ return false;
598
+ }
599
+
600
+ if (text == null || text.trim().isEmpty()) {
601
+ call.reject("INVALID_NOTIFICATION", "Notification text is required in notification configuration.");
602
+ return false;
603
+ }
604
+
605
+ Log.d(TAG, "Notification configuration validated successfully - Title: " + title + ", Text: " + text);
606
+ return true;
607
+ }
608
+
609
+ private PermissionState getLocationPermissionState() {
610
+ return getPermissionState("location");
611
+ }
612
+
613
+ private PermissionState getBackgroundLocationPermissionState() {
614
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
615
+ return getPermissionState("backgroundLocation");
616
+ }
617
+ return PermissionState.GRANTED; // Not required on older versions
618
+ }
619
+
620
+ private PermissionState getNotificationPermissionState() {
621
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
622
+ return getPermissionState("notifications");
623
+ }
624
+ return PermissionState.GRANTED; // Not required on older versions
625
+ }
626
+
627
+ private int convertPriority(String priorityStr) {
628
+ switch (priorityStr) {
629
+ case "HIGH_ACCURACY":
630
+ return com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY;
631
+ case "BALANCED_POWER":
632
+ return com.google.android.gms.location.Priority.PRIORITY_BALANCED_POWER_ACCURACY;
633
+ case "LOW_POWER":
634
+ return com.google.android.gms.location.Priority.PRIORITY_LOW_POWER;
635
+ case "NO_POWER":
636
+ return com.google.android.gms.location.Priority.PRIORITY_PASSIVE;
637
+ default:
638
+ return com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY;
639
+ }
640
+ }
641
+
642
+ private boolean isValidPriority(String priority) {
643
+ return priority != null && (
644
+ priority.equals("HIGH_ACCURACY") ||
645
+ priority.equals("BALANCED_POWER") ||
646
+ priority.equals("LOW_POWER") ||
647
+ priority.equals("NO_POWER")
648
+ );
649
+ }
650
+ }