diff --git a/app/build.gradle b/app/build.gradle index bf821609..77321a97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,8 +37,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 30 // 30 can't work until an explicit location permissions dialog is added - versionCode 20231 // format is Mmmss (where M is 1+the numeric major number - versionName "1.2.31" + versionCode 20240 // format is Mmmss (where M is 1+the numeric major number + versionName "1.2.40" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // per https://developer.android.com/studio/write/vector-asset-studio diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index a083bfdf..6fbdac6f 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -25,6 +25,7 @@ import android.view.View import android.widget.TextView import android.widget.Toast import androidx.activity.viewModels +import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate @@ -41,6 +42,8 @@ import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.android.ServiceClient import com.geeksville.concurrent.handledLaunch +import com.geeksville.mesh.android.getBackgroundPermissions +import com.geeksville.mesh.android.getMissingPermissions import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.databinding.ActivityMainBinding import com.geeksville.mesh.model.ChannelSet @@ -120,7 +123,6 @@ eventually: val utf8 = Charset.forName("UTF-8") - class MainActivity : AppCompatActivity(), Logging, ActivityCompat.OnRequestPermissionsResultCallback { @@ -223,17 +225,9 @@ class MainActivity : AppCompatActivity(), Logging, model.bluetoothEnabled.value = enabled } - /** - * return a list of the permissions we don't have + /** Get the minimum permissions our app needs to run correctly */ - private fun getMissingPermissions(perms: List) = perms.filter { - ContextCompat.checkSelfPermission( - this, - it - ) != PackageManager.PERMISSION_GRANTED - } - - private fun getMissingPermissions(): List { + private fun getMinimumPermissions(): List { val perms = mutableListOf( Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, @@ -245,9 +239,6 @@ class MainActivity : AppCompatActivity(), Logging, // Manifest.permission.WRITE_EXTERNAL_STORAGE ) - if (Build.VERSION.SDK_INT >= 29) // only added later - perms.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION) - // Some old phones complain about requesting perms they don't understand if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { perms.add(Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND) @@ -257,10 +248,16 @@ class MainActivity : AppCompatActivity(), Logging, return getMissingPermissions(perms) } - private fun requestPermission() { - debug("Checking permissions") - val missingPerms = getMissingPermissions() + + /** Ask the user to grant background location permission */ + fun requestBackgroundPermission() = requestPermission(getBackgroundPermissions()) + + /** Possibly prompt user to grant permissions + * + * @return true if we already have the needed permissions + */ + private fun requestPermission(missingPerms: List = getMinimumPermissions()): Boolean = if (missingPerms.isNotEmpty()) { missingPerms.forEach { // Permission is not granted @@ -283,17 +280,20 @@ class MainActivity : AppCompatActivity(), Logging, // DID_REQUEST_PERM is an // app-defined int constant. The callback method gets the // result of the request. + error("Permissions missing, asked user to grant") + false } else { // Permission has already been granted + debug("We have our required permissions") + true } - } - /** * Remind user he's disabled permissions we need * * @return true if we did warn */ + @SuppressLint("InlinedApi") // This function is careful to work with old APIs correctly fun warnMissingPermissions(): Boolean { // Older versions of android don't know about these permissions - ignore failure to grant val ignoredPermissions = setOf( @@ -302,7 +302,7 @@ class MainActivity : AppCompatActivity(), Logging, Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND ) - val deniedPermissions = getMissingPermissions().filter { name -> + val deniedPermissions = getMinimumPermissions().filter { name -> !ignoredPermissions.contains(name) } diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt index 006f59b5..656ed745 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt @@ -1,9 +1,13 @@ package com.geeksville.mesh.android +import android.Manifest import android.app.NotificationManager import android.bluetooth.BluetoothManager import android.content.Context +import android.content.pm.PackageManager import android.hardware.usb.UsbManager +import android.os.Build +import androidx.core.content.ContextCompat /** * @return null on platforms without a BlueTooth driver (i.e. the emulator) @@ -13,3 +17,28 @@ val Context.bluetoothManager: BluetoothManager? get() = getSystemService(Context val Context.usbManager: UsbManager get() = requireNotNull(getSystemService(Context.USB_SERVICE) as? UsbManager?) { "USB_SERVICE is not available"} val Context.notificationManager: NotificationManager get() = requireNotNull(getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager?) + +/** + * return a list of the permissions we don't have + */ +fun Context.getMissingPermissions(perms: List) = perms.filter { + ContextCompat.checkSelfPermission( + this, + it + ) != PackageManager.PERMISSION_GRANTED +} + +/** + * A list of missing background location permissions (or empty if we already have what we need) + */ +fun Context.getBackgroundPermissions(): List { + val perms = mutableListOf() + + if (Build.VERSION.SDK_INT >= 29) // only added later + perms.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION) + + return getMissingPermissions(perms) +} + +/** @return true if the user already has background location permission */ +fun Context.hasBackgroundPermission() = getBackgroundPermissions().isEmpty() \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 981e49f6..33677ea2 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -20,6 +20,7 @@ import com.geeksville.concurrent.handledLaunch import com.geeksville.mesh.* import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.MeshProtos.ToRadio +import com.geeksville.mesh.android.hasBackgroundPermission import com.geeksville.mesh.database.MeshtasticDatabase import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.database.entity.Packet @@ -200,7 +201,7 @@ class MeshService : Service(), Logging { @UiThread private fun startLocationRequests(requestInterval: Long) { // FIXME - currently we don't support location reading without google play - if (fusedLocationClient == null && isGooglePlayAvailable(this)) { + if (fusedLocationClient == null && hasBackgroundPermission() && isGooglePlayAvailable(this)) { GeeksvilleApplication.analytics.track("location_start") // Figure out how many users needed to use the phone GPS locationIntervalMsec = requestInterval diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 429c8461..e5b024ae 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -13,21 +13,7 @@ android:layout_height="0dp" android:layout_marginTop="16dp"> - - - - - - - + + + + + + + + \ No newline at end of file