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..9a9c25da --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/LocationRepository.kt @@ -0,0 +1,103 @@ +/* + * 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.annotation.MainThread +import androidx.lifecycle.LiveData +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" + +/** + * 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, + private val executor:ExecutorService +) { + + // Database related fields/methods: + private val locationDao = myLocationDatabase.locationDao() + + /** + * 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) + } + } + + // 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 { + @Volatile private var INSTANCE: LocationRepository? = null + + fun getInstance(context: Context, executor: ExecutorService): LocationRepository { + return INSTANCE ?: synchronized(this) { + INSTANCE ?: LocationRepository( + MyLocationDatabase.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 new file mode 100644 index 00000000..8ec33738 --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt @@ -0,0 +1,132 @@ +/* + * 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.annotation.MainThread +import androidx.lifecycle.LiveData +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) { + + 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 = + LocationServices.getFusedLocationProviderClient(context) + + // Stores parameters for requests to the FusedLocationProviderApi. + 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 { + 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 + // 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 + + // 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 + 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..5c246ff8 --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDao.kt @@ -0,0 +1,42 @@ +/* + * 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 + +/** + * Defines database operations. + */ +@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..4dfcca57 --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationDatabase.kt @@ -0,0 +1,52 @@ +/* + * 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..1fbfb9dd --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationEntity.kt @@ -0,0 +1,47 @@ +/* + * 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..f4b2f573 --- /dev/null +++ b/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/db/MyLocationTypeConverters.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.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) + } +} 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 - 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 { 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.