Skip to content

Commit d6dcf42

Browse files
Merge pull request #91155 from software-mansion-labs/@GCyganek/gps/fix-android-gps-crashes-in-bg
Fix GPS crashes in the background on Android
2 parents 2052f46 + b5a0c80 commit d6dcf42

12 files changed

Lines changed: 165 additions & 152 deletions

File tree

android/app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
1010
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
1111
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
12+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
1213
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
1314
<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS"/>
1415
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
@@ -31,7 +32,8 @@
3132
android:exported="false" />
3233

3334
<service
34-
android:name=".customairshipextender.GpsTripService"
35+
android:name=".GpsTripService"
36+
android:foregroundServiceType="location"
3537
android:stopWithTask="false"
3638
android:exported="false" />
3739

android/app/src/main/java/com/expensify/chat/ExpensifyAppPackage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public List<NativeModule> createNativeModules(
2323

2424
modules.add(new ShareActionHandlerModule(reactContext));
2525
modules.add(new AppStateTrackerModule(reactContext));
26+
modules.add(new GpsTripServiceModule(reactContext));
2627

2728
return modules;
2829
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.expensify.chat
2+
3+
import android.app.NotificationChannel
4+
import android.app.NotificationManager
5+
import android.app.PendingIntent
6+
import android.app.Service
7+
import android.content.Intent
8+
import android.content.pm.ServiceInfo
9+
import android.net.Uri
10+
import android.os.Build
11+
import android.os.IBinder
12+
import android.util.Log
13+
import androidx.core.app.NotificationCompat
14+
import com.expensify.chat.R
15+
16+
class GpsTripService : Service() {
17+
18+
override fun onBind(intent: Intent?): IBinder? = null
19+
20+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
21+
val title = intent?.getStringExtra(EXTRA_TITLE) ?: "GPS tracking in progress"
22+
val body = intent?.getStringExtra(EXTRA_BODY) ?: "Go to the app to finish"
23+
val deepLink = intent?.getStringExtra(EXTRA_DEEP_LINK)
24+
25+
ensureNotificationChannel()
26+
27+
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
28+
.setSmallIcon(R.drawable.ic_notification)
29+
.setContentTitle(title)
30+
.setContentText(body)
31+
.setOngoing(true)
32+
.setPriority(NotificationCompat.PRIORITY_HIGH)
33+
.setCategory(NotificationCompat.CATEGORY_SERVICE)
34+
.setAutoCancel(false)
35+
36+
if (!deepLink.isNullOrEmpty()) {
37+
// deepLink example: 'create/create/start/1/102024159558962/distance-new/distance-gps'
38+
// we need to add the prefix to make sure it opens correctly
39+
val tapIntent = Intent(Intent.ACTION_VIEW, Uri.parse("new-expensify://$deepLink")).apply {
40+
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
41+
}
42+
val pendingIntent = PendingIntent.getActivity(
43+
this,
44+
0,
45+
tapIntent,
46+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
47+
)
48+
builder.setContentIntent(pendingIntent)
49+
}
50+
51+
try {
52+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
53+
startForeground(NOTIFICATION_ID, builder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
54+
} else {
55+
startForeground(NOTIFICATION_ID, builder.build())
56+
}
57+
} catch (e: Exception) {
58+
Log.e(TAG, "Failed to start foreground", e)
59+
stopSelf()
60+
return START_NOT_STICKY
61+
}
62+
63+
return START_STICKY
64+
}
65+
66+
override fun onTaskRemoved(rootIntent: Intent?) {
67+
try {
68+
stopForeground(STOP_FOREGROUND_REMOVE)
69+
} catch (_: Exception) {}
70+
stopSelf()
71+
super.onTaskRemoved(rootIntent)
72+
}
73+
74+
private fun ensureNotificationChannel() {
75+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
76+
77+
val manager = getSystemService(NotificationManager::class.java)
78+
if (manager.getNotificationChannel(CHANNEL_ID) != null) return
79+
80+
val channel = NotificationChannel(
81+
CHANNEL_ID,
82+
CHANNEL_NAME,
83+
NotificationManager.IMPORTANCE_HIGH
84+
).apply {
85+
description = CHANNEL_DESCRIPTION
86+
}
87+
manager.createNotificationChannel(channel)
88+
}
89+
90+
companion object {
91+
private const val TAG = "GpsTripService"
92+
const val CHANNEL_ID = "gps_foreground_service_channel"
93+
const val CHANNEL_NAME = "GPS Trip Tracking"
94+
const val CHANNEL_DESCRIPTION = "Shows when a GPS distance trip is being tracked"
95+
const val NOTIFICATION_ID = 90001
96+
const val EXTRA_TITLE = "gps_title"
97+
const val EXTRA_BODY = "gps_body"
98+
const val EXTRA_DEEP_LINK = "gps_deep_link"
99+
}
100+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.expensify.chat
2+
3+
import android.content.Intent
4+
import android.os.Build
5+
import android.util.Log
6+
import com.facebook.react.bridge.ReactApplicationContext
7+
import com.facebook.react.bridge.ReactContextBaseJavaModule
8+
import com.facebook.react.bridge.ReactMethod
9+
10+
class GpsTripServiceModule(reactContext: ReactApplicationContext) :
11+
ReactContextBaseJavaModule(reactContext) {
12+
13+
override fun getName(): String = "GpsTripServiceModule"
14+
15+
@ReactMethod
16+
fun startService(title: String, body: String, deepLink: String) {
17+
val context = reactApplicationContext
18+
val intent = Intent(context, GpsTripService::class.java).apply {
19+
putExtra(GpsTripService.EXTRA_TITLE, title)
20+
putExtra(GpsTripService.EXTRA_BODY, body)
21+
putExtra(GpsTripService.EXTRA_DEEP_LINK, deepLink)
22+
}
23+
24+
try {
25+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
26+
context.startForegroundService(intent)
27+
} else {
28+
context.startService(intent)
29+
}
30+
} catch (e: Exception) {
31+
Log.e(TAG, "Failed to start GPS trip service", e)
32+
}
33+
}
34+
35+
@ReactMethod
36+
fun stopService() {
37+
val context = reactApplicationContext
38+
context.stopService(Intent(context, GpsTripService::class.java))
39+
}
40+
41+
companion object {
42+
private const val TAG = "GpsTripServiceModule"
43+
}
44+
}

android/app/src/main/java/com/expensify/chat/customairshipextender/CustomAirshipExtender.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.urbanairship.UAirship;
66
import com.urbanairship.push.NotificationListener;
77
import com.urbanairship.push.PushManager;
8-
import com.urbanairship.liveupdate.LiveUpdateManager;
98
import com.urbanairship.reactnative.AirshipExtender;
109

1110
public class CustomAirshipExtender implements AirshipExtender {
@@ -15,7 +14,5 @@ public void onAirshipReady(@NonNull Context context, @NonNull UAirship airship)
1514

1615
CustomNotificationProvider notificationProvider = new CustomNotificationProvider(context, airship.getAirshipConfigOptions());
1716
pushManager.setNotificationProvider(notificationProvider);
18-
19-
LiveUpdateManager.shared().register(GpsLiveUpdateHandler.TYPE, new GpsLiveUpdateHandler());
2017
}
2118
}

android/app/src/main/java/com/expensify/chat/customairshipextender/GpsLiveUpdateHandler.kt

Lines changed: 0 additions & 86 deletions
This file was deleted.

android/app/src/main/java/com/expensify/chat/customairshipextender/GpsTripService.kt

Lines changed: 0 additions & 29 deletions
This file was deleted.

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
"expo-font": "14.0.8",
136136
"expo-image": "55.0.3",
137137
"expo-image-manipulator": "55.0.2",
138-
"expo-location": "55.0.3",
138+
"expo-location": "55.1.10",
139139
"expo-modules-core": "55.0.25",
140140
"expo-store-review": "~9.0.8",
141141
"expo-task-manager": "55.0.2",

src/components/GPSTripStateChecker/index.native.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ function GPSTripStateChecker() {
4343
}
4444

4545
handleGpsTripInProgressOnAppRestart();
46-
checkAndCleanGpsNotification();
4746

4847
return () => {
4948
hasStartedLocationUpdatesAsync(BACKGROUND_LOCATION_TRACKING_TASK_NAME).then((isRunning) => {

0 commit comments

Comments
 (0)