Move the location updates to a foreground service and fix multiple bugs

This commit is contained in:
Tommaso Berlose 2021-01-03 17:23:22 +01:00
parent ebb37f21ed
commit 66d0214cd9
18 changed files with 169 additions and 47 deletions

Binary file not shown.

View File

@ -16,15 +16,15 @@ apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
android { android {
compileSdkVersion 29 compileSdkVersion 30
buildToolsVersion "29.0.3" buildToolsVersion "29.0.3"
defaultConfig { defaultConfig {
applicationId "com.tommasoberlose.anotherwidget" applicationId "com.tommasoberlose.anotherwidget"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 30
versionCode 110 versionCode 112
versionName "2.0.15" versionName "2.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY']) buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY'])
@ -72,9 +72,9 @@ dependencies {
// UI // UI
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.3.0-alpha03' implementation 'com.google.android.material:material:1.3.0-beta01'
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.3.0'
implementation 'net.idik:slimadapter:2.1.2' implementation 'net.idik:slimadapter:2.1.2'
implementation 'com.google.android:flexbox:2.0.1' implementation 'com.google.android:flexbox:2.0.1'
@ -89,8 +89,8 @@ dependencies {
implementation 'org.greenrobot:eventbus:3.2.0' implementation 'org.greenrobot:eventbus:3.2.0'
// Navigation // Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1' implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
// Other // Other
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
@ -103,7 +103,7 @@ dependencies {
kapt 'com.github.bumptech.glide:compiler:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0'
// Fitness // Fitness
implementation 'com.google.android.gms:play-services-fitness:18.0.0' implementation 'com.google.android.gms:play-services-fitness:20.0.0'
implementation 'com.google.android.gms:play-services-auth:18.1.0' implementation 'com.google.android.gms:play-services-auth:18.1.0'
//Weather //Weather
@ -111,8 +111,8 @@ dependencies {
implementation 'com.google.android.gms:play-services-location:17.1.0' implementation 'com.google.android.gms:play-services-location:17.1.0'
// Billing // Billing
implementation 'com.android.billingclient:billing:3.0.1' implementation 'com.android.billingclient:billing:3.0.2'
implementation 'com.android.billingclient:billing-ktx:3.0.1' implementation 'com.android.billingclient:billing-ktx:3.0.2'
// KTX // KTX
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.3.2"
@ -128,10 +128,10 @@ dependencies {
implementation "com.github.haroldadmin:NetworkResponseAdapter:4.0.1" implementation "com.github.haroldadmin:NetworkResponseAdapter:4.0.1"
//Coroutines //Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
// Add the Firebase SDK for Crashlytics. // Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.2.2' implementation 'com.google.firebase:firebase-crashlytics:17.3.0'
// Preferences // Preferences
implementation 'com.chibatching.kotpref:kotpref:2.11.0' implementation 'com.chibatching.kotpref:kotpref:2.11.0'

View File

@ -5,7 +5,6 @@
<uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.android.vending.BILLING" />
@ -13,6 +12,7 @@
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/> <uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
@ -154,6 +154,14 @@
android:name=".services.UpdateCalendarJob" android:name=".services.UpdateCalendarJob"
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/> android:exported="true"/>
<service
android:name=".services.LocationService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
</application> </application>
<queries>
<package android:name="com.google.android.apps.fitness"/>
</queries>
</manifest> </manifest>

View File

@ -24,6 +24,7 @@ import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.helpers.ColorHelper import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.utils.expand import com.tommasoberlose.anotherwidget.utils.expand
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.reveal import com.tommasoberlose.anotherwidget.utils.reveal
import com.tommasoberlose.anotherwidget.utils.toPixel import com.tommasoberlose.anotherwidget.utils.toPixel
import com.warkiz.widget.IndicatorSeekBar import com.warkiz.widget.IndicatorSeekBar
@ -93,6 +94,7 @@ class BottomSheetColorPicker(
injector injector
.with<MaterialCardView>(R.id.color) { .with<MaterialCardView>(R.id.color) {
it.setCardBackgroundColor(ColorStateList.valueOf(item)) it.setCardBackgroundColor(ColorStateList.valueOf(item))
it.strokeWidth = if ((colors.indexOf(item) == 0 && !context.isDarkTheme()) || (colors.indexOf(item) == 10 && context.isDarkTheme())) 2 else 0
} }
.with<AppCompatImageView>(R.id.check) { .with<AppCompatImageView>(R.id.check) {
if (getSelected?.invoke() == item) { if (getSelected?.invoke() == item) {

View File

@ -10,6 +10,7 @@ import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.services.LocationService
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
@ -31,26 +32,8 @@ object WeatherHelper {
val networkApi = WeatherNetworkApi(context) val networkApi = WeatherNetworkApi(context)
if (Preferences.customLocationAdd != "") { if (Preferences.customLocationAdd != "") {
networkApi.updateWeather() networkApi.updateWeather()
} else if (context.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION)) { } else if (context.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
LocationServices.getFusedLocationProviderClient(context).lastLocation.addOnCompleteListener { task -> LocationService.requestNewLocation(context)
if (task.isSuccessful) {
val location = task.result
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} }
} }

View File

@ -0,0 +1,126 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.app.*
import android.app.job.JobScheduler
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.os.IBinder
import android.util.Log
import androidx.core.app.*
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
import java.util.*
import kotlin.collections.ArrayList
class LocationService : Service() {
private val jobs: ArrayList<Job> = ArrayList()
override fun onCreate() {
super.onCreate()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
jobs += GlobalScope.launch(Dispatchers.IO) {
LocationServices.getFusedLocationProviderClient(this@LocationService).lastLocation.addOnCompleteListener { task ->
val networkApi = WeatherNetworkApi(this@LocationService)
if (task.isSuccessful) {
val location = task.result
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopForeground(true)
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopForeground(true)
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
}
} else {
stopForeground(true)
}
}
override fun onDestroy() {
super.onDestroy()
jobs.forEach {
it.cancel()
}
}
companion object {
const val LOCATION_ACCESS_NOTIFICATION_ID = 28465
@JvmStatic
fun requestNewLocation(context: Context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context.startForegroundService(Intent(context, LocationService::class.java))
} else {
context.startService(Intent(context, LocationService::class.java))
}
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun getLocationAccessNotification(): Notification {
with(NotificationManagerCompat.from(this)) {
// Create channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
createNotificationChannel(
NotificationChannel(
getString(R.string.location_access_notification_channel_id),
getString(R.string.location_access_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.location_access_notification_channel_description)
}
)
}
val builder = NotificationCompat.Builder(this@LocationService, getString(R.string.location_access_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(getString(R.string.location_access_notification_title))
.setStyle(NotificationCompat.BigTextStyle().bigText(getString(R.string.location_access_notification_subtitle)))
.setOngoing(true)
.setColor(ContextCompat.getColor(this@LocationService, R.color.colorAccent))
// Main intent that open the activity
builder.setContentIntent(PendingIntent.getActivity(this@LocationService, 0, Intent(this@LocationService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
return builder.build()
}
}
}

View File

@ -140,7 +140,7 @@ class CustomLocationActivity : AppCompatActivity() {
private fun requirePermission() { private fun requirePermission() {
Dexter.withContext(this) Dexter.withContext(this)
.withPermissions( .withPermissions(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener { ).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) { override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let { report?.let {

View File

@ -351,7 +351,7 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
}?.isVisible = if (Preferences.showWeather) { }?.isVisible = if (Preferences.showWeather) {
(WeatherHelper.isKeyRequired() && WeatherHelper.getApiKey() == "") (WeatherHelper.isKeyRequired() && WeatherHelper.getApiKey() == "")
|| (Preferences.customLocationAdd == "" && activity?.checkGrantedPermission( || (Preferences.customLocationAdd == "" && activity?.checkGrantedPermission(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
) != true) ) != true)
|| (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") || (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-")
|| (Preferences.weatherProviderLocationError != "") || (Preferences.weatherProviderLocationError != "")

View File

@ -161,12 +161,7 @@ class WeatherTabFragment : Fragment() {
} }
private fun checkLocationPermission() { private fun checkLocationPermission() {
// Background permission if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && activity?.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) == true && activity?.checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) != true) {
requirePermission()
}
if (activity?.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION) == true) {
location_permission_alert?.isVisible = false location_permission_alert?.isVisible = false
background_location_warning.isVisible = Preferences.customLocationAdd == "" background_location_warning.isVisible = Preferences.customLocationAdd == ""
WeatherReceiver.setUpdates(requireContext()) WeatherReceiver.setUpdates(requireContext())
@ -299,7 +294,7 @@ class WeatherTabFragment : Fragment() {
private fun requirePermission() { private fun requirePermission() {
Dexter.withContext(requireContext()) Dexter.withContext(requireContext())
.withPermissions( .withPermissions(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener { ).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) { override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let { report?.let {

View File

@ -21,6 +21,7 @@ import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.ViewPropertyAnimator import android.view.ViewPropertyAnimator
import android.view.animation.Animation import android.view.animation.Animation

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -13,6 +13,7 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
app:cardCornerRadius="24dp" app:cardCornerRadius="24dp"
app:strokeColor="@color/disabledButtonBackground"
app:cardElevation="0dp" app:cardElevation="0dp"
android:id="@+id/color" android:id="@+id/color"
app:cardBackgroundColor="@color/colorNightDark"> app:cardBackgroundColor="@color/colorNightDark">

View File

@ -161,6 +161,12 @@
<string name="weather_select_a_provider">Select a provider</string> <string name="weather_select_a_provider">Select a provider</string>
<string name="weather_provider_activity_subtitle">Select a weather provider from the list.\nA few providers need a free personal account,\nbut they are usually more accurate.</string> <string name="weather_provider_activity_subtitle">Select a weather provider from the list.\nA few providers need a free personal account,\nbut they are usually more accurate.</string>
<string name="location_access_notification_channel_id" translatable="false">location-access</string>
<string name="location_access_notification_channel_name">Background service</string>
<string name="location_access_notification_channel_description">Service used to update the weather based on the current location of the user.</string>
<string name="location_access_notification_title">Weather update</string>
<string name="location_access_notification_subtitle">We\'re updating the weather based on your current location.</string>
<!-- Clock --> <!-- Clock -->
<string name="settings_clock_title">Clock</string> <string name="settings_clock_title">Clock</string>
<string name="settings_clock_app_title">Tap on clock opens</string> <string name="settings_clock_app_title">Tap on clock opens</string>

View File

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.4.10' ext.kotlin_version = '1.4.21'
repositories { repositories {
google() google()
jcenter() jcenter()
@ -10,12 +10,12 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.4' classpath 'com.google.gms:google-services:4.3.4'
// Add the Crashlytics Gradle plugin. // Add the Crashlytics Gradle plugin.
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
classpath 'io.realm:realm-gradle-plugin:6.0.2' classpath 'io.realm:realm-gradle-plugin:6.0.2'