expo-location 16.4.0 → 16.5.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/CHANGELOG.md +20 -0
- package/android/build.gradle +5 -6
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/expo/modules/location/LocationActivityResultListener.kt +5 -0
- package/android/src/main/java/expo/modules/location/LocationExceptions.kt +57 -0
- package/android/src/main/java/expo/modules/location/LocationHelpers.kt +220 -0
- package/android/src/main/java/expo/modules/location/LocationModule.kt +820 -0
- package/android/src/main/java/expo/modules/location/LocationRequestCallbacks.kt +11 -0
- package/android/src/main/java/expo/modules/location/records/LocationArguments.kt +101 -0
- package/android/src/main/java/expo/modules/location/records/LocationResults.kt +202 -0
- package/android/src/main/java/expo/modules/location/services/LocationTaskService.kt +120 -0
- package/android/src/main/java/expo/modules/location/taskConsumers/GeofencingTaskConsumer.kt +245 -0
- package/android/src/main/java/expo/modules/location/taskConsumers/LocationTaskConsumer.kt +322 -0
- package/expo-location-0.0.1-canary-20231129-c0ec023.tgz +0 -0
- package/expo-location-0.0.1-canary-20231130-c8a9bf9.tgz +0 -0
- package/expo-location-0.0.1-canary-20231130-ede75a7-1.tgz +0 -0
- package/expo-location-0.0.1-canary-20231130-ede75a7.tgz +0 -0
- package/expo-module.config.json +6 -0
- package/ios/EXLocation.podspec +1 -1
- package/package.json +2 -2
- package/android/src/main/java/expo/modules/location/LocationActivityResultListener.java +0 -5
- package/android/src/main/java/expo/modules/location/LocationHelpers.java +0 -267
- package/android/src/main/java/expo/modules/location/LocationModule.java +0 -1012
- package/android/src/main/java/expo/modules/location/LocationPackage.java +0 -16
- package/android/src/main/java/expo/modules/location/LocationRequestCallbacks.java +0 -12
- package/android/src/main/java/expo/modules/location/exceptions/LocationBackgroundUnauthorizedException.java +0 -15
- package/android/src/main/java/expo/modules/location/exceptions/LocationRequestRejectedException.java +0 -15
- package/android/src/main/java/expo/modules/location/exceptions/LocationSettingsUnsatisfiedException.java +0 -15
- package/android/src/main/java/expo/modules/location/exceptions/LocationUnauthorizedException.java +0 -15
- package/android/src/main/java/expo/modules/location/exceptions/LocationUnavailableException.java +0 -15
- package/android/src/main/java/expo/modules/location/services/LocationTaskService.java +0 -143
- package/android/src/main/java/expo/modules/location/taskConsumers/GeofencingTaskConsumer.java +0 -289
- package/android/src/main/java/expo/modules/location/taskConsumers/LocationTaskConsumer.java +0 -364
- package/unimodule.json +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,26 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 16.5.1 — 2023-12-19
|
|
14
|
+
|
|
15
|
+
_This version does not introduce any user-facing changes._
|
|
16
|
+
|
|
17
|
+
## 16.5.0 — 2023-11-14
|
|
18
|
+
|
|
19
|
+
### 🛠 Breaking changes
|
|
20
|
+
|
|
21
|
+
- Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
22
|
+
- On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes))
|
|
23
|
+
|
|
24
|
+
### 💡 Others
|
|
25
|
+
|
|
26
|
+
- [Android] Moved to the new Modules API. ([#24737](https://github.com/expo/expo/pull/24737) by [@behenate](https://github.com/behenate))
|
|
27
|
+
- Remove `unimodule.json` in favour of `expo-module.config.json`. ([#25100](https://github.com/expo/expo/pull/25100) by [@reichhartd](https://github.com/reichhartd))
|
|
28
|
+
|
|
29
|
+
### 📚 3rd party library updates
|
|
30
|
+
|
|
31
|
+
- Updated `com.google.android.gms:play-services-location` to `21.0.1`. ([#25028](https://github.com/expo/expo/pull/25028) by [@behenate](https://github.com/behenate))
|
|
32
|
+
|
|
13
33
|
## 16.4.0 — 2023-10-17
|
|
14
34
|
|
|
15
35
|
### 🛠 Breaking changes
|
package/android/build.gradle
CHANGED
|
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '16.
|
|
6
|
+
version = '16.5.1'
|
|
7
7
|
|
|
8
8
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
9
9
|
if (expoModulesCorePlugin.exists()) {
|
|
@@ -61,11 +61,11 @@ if (!safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
|
61
61
|
android {
|
|
62
62
|
// Remove this if and it's contents, when support for SDK49 is dropped
|
|
63
63
|
if (!safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
64
|
-
compileSdkVersion safeExtGet("compileSdkVersion",
|
|
64
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
|
65
65
|
|
|
66
66
|
defaultConfig {
|
|
67
67
|
minSdkVersion safeExtGet("minSdkVersion", 23)
|
|
68
|
-
targetSdkVersion safeExtGet("targetSdkVersion",
|
|
68
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
publishing {
|
|
@@ -94,7 +94,7 @@ android {
|
|
|
94
94
|
namespace "expo.modules.location"
|
|
95
95
|
defaultConfig {
|
|
96
96
|
versionCode 29
|
|
97
|
-
versionName "16.
|
|
97
|
+
versionName "16.5.1"
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -105,8 +105,7 @@ dependencies {
|
|
|
105
105
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
api 'com.google.android.gms:play-services-location:
|
|
109
|
-
|
|
108
|
+
api 'com.google.android.gms:play-services-location:21.0.1'
|
|
110
109
|
api('io.nlopez.smartlocation:library:3.3.3') {
|
|
111
110
|
transitive = false
|
|
112
111
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
3
3
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
4
4
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
5
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
5
6
|
|
|
6
7
|
<application>
|
|
7
8
|
<service
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package expo.modules.location
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.exception.CodedException
|
|
4
|
+
|
|
5
|
+
internal class NoPermissionsModuleException :
|
|
6
|
+
CodedException("Permissions module is null. Are you sure all the installed Expo modules are properly linked?")
|
|
7
|
+
|
|
8
|
+
internal class NoPermissionInManifestException(permissionName: String) :
|
|
9
|
+
CodedException("You need to add `$permissionName` to the AndroidManifest")
|
|
10
|
+
|
|
11
|
+
internal class LocationBackgroundUnauthorizedException :
|
|
12
|
+
CodedException("Not authorized to use background location services")
|
|
13
|
+
|
|
14
|
+
internal class LocationRequestRejectedException(cause: Exception) :
|
|
15
|
+
CodedException("Location request has been rejected: " + cause.message)
|
|
16
|
+
|
|
17
|
+
internal class LocationRequestCancelledException :
|
|
18
|
+
CodedException("Location request has been cancelled")
|
|
19
|
+
|
|
20
|
+
internal class LocationSettingsUnsatisfiedException :
|
|
21
|
+
CodedException("Location request failed due to unsatisfied device settings")
|
|
22
|
+
|
|
23
|
+
internal class LocationUnauthorizedException :
|
|
24
|
+
CodedException("Not authorized to use location services")
|
|
25
|
+
|
|
26
|
+
internal class LocationUnavailableException :
|
|
27
|
+
CodedException("Location is unavailable. Make sure that location services are enabled")
|
|
28
|
+
|
|
29
|
+
internal class LocationUnknownException :
|
|
30
|
+
CodedException("Current location is unknown")
|
|
31
|
+
|
|
32
|
+
internal class SensorManagerUnavailable :
|
|
33
|
+
CodedException("Sensor manager is unavailable")
|
|
34
|
+
|
|
35
|
+
internal class GeocodeException(message: String?, cause: Throwable? = null) :
|
|
36
|
+
CodedException("An exception occurred when accessing the geocode: ${message ?: ""} ${cause?.message ?: ""}")
|
|
37
|
+
|
|
38
|
+
internal class NoGeocodeException :
|
|
39
|
+
CodedException("Could not find the Geocoder")
|
|
40
|
+
|
|
41
|
+
internal class TaskManagerNotFoundException :
|
|
42
|
+
CodedException("Could not find the task manager")
|
|
43
|
+
|
|
44
|
+
internal class GeofencingException(message: String?, cause: Throwable? = null) :
|
|
45
|
+
CodedException("A geofencing exception has occurred: ${message ?: ""} ${cause?.message ?: ""}")
|
|
46
|
+
|
|
47
|
+
internal class MissingActivityManagerException :
|
|
48
|
+
CodedException("Activity manager is unavailable")
|
|
49
|
+
|
|
50
|
+
internal class MissingUIManagerException :
|
|
51
|
+
CodedException("UIManager is unavailable")
|
|
52
|
+
|
|
53
|
+
internal class ConversionException(fromClass: Class<*>, toClass: Class<*>, message: String? = "") :
|
|
54
|
+
CodedException("Couldn't cast from ${fromClass::class.simpleName} to ${toClass::class.java.simpleName}: $message")
|
|
55
|
+
|
|
56
|
+
internal class ForegroundServiceStartNotAllowedException :
|
|
57
|
+
CodedException("Couldn't start the foreground service. Foreground service cannot be started when the application is in the background.")
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
package expo.modules.location
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.location.Location
|
|
5
|
+
import android.location.LocationManager
|
|
6
|
+
import android.os.Bundle
|
|
7
|
+
import com.google.android.gms.location.CurrentLocationRequest
|
|
8
|
+
import com.google.android.gms.location.FusedLocationProviderClient
|
|
9
|
+
import com.google.android.gms.location.Granularity
|
|
10
|
+
import com.google.android.gms.location.LocationRequest
|
|
11
|
+
import com.google.android.gms.location.Priority
|
|
12
|
+
import expo.modules.interfaces.permissions.Permissions
|
|
13
|
+
import expo.modules.kotlin.Promise
|
|
14
|
+
import expo.modules.kotlin.exception.CodedException
|
|
15
|
+
import expo.modules.location.records.LocationLastKnownOptions
|
|
16
|
+
import expo.modules.location.records.LocationOptions
|
|
17
|
+
import expo.modules.location.records.LocationResponse
|
|
18
|
+
import expo.modules.location.records.PermissionRequestResponse
|
|
19
|
+
import io.nlopez.smartlocation.location.config.LocationAccuracy
|
|
20
|
+
import io.nlopez.smartlocation.location.config.LocationParams
|
|
21
|
+
import kotlin.coroutines.resume
|
|
22
|
+
import kotlin.coroutines.resumeWithException
|
|
23
|
+
import kotlin.coroutines.suspendCoroutine
|
|
24
|
+
|
|
25
|
+
class LocationHelpers {
|
|
26
|
+
companion object {
|
|
27
|
+
/**
|
|
28
|
+
* Checks whether given location didn't exceed given `maxAge` and fits in the required accuracy.
|
|
29
|
+
*/
|
|
30
|
+
internal fun isLocationValid(location: Location?, options: LocationLastKnownOptions): Boolean {
|
|
31
|
+
if (location == null) {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
val maxAge = options.maxAge ?: Double.MAX_VALUE
|
|
35
|
+
val requiredAccuracy = options.requiredAccuracy ?: Double.MAX_VALUE
|
|
36
|
+
val timeDiff = (System.currentTimeMillis() - location.time).toDouble()
|
|
37
|
+
return timeDiff <= maxAge && location.accuracy <= requiredAccuracy
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fun hasNetworkProviderEnabled(context: Context?): Boolean {
|
|
41
|
+
if (context == null) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
|
|
45
|
+
return locationManager != null && locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
internal fun prepareLocationRequest(options: LocationOptions): LocationRequest {
|
|
49
|
+
val locationParams = mapOptionsToLocationParams(options)
|
|
50
|
+
|
|
51
|
+
return LocationRequest.Builder(locationParams.interval)
|
|
52
|
+
.setMinUpdateIntervalMillis(locationParams.interval)
|
|
53
|
+
.setMaxUpdateDelayMillis(locationParams.interval)
|
|
54
|
+
.setMinUpdateDistanceMeters(locationParams.distance)
|
|
55
|
+
.setPriority(mapAccuracyToPriority(options.accuracy))
|
|
56
|
+
.build()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
internal fun prepareCurrentLocationRequest(options: LocationOptions): CurrentLocationRequest {
|
|
60
|
+
val locationParams = mapOptionsToLocationParams(options)
|
|
61
|
+
|
|
62
|
+
return CurrentLocationRequest.Builder().apply {
|
|
63
|
+
setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
|
|
64
|
+
setPriority(mapAccuracyToPriority(options.accuracy))
|
|
65
|
+
setMaxUpdateAgeMillis(locationParams.interval)
|
|
66
|
+
}.build()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fun requestSingleLocation(locationProvider: FusedLocationProviderClient, locationRequest: CurrentLocationRequest, promise: Promise) {
|
|
70
|
+
try {
|
|
71
|
+
locationProvider.getCurrentLocation(locationRequest, null)
|
|
72
|
+
.addOnSuccessListener {
|
|
73
|
+
promise.resolve(LocationResponse(it))
|
|
74
|
+
}
|
|
75
|
+
.addOnFailureListener {
|
|
76
|
+
promise.reject(LocationRequestRejectedException(it))
|
|
77
|
+
}
|
|
78
|
+
.addOnCanceledListener {
|
|
79
|
+
promise.reject(LocationRequestCancelledException())
|
|
80
|
+
}
|
|
81
|
+
} catch (e: SecurityException) {
|
|
82
|
+
promise.reject(LocationRequestRejectedException(e))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fun requestContinuousUpdates(locationModule: LocationModule, locationRequest: LocationRequest, watchId: Int, promise: Promise) {
|
|
87
|
+
locationModule.requestLocationUpdates(
|
|
88
|
+
locationRequest,
|
|
89
|
+
watchId,
|
|
90
|
+
object : LocationRequestCallbacks {
|
|
91
|
+
override fun onLocationChanged(location: Location) {
|
|
92
|
+
locationModule.sendLocationResponse(watchId, LocationResponse(location))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override fun onRequestSuccess() {
|
|
96
|
+
promise.resolve(null)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override fun onRequestFailed(cause: CodedException) {
|
|
100
|
+
promise.reject(cause)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private fun mapOptionsToLocationParams(options: LocationOptions): LocationParams {
|
|
107
|
+
val accuracy = options.accuracy
|
|
108
|
+
val locationParamsBuilder = buildLocationParamsForAccuracy(accuracy)
|
|
109
|
+
|
|
110
|
+
options.timeInterval?.let {
|
|
111
|
+
locationParamsBuilder.setInterval(it)
|
|
112
|
+
}
|
|
113
|
+
options.distanceInterval?.let {
|
|
114
|
+
locationParamsBuilder.setDistance(it.toFloat())
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return locationParamsBuilder.build()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private fun mapAccuracyToPriority(accuracy: Int): Int {
|
|
121
|
+
return when (accuracy) {
|
|
122
|
+
LocationModule.ACCURACY_BEST_FOR_NAVIGATION, LocationModule.ACCURACY_HIGHEST, LocationModule.ACCURACY_HIGH -> Priority.PRIORITY_HIGH_ACCURACY
|
|
123
|
+
LocationModule.ACCURACY_BALANCED, LocationModule.ACCURACY_LOW -> Priority.PRIORITY_BALANCED_POWER_ACCURACY
|
|
124
|
+
LocationModule.ACCURACY_LOWEST -> Priority.PRIORITY_LOW_POWER
|
|
125
|
+
else -> Priority.PRIORITY_BALANCED_POWER_ACCURACY
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private fun buildLocationParamsForAccuracy(accuracy: Int): LocationParams.Builder {
|
|
130
|
+
return when (accuracy) {
|
|
131
|
+
LocationModule.ACCURACY_LOWEST -> LocationParams.Builder()
|
|
132
|
+
.setAccuracy(LocationAccuracy.LOWEST)
|
|
133
|
+
.setDistance(3000f)
|
|
134
|
+
.setInterval(10000)
|
|
135
|
+
|
|
136
|
+
LocationModule.ACCURACY_LOW -> LocationParams.Builder()
|
|
137
|
+
.setAccuracy(LocationAccuracy.LOW)
|
|
138
|
+
.setDistance(1000f)
|
|
139
|
+
.setInterval(5000)
|
|
140
|
+
|
|
141
|
+
LocationModule.ACCURACY_BALANCED -> LocationParams.Builder()
|
|
142
|
+
.setAccuracy(LocationAccuracy.MEDIUM)
|
|
143
|
+
.setDistance(100f)
|
|
144
|
+
.setInterval(3000)
|
|
145
|
+
|
|
146
|
+
LocationModule.ACCURACY_HIGH -> LocationParams.Builder()
|
|
147
|
+
.setAccuracy(LocationAccuracy.HIGH)
|
|
148
|
+
.setDistance(50f)
|
|
149
|
+
.setInterval(2000)
|
|
150
|
+
|
|
151
|
+
LocationModule.ACCURACY_HIGHEST -> LocationParams.Builder()
|
|
152
|
+
.setAccuracy(LocationAccuracy.HIGH)
|
|
153
|
+
.setDistance(25f)
|
|
154
|
+
.setInterval(1000)
|
|
155
|
+
|
|
156
|
+
LocationModule.ACCURACY_BEST_FOR_NAVIGATION -> LocationParams.Builder()
|
|
157
|
+
.setAccuracy(LocationAccuracy.HIGH)
|
|
158
|
+
.setDistance(0f)
|
|
159
|
+
.setInterval(500)
|
|
160
|
+
|
|
161
|
+
else -> LocationParams.Builder()
|
|
162
|
+
.setAccuracy(LocationAccuracy.MEDIUM)
|
|
163
|
+
.setDistance(100f)
|
|
164
|
+
.setInterval(3000)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fun isAnyProviderAvailable(context: Context?): Boolean {
|
|
169
|
+
val locationManager = context?.getSystemService(Context.LOCATION_SERVICE) as? LocationManager ?: return false
|
|
170
|
+
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Decorator for Permissions.getPermissionsWithPermissionsManager, for use in Kotlin coroutines
|
|
174
|
+
internal suspend fun getPermissionsWithPermissionsManager(contextPermissions: Permissions, vararg permissionStrings: String): PermissionRequestResponse {
|
|
175
|
+
return suspendCoroutine { continuation ->
|
|
176
|
+
Permissions.getPermissionsWithPermissionsManager(
|
|
177
|
+
contextPermissions,
|
|
178
|
+
object : Promise {
|
|
179
|
+
override fun resolve(value: Any?) {
|
|
180
|
+
val result = value as? Bundle ?: throw ConversionException(Any::class.java, Bundle::class.java, "value returned by the permission promise is not a Bundle")
|
|
181
|
+
continuation.resume(PermissionRequestResponse(result))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
override fun reject(code: String, message: String?, cause: Throwable?) {
|
|
185
|
+
continuation.resumeWithException(CodedException(code, message, cause))
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
*permissionStrings
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Decorator for Permissions.getPermissionsWithPermissionsManager, for use in Kotlin coroutines
|
|
194
|
+
internal suspend fun askForPermissionsWithPermissionsManager(contextPermissions: Permissions, vararg permissionStrings: String): Bundle {
|
|
195
|
+
return suspendCoroutine {
|
|
196
|
+
Permissions.askForPermissionsWithPermissionsManager(
|
|
197
|
+
contextPermissions,
|
|
198
|
+
object : Promise {
|
|
199
|
+
override fun resolve(value: Any?) {
|
|
200
|
+
it.resume(value as? Bundle ?: throw ConversionException(Any::class.java, Bundle::class.java, "value returned by the permission promise is not a Bundle"))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
override fun reject(code: String, message: String?, cause: Throwable?) {
|
|
204
|
+
it.resumeWithException(CodedException(code, message, cause))
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
*permissionStrings
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* A singleton that keeps information about whether the app is in the foreground or not.
|
|
216
|
+
* This is a simple solution for passing current foreground information from the LocationModule to LocationTaskConsumer.
|
|
217
|
+
*/
|
|
218
|
+
object AppForegroundedSingleton {
|
|
219
|
+
var isForegrounded = false
|
|
220
|
+
}
|