From f356c155956f6f18d89805f96a459089475db99d Mon Sep 17 00:00:00 2001 From: Dario Alessandro Lencina Talarico Date: Wed, 4 Mar 2020 20:06:25 -0500 Subject: [PATCH 1/5] fix typo --- .../LocationUpdatesService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/LocationUpdatesService.java b/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/LocationUpdatesService.java index 5804bc5f..13c7ec0c 100644 --- a/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/LocationUpdatesService.java +++ b/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/LocationUpdatesService.java @@ -57,7 +57,7 @@ * bound to this service, frequent location updates are permitted. When the activity is removed * from the foreground, the service promotes itself to a foreground service, and location updates * continue. When the activity comes back to the foreground, the foreground service stops, and the - * notification assocaited with that service is removed. + * notification associated with that service is removed. */ public class LocationUpdatesService extends Service { From e9d54e9cbd13af77ab3ac19709ac2c39f732633e Mon Sep 17 00:00:00 2001 From: Dario Alessandro Lencina Talarico Date: Wed, 4 Mar 2020 20:14:59 -0500 Subject: [PATCH 2/5] found and fixed another typo --- .../location/sample/locationupdatesforegroundservice/Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/Utils.java b/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/Utils.java index 07164cf0..01dc5887 100644 --- a/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/Utils.java +++ b/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/Utils.java @@ -26,7 +26,7 @@ class Utils { - static final String KEY_REQUESTING_LOCATION_UPDATES = "requesting_locaction_updates"; + static final String KEY_REQUESTING_LOCATION_UPDATES = "requesting_location_updates"; /** * Returns true if requesting location updates, otherwise returns false. From fdfa4b05060134826b854f6454b3ae7c78e63ea4 Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Thu, 12 Mar 2020 14:23:53 -0700 Subject: [PATCH 3/5] Adds Location data layer files and permission extension funs. --- .../locationupdatesbackgroundkotlin/Utils.kt | 55 +++++++++ .../data/LocationRepository.kt | 80 +++++++++++++ .../data/MyLocationManager.kt | 109 ++++++++++++++++++ .../data/db/MyLocationDao.kt | 45 ++++++++ .../data/db/MyLocationDatabase.kt | 58 ++++++++++ .../data/db/MyLocationEntity.kt | 48 ++++++++ .../data/db/MyLocationTypeConverters.kt | 49 ++++++++ 7 files changed, 444 insertions(+) create mode 100644 LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt create mode 100644 LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt create mode 100644 LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt create mode 100644 LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt create mode 100644 LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt create mode 100644 LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt create mode 100644 LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.kt diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt new file mode 100644 index 00000000..aacfc64b --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager + +import androidx.core.app.ActivityCompat +import androidx.fragment.app.Fragment + +import com.google.android.material.snackbar.Snackbar + +/** + * Helper functions to simplify permission checks/requests. + */ +fun Context.hasPermission(permission: String): Boolean { + + // Background permissions didn't exit prior to Q, so it's approved by default. + if (permission == Manifest.permission.ACCESS_BACKGROUND_LOCATION && + android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) { + + return true + } + + return ActivityCompat.checkSelfPermission(this, permission) == + PackageManager.PERMISSION_GRANTED +} + +fun Fragment.requestPermissionWithRationale( + permission: String, requestCode: Int, snackbar: Snackbar +) { + val provideRationale = shouldShowRequestPermissionRationale(permission) + + // If the user denied a previous request, but didn't check "Don't ask again", we provide + // additional rationale. (The Snackbar should have an action to request the permission.) + if (provideRationale) { + snackbar.show() + } else { + requestPermissions(arrayOf(permission), requestCode) + } +} diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt new file mode 100644 index 00000000..81730a50 --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data + +import android.content.Context + +import androidx.lifecycle.LiveData + +import java.util.UUID +import java.util.concurrent.Executors + +import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db.MyLocationDatabase +import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db.MyLocationEntity + +private const val TAG = "LocationRepository" + +/** + * Access point for database (MyLocation data) and location APIs (start/stop location tracking and + * checking tracking status). + */ +class LocationRepository private constructor(private val myLocationDatabase: MyLocationDatabase, + private val myLocationManager: MyLocationManager + ) { + + // Database related fields/methods: + private val locationDao = myLocationDatabase.locationDao() + + private val executor = Executors.newSingleThreadExecutor() + + fun getLocations(): LiveData> = locationDao.getLocations() + + // Not being used now but could in future versions. + fun getLocation(id: UUID): LiveData = locationDao.getLocation(id) + + // Not being used now but could in future versions. + fun updateLocation(myLocationEntity: MyLocationEntity) { + executor.execute { + locationDao.updateLocation(myLocationEntity) + } + } + + fun addLocation(myLocationEntity: MyLocationEntity) { + executor.execute { + locationDao.addLocation(myLocationEntity) + } + } + + // Location related fields/methods: + val trackingLocation: LiveData = myLocationManager.trackingLocation + + fun startLocationUpdates() = myLocationManager.startLocationUpdates() + + fun stopLocationUpdates() = myLocationManager.stopLocationUpdates() + + companion object { + private var INSTANCE: LocationRepository? = null + + fun getInstance(context: Context): LocationRepository { + return INSTANCE ?: synchronized(this) { + INSTANCE ?: LocationRepository( + MyLocationDatabase.getInstance(context), + MyLocationManager.getInstance(context)) + .also { INSTANCE = it } + } + } + } +} diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt new file mode 100644 index 00000000..871e3c52 --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data + +import android.Manifest +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.lifecycle.MutableLiveData +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.LocationUpdatesBroadcastReceiver +import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.hasPermission +import java.util.concurrent.TimeUnit + +private const val TAG = "MyLocationManager" + +/** + * Manages all location related tasks for the app. + */ +class MyLocationManager private constructor(private val context: Context) { + + val trackingLocation: MutableLiveData by lazy { + MutableLiveData(false) + } + + // The Fused Location Provider provides access to location tracking APIs. + private val fusedLocationClient: FusedLocationProviderClient by lazy { + LocationServices.getFusedLocationProviderClient(context) + } + + // Stores parameters for requests to the FusedLocationProviderApi. + private val locationRequest: LocationRequest by lazy { + LocationRequest() + // Sets the desired interval for active location updates. This interval is inexact. You + // may not receive updates at all if no location sources are available, or you may + // receive them slower than requested. You may also receive updates faster than + // requested if other applications are requesting location at a faster interval. + // + // IMPORTANT NOTE: Apps running on "O" devices (regardless of targetSdkVersion) may + // receive updates less frequently than this interval when the app is no longer in the + // foreground. + .setInterval(TimeUnit.SECONDS.toMillis(60)) + + // Sets the fastest rate for active location updates. This interval is exact, and your + // application will never receive updates faster than this value. + .setFastestInterval(TimeUnit.SECONDS.toMillis(30)) + + // Sets the maximum time when batched location updates are delivered. Updates may be + // delivered sooner than this interval. + .setMaxWaitTime(TimeUnit.MINUTES.toMillis(2)) + + .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) + } + + private val locationUpdatePendingIntent: PendingIntent by lazy { + // API level 26 and above (Oreo+) place limits on Services, so we use a BroadcastReceiver. + val intent = Intent (context, LocationUpdatesBroadcastReceiver::class.java) + intent.action = LocationUpdatesBroadcastReceiver.ACTION_PROCESS_UPDATES + PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + fun startLocationUpdates() { + Log.d(TAG, "startLocationUpdates()") + + if (!context.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) return + + try { + trackingLocation.value = true + fusedLocationClient.requestLocationUpdates(locationRequest, locationUpdatePendingIntent) + + } catch (e: SecurityException) { + trackingLocation.value = false + e.printStackTrace() + } + } + + fun stopLocationUpdates() { + Log.d(TAG, "stopLocationUpdates()") + trackingLocation.value = false + fusedLocationClient.removeLocationUpdates(locationUpdatePendingIntent) + } + + companion object { + @Volatile private var INSTANCE: MyLocationManager? = null + + fun getInstance(context: Context): MyLocationManager { + + return INSTANCE ?: synchronized(this) { + INSTANCE ?: MyLocationManager(context).also { INSTANCE = it } + } + } + } +} diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt new file mode 100644 index 00000000..8a368522 --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update + +import java.util.UUID + +/** + * Definition of SQL for mapping to database. + */ +@Dao +interface MyLocationDao { + + @Query("SELECT * FROM my_location_table ORDER BY date DESC") + fun getLocations(): LiveData> + + @Query("SELECT * FROM my_location_table WHERE id=(:id)") + fun getLocation(id: UUID): LiveData + + + @Update + fun updateLocation(myLocationEntity: MyLocationEntity) + + + @Insert + fun addLocation(myLocationEntity: MyLocationEntity) +} diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt new file mode 100644 index 00000000..8b8e989d --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db + +import android.content.Context + +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters + +private const val DATABASE_NAME = "my-location-database" + +/** + * Database for storing all location data. + */ +@Database(entities = [MyLocationEntity::class], version = 1) +@TypeConverters(MyLocationTypeConverters::class) +abstract class MyLocationDatabase: RoomDatabase() { + abstract fun locationDao(): MyLocationDao + + companion object { + // For Singleton instantiation + @Volatile private var INSTANCE: MyLocationDatabase? = null + + fun getInstance(context: Context): MyLocationDatabase { + return INSTANCE + ?: synchronized(this) { + INSTANCE + ?: buildDatabase( + context + ).also { INSTANCE = it } + } + } + + private fun buildDatabase(context: Context): MyLocationDatabase { + return Room.databaseBuilder( + context, + MyLocationDatabase::class.java, + DATABASE_NAME + ) + .build() + } + } +} diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt new file mode 100644 index 00000000..b703591a --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db + +import androidx.room.Entity +import androidx.room.PrimaryKey + +import java.text.DateFormat +import java.util.Date +import java.util.UUID + +/** + * Data class for Location related data (only takes what's needed from + * {@link android.location.Location} class). + */ +@Entity(tableName = "my_location_table") +data class MyLocationEntity (@PrimaryKey val id:UUID = UUID.randomUUID(), + val latitude:Double = 0.0, + val longitude:Double = 0.0, + val foreground:Boolean = true, + val date:Date = Date() + ) { + + override fun toString(): String { + + val appState = if(foreground) { + "in app" + } else { + "in BG" + } + + return "$latitude, $longitude $appState on " + + "${DateFormat.getDateTimeInstance().format(date)}.\n" + } +} diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.kt new file mode 100644 index 00000000..cddc4822 --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db + +import androidx.room.TypeConverter + +import java.util.Date +import java.util.UUID + +/** + * Converts non-standard objects in the {@link MyLocation} data class into and out of the database. + */ +class MyLocationTypeConverters { + + @TypeConverter + fun fromDate(date: Date?): Long? { + return date?.time + } + + @TypeConverter + fun toDate(millisSinceEpoch: Long?): Date? { + return millisSinceEpoch?.let { + Date(it) + } + } + + @TypeConverter + fun fromUUID(uuid: UUID?): String? { + return uuid?.toString() + } + + @TypeConverter + fun toUUID(uuid: String?): UUID? { + return UUID.fromString(uuid) + } +} From 3c7720c8b7e92383db46a474f2d4207676e7891c Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Mon, 16 Mar 2020 15:49:10 -0700 Subject: [PATCH 4/5] Addresses PR feedback (name changes, style changes, and removes unused strings). --- .../locationupdatesbackgroundkotlin/Utils.kt | 15 +-- .../data/LocationRepository.kt | 49 +++++++--- .../data/MyLocationManager.kt | 91 ++++++++++++------- .../data/db/MyLocationDao.kt | 5 +- .../data/db/MyLocationDatabase.kt | 14 +-- .../data/db/MyLocationEntity.kt | 17 ++-- .../data/db/MyLocationTypeConverters.kt | 1 - .../app/src/main/res/values/strings.xml | 6 -- 8 files changed, 115 insertions(+), 83 deletions(-) diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt index aacfc64b..35e68438 100644 --- a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt @@ -18,10 +18,8 @@ package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin import android.Manifest import android.content.Context import android.content.pm.PackageManager - import androidx.core.app.ActivityCompat import androidx.fragment.app.Fragment - import com.google.android.material.snackbar.Snackbar /** @@ -32,7 +30,6 @@ fun Context.hasPermission(permission: String): Boolean { // Background permissions didn't exit prior to Q, so it's approved by default. if (permission == Manifest.permission.ACCESS_BACKGROUND_LOCATION && android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) { - return true } @@ -40,13 +37,19 @@ fun Context.hasPermission(permission: String): Boolean { PackageManager.PERMISSION_GRANTED } +/** + * Requests permission and if the user denied a previous request, but didn't check + * "Don't ask again", we provide additional rationale. + * + * Note: The Snackbar should have an action to request the permission. + */ fun Fragment.requestPermissionWithRationale( - permission: String, requestCode: Int, snackbar: Snackbar + permission: String, + requestCode: Int, + snackbar: Snackbar ) { val provideRationale = shouldShowRequestPermissionRationale(permission) - // If the user denied a previous request, but didn't check "Don't ask again", we provide - // additional rationale. (The Snackbar should have an action to request the permission.) if (provideRationale) { snackbar.show() } else { diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt index 81730a50..9a9c25da 100644 --- a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt @@ -16,14 +16,13 @@ package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data import android.content.Context - +import androidx.annotation.MainThread import androidx.lifecycle.LiveData - -import java.util.UUID -import java.util.concurrent.Executors - import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db.MyLocationDatabase import com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db.MyLocationEntity +import java.util.UUID +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors private const val TAG = "LocationRepository" @@ -31,27 +30,39 @@ private const val TAG = "LocationRepository" * Access point for database (MyLocation data) and location APIs (start/stop location tracking and * checking tracking status). */ -class LocationRepository private constructor(private val myLocationDatabase: MyLocationDatabase, - private val myLocationManager: MyLocationManager - ) { +class LocationRepository private constructor( + private val myLocationDatabase: MyLocationDatabase, + private val myLocationManager: MyLocationManager, + private val executor:ExecutorService +) { // Database related fields/methods: private val locationDao = myLocationDatabase.locationDao() - private val executor = Executors.newSingleThreadExecutor() - + /** + * Returns all recorded locations from database. + */ fun getLocations(): LiveData> = locationDao.getLocations() // Not being used now but could in future versions. + /** + * Returns specific location in database. + */ fun getLocation(id: UUID): LiveData = locationDao.getLocation(id) // Not being used now but could in future versions. + /** + * Updates location in database. + */ fun updateLocation(myLocationEntity: MyLocationEntity) { executor.execute { locationDao.updateLocation(myLocationEntity) } } + /** + * Adds location to the database. + */ fun addLocation(myLocationEntity: MyLocationEntity) { executor.execute { locationDao.addLocation(myLocationEntity) @@ -59,20 +70,32 @@ class LocationRepository private constructor(private val myLocationDatabase: MyL } // Location related fields/methods: + /** + * Tracks whether the app is actively subscribed to location changes. + */ val trackingLocation: LiveData = myLocationManager.trackingLocation + /** + * Subscribes to location updates. + */ + @MainThread fun startLocationUpdates() = myLocationManager.startLocationUpdates() + /** + * Un-subscribes from location updates. + */ + @MainThread fun stopLocationUpdates() = myLocationManager.stopLocationUpdates() companion object { - private var INSTANCE: LocationRepository? = null + @Volatile private var INSTANCE: LocationRepository? = null - fun getInstance(context: Context): LocationRepository { + fun getInstance(context: Context, executor: ExecutorService): LocationRepository { return INSTANCE ?: synchronized(this) { INSTANCE ?: LocationRepository( MyLocationDatabase.getInstance(context), - MyLocationManager.getInstance(context)) + MyLocationManager.getInstance(context), + executor) .also { INSTANCE = it } } } diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt index 871e3c52..8ec33738 100644 --- a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt @@ -20,6 +20,8 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.util.Log +import androidx.annotation.MainThread +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationRequest @@ -35,64 +37,86 @@ private const val TAG = "MyLocationManager" */ class MyLocationManager private constructor(private val context: Context) { - val trackingLocation: MutableLiveData by lazy { - MutableLiveData(false) - } + private val _trackingLocation: MutableLiveData = MutableLiveData(false) + + /** + * Tracks whether the app is actively subscribed to location changes. + */ + val trackingLocation: LiveData + get() = _trackingLocation // The Fused Location Provider provides access to location tracking APIs. - private val fusedLocationClient: FusedLocationProviderClient by lazy { + private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) - } // Stores parameters for requests to the FusedLocationProviderApi. - private val locationRequest: LocationRequest by lazy { - LocationRequest() - // Sets the desired interval for active location updates. This interval is inexact. You - // may not receive updates at all if no location sources are available, or you may - // receive them slower than requested. You may also receive updates faster than - // requested if other applications are requesting location at a faster interval. - // - // IMPORTANT NOTE: Apps running on "O" devices (regardless of targetSdkVersion) may - // receive updates less frequently than this interval when the app is no longer in the - // foreground. - .setInterval(TimeUnit.SECONDS.toMillis(60)) - - // Sets the fastest rate for active location updates. This interval is exact, and your - // application will never receive updates faster than this value. - .setFastestInterval(TimeUnit.SECONDS.toMillis(30)) - - // Sets the maximum time when batched location updates are delivered. Updates may be - // delivered sooner than this interval. - .setMaxWaitTime(TimeUnit.MINUTES.toMillis(2)) - - .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) + private val locationRequest: LocationRequest = LocationRequest().apply { + // Sets the desired interval for active location updates. This interval is inexact. You + // may not receive updates at all if no location sources are available, or you may + // receive them slower than requested. You may also receive updates faster than + // requested if other applications are requesting location at a faster interval. + // + // IMPORTANT NOTE: Apps running on "O" devices (regardless of targetSdkVersion) may + // receive updates less frequently than this interval when the app is no longer in the + // foreground. + interval = TimeUnit.SECONDS.toMillis(60) + + // Sets the fastest rate for active location updates. This interval is exact, and your + // application will never receive updates faster than this value. + fastestInterval = TimeUnit.SECONDS.toMillis(30) + + // Sets the maximum time when batched location updates are delivered. Updates may be + // delivered sooner than this interval. + maxWaitTime = TimeUnit.MINUTES.toMillis(2) + + priority = LocationRequest.PRIORITY_HIGH_ACCURACY } + /** + * Creates default PendingIntent for location changes. + * + * Note: We use a BroadcastReceiver because on API level 26 and above (Oreo+), Android places + * limits on Services. + */ private val locationUpdatePendingIntent: PendingIntent by lazy { - // API level 26 and above (Oreo+) place limits on Services, so we use a BroadcastReceiver. - val intent = Intent (context, LocationUpdatesBroadcastReceiver::class.java) + val intent = Intent(context, LocationUpdatesBroadcastReceiver::class.java) intent.action = LocationUpdatesBroadcastReceiver.ACTION_PROCESS_UPDATES PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } + /** + * Uses the FusedLocationProvider to start tracking location if the correct fine locations are + * approved. + * + * @throws SecurityException if ACCESS_FINE_LOCATION permission is removed before the + * FusedLocationClient's requestLocationUpdates() has been completed. + */ + @Throws(SecurityException::class) + @MainThread fun startLocationUpdates() { Log.d(TAG, "startLocationUpdates()") if (!context.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) return try { - trackingLocation.value = true + _trackingLocation.value = true + // If the PendingIntent is the same as the last request (which it always is), this + // request will replace any requestLocationUpdates() called before. fusedLocationClient.requestLocationUpdates(locationRequest, locationUpdatePendingIntent) + } catch (permissionRevoked: SecurityException) { + _trackingLocation.value = false - } catch (e: SecurityException) { - trackingLocation.value = false - e.printStackTrace() + // Exception only occurs if the user revokes the FINE location permission before + // requestLocationUpdates() is finished executing (very rare). + Log.d(TAG, "Location permission revoked; details: $permissionRevoked") + throw permissionRevoked } } + @MainThread fun stopLocationUpdates() { Log.d(TAG, "stopLocationUpdates()") - trackingLocation.value = false + _trackingLocation.value = false fusedLocationClient.removeLocationUpdates(locationUpdatePendingIntent) } @@ -100,7 +124,6 @@ class MyLocationManager private constructor(private val context: Context) { @Volatile private var INSTANCE: MyLocationManager? = null fun getInstance(context: Context): MyLocationManager { - return INSTANCE ?: synchronized(this) { INSTANCE ?: MyLocationManager(context).also { INSTANCE = it } } diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt index 8a368522..5c246ff8 100644 --- a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt @@ -20,11 +20,10 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.Query import androidx.room.Update - import java.util.UUID /** - * Definition of SQL for mapping to database. + * Defines database operations. */ @Dao interface MyLocationDao { @@ -35,11 +34,9 @@ interface MyLocationDao { @Query("SELECT * FROM my_location_table WHERE id=(:id)") fun getLocation(id: UUID): LiveData - @Update fun updateLocation(myLocationEntity: MyLocationEntity) - @Insert fun addLocation(myLocationEntity: MyLocationEntity) } diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt index 8b8e989d..4dfcca57 100644 --- a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt @@ -16,7 +16,6 @@ package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db import android.content.Context - import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase @@ -29,7 +28,7 @@ private const val DATABASE_NAME = "my-location-database" */ @Database(entities = [MyLocationEntity::class], version = 1) @TypeConverters(MyLocationTypeConverters::class) -abstract class MyLocationDatabase: RoomDatabase() { +abstract class MyLocationDatabase : RoomDatabase() { abstract fun locationDao(): MyLocationDao companion object { @@ -37,12 +36,8 @@ abstract class MyLocationDatabase: RoomDatabase() { @Volatile private var INSTANCE: MyLocationDatabase? = null fun getInstance(context: Context): MyLocationDatabase { - return INSTANCE - ?: synchronized(this) { - INSTANCE - ?: buildDatabase( - context - ).also { INSTANCE = it } + return INSTANCE ?: synchronized(this) { + INSTANCE ?: buildDatabase(context).also { INSTANCE = it } } } @@ -51,8 +46,7 @@ abstract class MyLocationDatabase: RoomDatabase() { context, MyLocationDatabase::class.java, DATABASE_NAME - ) - .build() + ).build() } } } diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt index b703591a..1fbfb9dd 100644 --- a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt @@ -17,7 +17,6 @@ package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.d import androidx.room.Entity import androidx.room.PrimaryKey - import java.text.DateFormat import java.util.Date import java.util.UUID @@ -27,16 +26,16 @@ import java.util.UUID * {@link android.location.Location} class). */ @Entity(tableName = "my_location_table") -data class MyLocationEntity (@PrimaryKey val id:UUID = UUID.randomUUID(), - val latitude:Double = 0.0, - val longitude:Double = 0.0, - val foreground:Boolean = true, - val date:Date = Date() - ) { +data class MyLocationEntity( + @PrimaryKey val id: UUID = UUID.randomUUID(), + val latitude: Double = 0.0, + val longitude: Double = 0.0, + val foreground: Boolean = true, + val date: Date = Date() +) { override fun toString(): String { - - val appState = if(foreground) { + val appState = if (foreground) { "in app" } else { "in BG" diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.kt index cddc4822..f4b2f573 100644 --- a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.kt +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.kt @@ -16,7 +16,6 @@ package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin.data.db import androidx.room.TypeConverter - import java.util.Date import java.util.UUID diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/res/values/strings.xml b/LocationUpdatesBackgroundKotlin/app/src/main/res/values/strings.xml index 33243524..5a80ca75 100644 --- a/LocationUpdatesBackgroundKotlin/app/src/main/res/values/strings.xml +++ b/LocationUpdatesBackgroundKotlin/app/src/main/res/values/strings.xml @@ -64,10 +64,4 @@ Batched location updates Please start tracking location! - - - No location reported - One location reported - %d locations reported - From 3a39cb9ee57e5255caf31642547fae0ee3171000 Mon Sep 17 00:00:00 2001 From: Robert77a <62155422+Robert77a@users.noreply.github.com> Date: Mon, 23 Mar 2020 11:02:49 +0430 Subject: [PATCH 5/5] Delete Utils.kt --- .../locationupdatesbackgroundkotlin/Utils.kt | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt diff --git a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt deleted file mode 100644 index 35e68438..00000000 --- a/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/Utils.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2020 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.gms.location.sample.locationupdatesbackgroundkotlin - -import android.Manifest -import android.content.Context -import android.content.pm.PackageManager -import androidx.core.app.ActivityCompat -import androidx.fragment.app.Fragment -import com.google.android.material.snackbar.Snackbar - -/** - * Helper functions to simplify permission checks/requests. - */ -fun Context.hasPermission(permission: String): Boolean { - - // Background permissions didn't exit prior to Q, so it's approved by default. - if (permission == Manifest.permission.ACCESS_BACKGROUND_LOCATION && - android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) { - return true - } - - return ActivityCompat.checkSelfPermission(this, permission) == - PackageManager.PERMISSION_GRANTED -} - -/** - * Requests permission and if the user denied a previous request, but didn't check - * "Don't ask again", we provide additional rationale. - * - * Note: The Snackbar should have an action to request the permission. - */ -fun Fragment.requestPermissionWithRationale( - permission: String, - requestCode: Int, - snackbar: Snackbar -) { - val provideRationale = shouldShowRequestPermissionRationale(permission) - - if (provideRationale) { - snackbar.show() - } else { - requestPermissions(arrayOf(permission), requestCode) - } -}