react-native-geofence-manager 0.1.0-beta.11 → 0.1.0-beta.13
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/README.md +173 -37
- package/android/src/main/AndroidManifest.xml +17 -16
- package/android/src/main/java/com/geofencemanager/GeofenceBroadcastReceiver.kt +133 -0
- package/android/src/main/java/com/geofencemanager/GeofenceHeadlessTaskService.kt +98 -141
- package/android/src/main/java/com/geofencemanager/GeofenceManagerModule.kt +187 -154
- package/ios/GeofenceManager.mm +21 -21
- package/lib/module/NativeGeofenceManager.js +1 -7
- package/lib/module/NativeGeofenceManager.js.map +1 -1
- package/lib/module/geofenceDebugLog.js +22 -0
- package/lib/module/geofenceDebugLog.js.map +1 -0
- package/lib/module/index.js +79 -35
- package/lib/module/index.js.map +1 -1
- package/lib/module/models/GeofenceEvent.js +4 -0
- package/lib/module/models/GeofenceEvent.js.map +1 -0
- package/lib/module/models/GeofenceEventType.js +2 -0
- package/lib/module/models/GeofenceEventType.js.map +1 -0
- package/lib/module/models/GeofenceModel.js +2 -0
- package/lib/module/models/GeofenceModel.js.map +1 -0
- package/lib/module/models/index.js +6 -0
- package/lib/module/models/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeGeofenceManager.d.ts +3 -16
- package/lib/typescript/src/NativeGeofenceManager.d.ts.map +1 -1
- package/lib/typescript/src/geofenceDebugLog.d.ts +3 -0
- package/lib/typescript/src/geofenceDebugLog.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +27 -26
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/models/GeofenceEvent.d.ts +11 -0
- package/lib/typescript/src/models/GeofenceEvent.d.ts.map +1 -0
- package/lib/typescript/src/models/GeofenceEventType.d.ts +2 -0
- package/lib/typescript/src/models/GeofenceEventType.d.ts.map +1 -0
- package/lib/typescript/src/models/GeofenceModel.d.ts +8 -0
- package/lib/typescript/src/models/GeofenceModel.d.ts.map +1 -0
- package/lib/typescript/src/models/index.d.ts +4 -0
- package/lib/typescript/src/models/index.d.ts.map +1 -0
- package/package.json +16 -15
- package/src/NativeGeofenceManager.ts +4 -29
- package/src/geofenceDebugLog.ts +28 -0
- package/src/index.tsx +99 -71
- package/src/models/GeofenceEvent.ts +11 -0
- package/src/models/GeofenceEventType.ts +1 -0
- package/src/models/GeofenceModel.ts +7 -0
- package/src/models/index.ts +3 -0
- package/android/src/main/java/com/geofencemanager/GeofenceReceiver.kt +0 -192
- package/android/src/main/java/com/geofencemanager/RegionManager.kt +0 -387
- package/lib/commonjs/NativeGeofenceManager.js +0 -15
- package/lib/commonjs/NativeGeofenceManager.js.map +0 -1
- package/lib/commonjs/index.js +0 -47
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/package.json +0 -1
- package/react-native.config.js +0 -11
package/README.md
CHANGED
|
@@ -1,37 +1,173 @@
|
|
|
1
|
-
# react-native-geofence-manager
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
# react-native-geofence-manager
|
|
2
|
+
|
|
3
|
+
> **⚠️ Status: POC (proof of concept)**
|
|
4
|
+
> Esta biblioteca é um **protótipo experimental**. A API, o comportamento e a estabilidade podem mudar sem aviso prévio. **Não use em produção** sem avaliar riscos e fazer testes próprios.
|
|
5
|
+
|
|
6
|
+
> **📱 Plataforma: apenas Android**
|
|
7
|
+
> A implementação nativa existe **somente para Android** (Google Play Services / Geofencing API). No **iOS** não há suporte funcional — não espere geofences nesta lib em dispositivos Apple.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## O que faz
|
|
12
|
+
|
|
13
|
+
Permite registrar **geofences circulares** e receber eventos de **entrada**, **saída** e **permanência (dwell)** no JavaScript, inclusive quando o app está em **segundo plano** ou **fechado**, desde que a headless task esteja registrada no `index` do app.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Instalação
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install react-native-geofence-manager
|
|
21
|
+
# ou
|
|
22
|
+
yarn add react-native-geofence-manager
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Siga o fluxo normal de autolinking do React Native. **Use apenas em projetos Android** para obter o comportamento descrito abaixo.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Uso
|
|
30
|
+
|
|
31
|
+
### 1. Registrar a headless task (obrigatório para eventos com app em background/fechado)
|
|
32
|
+
|
|
33
|
+
No arquivo de entrada do app (`index.js` / `index.tsx`), **antes** de `AppRegistry.registerComponent`:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { AppRegistry } from 'react-native';
|
|
37
|
+
import {
|
|
38
|
+
registerGeofenceHeadlessTask,
|
|
39
|
+
type GeofenceEvent,
|
|
40
|
+
} from 'react-native-geofence-manager';
|
|
41
|
+
import App from './App';
|
|
42
|
+
import { name as appName } from './app.json';
|
|
43
|
+
|
|
44
|
+
registerGeofenceHeadlessTask((event: GeofenceEvent) => {
|
|
45
|
+
// Ex.: notificação local, log, envio para API
|
|
46
|
+
console.log('Geofence (headless):', event.geofenceId, event.event);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
AppRegistry.registerComponent(appName, () => App);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Adicionar geofences
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { addGeofences } from 'react-native-geofence-manager';
|
|
56
|
+
|
|
57
|
+
const ok = await addGeofences([
|
|
58
|
+
{
|
|
59
|
+
id: 'loja-1',
|
|
60
|
+
name: 'Loja centro',
|
|
61
|
+
latitude: -23.5505,
|
|
62
|
+
longitude: -46.6333,
|
|
63
|
+
radius: 150, // metros (opcional; padrão nativo ~100 m se omitido)
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
// ok === true se o registro foi aceito pelo sistema
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Listener com o app em primeiro plano
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { addGeofenceEventListener } from 'react-native-geofence-manager';
|
|
73
|
+
|
|
74
|
+
const unsubscribe = addGeofenceEventListener((event) => {
|
|
75
|
+
console.log(event.geofenceId, event.event); // 'enter' | 'exit' | 'dwell'
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ao desmontar:
|
|
79
|
+
unsubscribe();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 4. Remover todas as geofences
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { removeAllGeofences } from 'react-native-geofence-manager';
|
|
86
|
+
|
|
87
|
+
await removeAllGeofences();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Modelo de geofence (`GeofenceModel`)
|
|
93
|
+
|
|
94
|
+
| Campo | Tipo | Obrigatório | Descrição |
|
|
95
|
+
|---------------|----------|-------------|------------------------------------|
|
|
96
|
+
| `id` | `string` | Sim | ID único da geofence |
|
|
97
|
+
| `latitude` | `number` | Sim | Latitude do centro |
|
|
98
|
+
| `longitude` | `number` | Sim | Longitude do centro |
|
|
99
|
+
| `name` | `string` | Não | Nome exibido nos eventos |
|
|
100
|
+
| `radius` | `number` | Não | Raio em metros |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Evento (`GeofenceEvent`)
|
|
105
|
+
|
|
106
|
+
- `geofenceId` — ID da geofence
|
|
107
|
+
- `event` — `'enter' | 'exit' | 'dwell'`
|
|
108
|
+
- `timestamp`, `name`, `latitude`, `longitude`, `radius` — quando disponíveis
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Permissões (Android)
|
|
113
|
+
|
|
114
|
+
Para geofences confiáveis é necessário **localização** (fina ou aproximada) e, em geral, **localização em segundo plano** (Android 10+) se quiser eventos com o app fechado. Notificações (API 33+) dependem do seu fluxo. Consulte a documentação oficial do Android e o app de exemplo.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Depuração (logs)
|
|
119
|
+
|
|
120
|
+
A lib emite logs em cada etapa para localizar falhas (SDK nativo + JS).
|
|
121
|
+
|
|
122
|
+
**Android (Logcat)** — filtre pela tag:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
adb logcat -s GeofenceManager
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Você verá, entre outros: inicialização do módulo, `addGeofences` (sucesso/falha do Play Services), `BroadcastReceiver` (transição, foreground vs headless), `HeadlessTaskService` e erros de reflexão.
|
|
129
|
+
|
|
130
|
+
**JavaScript** — mensagens com prefixo `[GeofenceManager]` no console (Metro) e também no Logcat como `ReactNativeJS` ao rodar no dispositivo.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## App de exemplo
|
|
135
|
+
|
|
136
|
+
Na raiz do repositório:
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
yarn example start
|
|
140
|
+
yarn example android
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
O diretório `example/` demonstra permissões, registro de geofences, listener e notificações locais (Notifee).
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## API exportada (resumo)
|
|
148
|
+
|
|
149
|
+
| Export | Descrição |
|
|
150
|
+
|--------------------------------|------------------------------------------------|
|
|
151
|
+
| `addGeofences` | Registra uma lista de geofences |
|
|
152
|
+
| `removeAllGeofences` | Remove todas as geofences registradas |
|
|
153
|
+
| `addGeofenceEventListener` | Inscreve callback para eventos (foreground) |
|
|
154
|
+
| `onGeofenceEvent` | Alias de `addGeofenceEventListener` |
|
|
155
|
+
| `registerGeofenceHeadlessTask` | Registra handler para background/app fechado |
|
|
156
|
+
| `GEOFENCE_HEADLESS_TASK_NAME` | Nome interno da headless task |
|
|
157
|
+
| Tipos `GeofenceModel`, `GeofenceEvent`, etc. | TypeScript |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Contributing
|
|
162
|
+
|
|
163
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
164
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
165
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
-
|
|
3
|
-
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
4
|
-
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
5
|
-
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
android:
|
|
11
|
-
|
|
12
|
-
<service
|
|
13
|
-
android:name=".GeofenceHeadlessTaskService"
|
|
14
|
-
android:exported="false"
|
|
15
|
-
|
|
16
|
-
</
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
3
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
4
|
+
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
5
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
6
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
7
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
8
|
+
<application>
|
|
9
|
+
<receiver
|
|
10
|
+
android:name=".GeofenceBroadcastReceiver"
|
|
11
|
+
android:exported="false" />
|
|
12
|
+
<service
|
|
13
|
+
android:name=".GeofenceHeadlessTaskService"
|
|
14
|
+
android:exported="false"
|
|
15
|
+
android:foregroundServiceType="location" />
|
|
16
|
+
</application>
|
|
17
|
+
</manifest>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
package com.geofencemanager
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
10
|
+
import com.facebook.react.HeadlessJsTaskService
|
|
11
|
+
import com.facebook.react.bridge.Arguments
|
|
12
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
|
+
import com.facebook.react.bridge.WritableMap
|
|
14
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
15
|
+
import com.google.android.gms.location.Geofence
|
|
16
|
+
import com.google.android.gms.location.GeofencingEvent
|
|
17
|
+
|
|
18
|
+
class GeofenceBroadcastReceiver : BroadcastReceiver() {
|
|
19
|
+
|
|
20
|
+
companion object {
|
|
21
|
+
private const val TAG = "GeofenceManager"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun onReceive(context: Context?, intent: Intent?) {
|
|
25
|
+
Log.i(TAG, "BroadcastReceiver.onReceive: invoked action=${intent?.action}")
|
|
26
|
+
if (context == null || intent == null) {
|
|
27
|
+
Log.w(TAG, "BroadcastReceiver.onReceive: abort — null context or intent")
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
if (intent.action != GeofenceManagerModule.GEOFENCE_ACTION) {
|
|
31
|
+
Log.w(TAG, "BroadcastReceiver.onReceive: wrong action, expected ${GeofenceManagerModule.GEOFENCE_ACTION}")
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
val geofencingEvent = GeofencingEvent.fromIntent(intent)
|
|
36
|
+
if (geofencingEvent == null) {
|
|
37
|
+
Log.e(TAG, "BroadcastReceiver.onReceive: GeofencingEvent.fromIntent returned null")
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
if (geofencingEvent.hasError()) {
|
|
41
|
+
Log.e(TAG, "BroadcastReceiver.onReceive: Geofencing error code=${geofencingEvent.errorCode}")
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (GeofenceManagerModule.isWithinGracePeriod(context)) {
|
|
46
|
+
Log.i(TAG, "BroadcastReceiver.onReceive: skipped — within grace period after addGeofences")
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
val transition = geofencingEvent.geofenceTransition
|
|
51
|
+
val eventName = when (transition) {
|
|
52
|
+
Geofence.GEOFENCE_TRANSITION_ENTER -> "enter"
|
|
53
|
+
Geofence.GEOFENCE_TRANSITION_EXIT -> "exit"
|
|
54
|
+
Geofence.GEOFENCE_TRANSITION_DWELL -> "dwell"
|
|
55
|
+
else -> {
|
|
56
|
+
Log.w(TAG, "BroadcastReceiver.onReceive: unknown transition=$transition")
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
Log.i(TAG, "BroadcastReceiver.onReceive: transition=$eventName")
|
|
61
|
+
|
|
62
|
+
val triggeringGeofences = geofencingEvent.triggeringGeofences
|
|
63
|
+
if (triggeringGeofences == null || triggeringGeofences.isEmpty()) {
|
|
64
|
+
Log.w(TAG, "BroadcastReceiver.onReceive: no triggering geofences")
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
val reactContext = GeofenceManagerModule.reactContextRef
|
|
69
|
+
val timestamp = System.currentTimeMillis().toDouble()
|
|
70
|
+
val isForeground = reactContext != null && reactContext.hasActiveReactInstance()
|
|
71
|
+
Log.i(
|
|
72
|
+
TAG,
|
|
73
|
+
"BroadcastReceiver.onReceive: ids=${triggeringGeofences.map { it.requestId }} isForeground=$isForeground hasReactContextRef=${reactContext != null}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if (isForeground) {
|
|
77
|
+
val ctx = reactContext!!
|
|
78
|
+
val geofenceIds = triggeringGeofences.map { it.requestId }
|
|
79
|
+
val eventNameForRunnable = eventName
|
|
80
|
+
Handler(Looper.getMainLooper()).postDelayed({
|
|
81
|
+
if (!ctx.hasActiveReactInstance()) {
|
|
82
|
+
Log.w(TAG, "BroadcastReceiver: foreground path — React instance gone after delay, skipping emit")
|
|
83
|
+
return@postDelayed
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
val emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
87
|
+
for (geofenceId in geofenceIds) {
|
|
88
|
+
val details = GeofenceManagerModule.getGeofenceDetails(context, geofenceId)
|
|
89
|
+
val params = Arguments.createMap().apply {
|
|
90
|
+
putString("geofenceId", geofenceId)
|
|
91
|
+
putString("event", eventNameForRunnable)
|
|
92
|
+
putDouble("timestamp", timestamp)
|
|
93
|
+
putString("name", details?.name ?: "")
|
|
94
|
+
details?.let {
|
|
95
|
+
putDouble("latitude", it.latitude)
|
|
96
|
+
putDouble("longitude", it.longitude)
|
|
97
|
+
putDouble("radius", it.radius.toDouble())
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
Log.i(TAG, "BroadcastReceiver: emitting onGeofenceTransition id=$geofenceId event=$eventNameForRunnable")
|
|
101
|
+
emitter.emit("onGeofenceTransition", params)
|
|
102
|
+
}
|
|
103
|
+
} catch (e: Exception) {
|
|
104
|
+
Log.e(TAG, "BroadcastReceiver: emit to JS failed", e)
|
|
105
|
+
}
|
|
106
|
+
}, 50)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Log.i(TAG, "BroadcastReceiver: background/headless path — starting GeofenceHeadlessTaskService")
|
|
111
|
+
val appContext = context.applicationContext
|
|
112
|
+
HeadlessJsTaskService.acquireWakeLockNow(appContext)
|
|
113
|
+
for (geofence in triggeringGeofences) {
|
|
114
|
+
val serviceIntent = Intent(appContext, GeofenceHeadlessTaskService::class.java).apply {
|
|
115
|
+
putExtra(GeofenceHeadlessTaskService.EXTRA_GEOFENCE_ID, geofence.requestId)
|
|
116
|
+
putExtra(GeofenceHeadlessTaskService.EXTRA_EVENT, eventName)
|
|
117
|
+
putExtra(GeofenceHeadlessTaskService.EXTRA_TIMESTAMP, timestamp)
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
121
|
+
appContext.startForegroundService(serviceIntent)
|
|
122
|
+
Log.i(TAG, "BroadcastReceiver: startForegroundService for id=${geofence.requestId}")
|
|
123
|
+
} else {
|
|
124
|
+
appContext.startService(serviceIntent)
|
|
125
|
+
Log.i(TAG, "BroadcastReceiver: startService for id=${geofence.requestId}")
|
|
126
|
+
}
|
|
127
|
+
} catch (e: Exception) {
|
|
128
|
+
Log.e(TAG, "BroadcastReceiver: failed to start headless service", e)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
package com.geofencemanager
|
|
2
2
|
|
|
3
|
+
import android.app.Notification
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
3
6
|
import android.content.Intent
|
|
4
|
-
import android.os.
|
|
7
|
+
import android.os.Build
|
|
5
8
|
import android.util.Log
|
|
6
9
|
import com.facebook.react.HeadlessJsTaskService
|
|
7
10
|
import com.facebook.react.bridge.Arguments
|
|
@@ -9,155 +12,109 @@ import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
|
|
9
12
|
|
|
10
13
|
class GeofenceHeadlessTaskService : HeadlessJsTaskService() {
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Log.d(TAG, "════════════════════════════════════════")
|
|
19
|
-
Log.d(TAG, "🚀 SERVICE CREATED - onCreate()")
|
|
20
|
-
Log.d(TAG, "════════════════════════════════════════")
|
|
21
|
-
}
|
|
22
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Quando o app está em background/frozen, reactContext existe mas hasActiveReactInstance()
|
|
17
|
+
* retorna false e a task não roda. Nesse caso chamamos createReactContextAndScheduleTask
|
|
18
|
+
* (via reflexão) em onStartCommand para que dwell/exit sejam executados.
|
|
19
|
+
* startForeground() é chamado para que o sistema inicie o serviço mesmo em background (dwell/exit).
|
|
20
|
+
*/
|
|
23
21
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
24
|
-
Log.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return try {
|
|
32
|
-
val result = super.onStartCommand(intent, flags, startId)
|
|
33
|
-
Log.d(TAG, " └─ Super.onStartCommand result: $result")
|
|
34
|
-
result
|
|
35
|
-
} catch (e: Exception) {
|
|
36
|
-
Log.e(TAG, "💥 ERRO em onStartCommand:")
|
|
37
|
-
Log.e(TAG, " └─ ${e.javaClass.simpleName}: ${e.message}")
|
|
38
|
-
Log.e(TAG, " └─ StackTrace: ${e.stackTraceToString()}")
|
|
39
|
-
START_NOT_STICKY
|
|
22
|
+
Log.i(TAG, "HeadlessTaskService.onStartCommand extras=${intent?.extras}")
|
|
23
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
24
|
+
ensureNotificationChannel()
|
|
25
|
+
val notification = buildNotification(intent)
|
|
26
|
+
startForeground(NOTIFICATION_ID, notification)
|
|
27
|
+
Log.d(TAG, "HeadlessTaskService: startForeground done")
|
|
40
28
|
}
|
|
29
|
+
val taskConfig = getTaskConfig(intent)
|
|
30
|
+
if (taskConfig == null) {
|
|
31
|
+
Log.e(TAG, "HeadlessTaskService: getTaskConfig returned null — check EXTRA_GEOFENCE_ID / EXTRA_EVENT")
|
|
32
|
+
return START_NOT_STICKY
|
|
33
|
+
}
|
|
34
|
+
val ctx = reactContext
|
|
35
|
+
val hasInstance = ctx?.hasActiveReactInstance() == true
|
|
36
|
+
Log.i(TAG, "HeadlessTaskService: hasActiveReactInstance=$hasInstance")
|
|
37
|
+
if (ctx != null && !ctx.hasActiveReactInstance()) {
|
|
38
|
+
HeadlessJsTaskService.acquireWakeLockNow(this)
|
|
39
|
+
try {
|
|
40
|
+
val method = HeadlessJsTaskService::class.java.getDeclaredMethod(
|
|
41
|
+
"createReactContextAndScheduleTask",
|
|
42
|
+
HeadlessJsTaskConfig::class.java
|
|
43
|
+
)
|
|
44
|
+
method.isAccessible = true
|
|
45
|
+
method.invoke(this, taskConfig)
|
|
46
|
+
Log.i(TAG, "HeadlessTaskService: createReactContextAndScheduleTask invoked OK")
|
|
47
|
+
} catch (e: Exception) {
|
|
48
|
+
Log.e(TAG, "HeadlessTaskService: reflection createReactContextAndScheduleTask failed", e)
|
|
49
|
+
return super.onStartCommand(intent, flags, startId)
|
|
50
|
+
}
|
|
51
|
+
return START_REDELIVER_INTENT
|
|
52
|
+
}
|
|
53
|
+
Log.i(TAG, "HeadlessTaskService: delegating to super.onStartCommand")
|
|
54
|
+
return super.onStartCommand(intent, flags, startId)
|
|
41
55
|
}
|
|
42
56
|
|
|
43
|
-
override fun
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
|
|
58
|
+
if (intent == null) {
|
|
59
|
+
Log.w(TAG, "getTaskConfig: null intent")
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
val geofenceId = intent.getStringExtra(EXTRA_GEOFENCE_ID)
|
|
63
|
+
val event = intent.getStringExtra(EXTRA_EVENT)
|
|
64
|
+
if (geofenceId == null || event == null) {
|
|
65
|
+
Log.e(TAG, "getTaskConfig: missing geofenceId or event (id=$geofenceId event=$event)")
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
val timestamp = intent.getDoubleExtra(EXTRA_TIMESTAMP, 0.0)
|
|
69
|
+
|
|
70
|
+
val details = GeofenceManagerModule.getGeofenceDetails(this, geofenceId)
|
|
71
|
+
Log.i(TAG, "getTaskConfig: task=${GeofenceManagerModule.GEOFENCE_HEADLESS_TASK_NAME} id=$geofenceId event=$event")
|
|
72
|
+
val data = Arguments.createMap().apply {
|
|
73
|
+
putString("geofenceId", geofenceId)
|
|
74
|
+
putString("event", event)
|
|
75
|
+
putDouble("timestamp", timestamp)
|
|
76
|
+
putString("name", details?.name ?: "")
|
|
77
|
+
putDouble("latitude", details?.latitude ?: 0.0)
|
|
78
|
+
putDouble("longitude", details?.longitude ?: 0.0)
|
|
79
|
+
putDouble("radius", details?.radius?.toDouble() ?: 0.0)
|
|
80
|
+
}
|
|
50
81
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
82
|
+
return HeadlessJsTaskConfig(
|
|
83
|
+
GeofenceManagerModule.GEOFENCE_HEADLESS_TASK_NAME,
|
|
84
|
+
data,
|
|
85
|
+
5000L,
|
|
86
|
+
false
|
|
87
|
+
)
|
|
57
88
|
}
|
|
58
89
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
90
|
+
private fun ensureNotificationChannel() {
|
|
91
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
|
92
|
+
val channel = NotificationChannel(
|
|
93
|
+
CHANNEL_ID,
|
|
94
|
+
"Geofence",
|
|
95
|
+
NotificationManager.IMPORTANCE_LOW
|
|
96
|
+
).apply { setShowBadge(false) }
|
|
97
|
+
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
|
|
98
|
+
.createNotificationChannel(channel)
|
|
64
99
|
}
|
|
65
100
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Log.e(TAG, "❌ Intent extras é NULL - retornando null")
|
|
77
|
-
return null
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
Log.d(TAG, "📋 Extras disponíveis:")
|
|
81
|
-
for (key in extras.keySet()) {
|
|
82
|
-
Log.d(TAG, " └─ $key = ${extras.get(key)}")
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
val eventName = extras.getString("eventName")
|
|
86
|
-
if (eventName == null) {
|
|
87
|
-
Log.e(TAG, "❌ eventName é NULL - retornando null")
|
|
88
|
-
return null
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
val regionId = extras.getString("regionId")
|
|
92
|
-
if (regionId == null) {
|
|
93
|
-
Log.e(TAG, "❌ regionId é NULL - retornando null")
|
|
94
|
-
return null
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
Log.d(TAG, "✅ Dados extraídos:")
|
|
98
|
-
Log.d(TAG, " └─ eventName: $eventName")
|
|
99
|
-
Log.d(TAG, " └─ regionId: $regionId")
|
|
100
|
-
|
|
101
|
-
val regionName = extras.getString("regionName") ?: ""
|
|
102
|
-
val regionData = extras.getString("regionData") ?: ""
|
|
103
|
-
val regionLatitude = extras.getDouble("regionLatitude", 0.0)
|
|
104
|
-
val regionLongitude = extras.getDouble("regionLongitude", 0.0)
|
|
105
|
-
val regionRadius = extras.getDouble("regionRadius", 100.0)
|
|
106
|
-
|
|
107
|
-
Log.d(TAG, "📍 Region data:")
|
|
108
|
-
Log.d(TAG, " └─ name: $regionName")
|
|
109
|
-
Log.d(TAG, " └─ data: $regionData")
|
|
110
|
-
Log.d(TAG, " └─ latitude: $regionLatitude")
|
|
111
|
-
Log.d(TAG, " └─ longitude: $regionLongitude")
|
|
112
|
-
Log.d(TAG, " └─ radius: $regionRadius")
|
|
113
|
-
|
|
114
|
-
val regionMap = Arguments.createMap().apply {
|
|
115
|
-
putString("id", regionId)
|
|
116
|
-
putString("name", regionName)
|
|
117
|
-
putString("data", regionData)
|
|
118
|
-
putDouble("latitude", regionLatitude)
|
|
119
|
-
putDouble("longitude", regionLongitude)
|
|
120
|
-
putDouble("radius", regionRadius)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
val timestamp = System.currentTimeMillis().toDouble()
|
|
124
|
-
val data = Arguments.createMap().apply {
|
|
125
|
-
putString("event", eventName)
|
|
126
|
-
putMap("region", regionMap)
|
|
127
|
-
putDouble("timestamp", timestamp)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
Log.d(TAG, "📦 HeadlessTask data construído:")
|
|
131
|
-
Log.d(TAG, " └─ event: $eventName")
|
|
132
|
-
Log.d(TAG, " └─ timestamp: $timestamp")
|
|
133
|
-
|
|
134
|
-
val timeout = RegionManager.getHeadlessTimeout(applicationContext)
|
|
135
|
-
Log.d(TAG, "⏱️ Timeout configurado: ${timeout}ms")
|
|
136
|
-
|
|
137
|
-
Log.d(TAG, "🔧 Criando HeadlessJsTaskConfig:")
|
|
138
|
-
Log.d(TAG, " └─ taskKey: ${GeofenceReceiver.HEADLESS_TASK_NAME}")
|
|
139
|
-
Log.d(TAG, " └─ timeout: ${timeout}ms")
|
|
140
|
-
Log.d(TAG, " └─ allowedInForeground: true")
|
|
141
|
-
|
|
142
|
-
val config = HeadlessJsTaskConfig(
|
|
143
|
-
GeofenceReceiver.HEADLESS_TASK_NAME,
|
|
144
|
-
data,
|
|
145
|
-
timeout.toLong(),
|
|
146
|
-
true
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
Log.d(TAG, "════════════════════════════════════════")
|
|
150
|
-
Log.d(TAG, "✅ HeadlessJsTaskConfig CRIADO COM SUCESSO!")
|
|
151
|
-
Log.d(TAG, "════════════════════════════════════════")
|
|
101
|
+
private fun buildNotification(intent: Intent?): Notification {
|
|
102
|
+
val event = intent?.getStringExtra(EXTRA_EVENT) ?: "geofence"
|
|
103
|
+
val smallIcon = applicationInfo.icon.takeIf { it != 0 } ?: android.R.drawable.ic_dialog_info
|
|
104
|
+
return Notification.Builder(this, CHANNEL_ID)
|
|
105
|
+
.setContentTitle("Geofence")
|
|
106
|
+
.setContentText("Evento: $event")
|
|
107
|
+
.setSmallIcon(smallIcon)
|
|
108
|
+
.setPriority(Notification.PRIORITY_LOW)
|
|
109
|
+
.build()
|
|
110
|
+
}
|
|
152
111
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return null
|
|
161
|
-
}
|
|
112
|
+
companion object {
|
|
113
|
+
private const val TAG = "GeofenceManager"
|
|
114
|
+
private const val CHANNEL_ID = "geofence_manager_channel"
|
|
115
|
+
private const val NOTIFICATION_ID = 9001
|
|
116
|
+
const val EXTRA_GEOFENCE_ID = "geofenceId"
|
|
117
|
+
const val EXTRA_EVENT = "event"
|
|
118
|
+
const val EXTRA_TIMESTAMP = "timestamp"
|
|
162
119
|
}
|
|
163
120
|
}
|