Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cab53042 authored by Jonathan Klee's avatar Jonathan Klee
Browse files

Merge branch '0000-a15-runtime-permissions' into 'main'

feat: ask required runtime permission when adding new account

See merge request !201
parents 6bb2b58a 1090105c
Loading
Loading
Loading
Loading
Loading
+17 −11
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.MainThread
import androidx.core.content.ContextCompat
@@ -25,31 +26,42 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.PackageChangedReceiver
import at.bitfire.davdroid.R
import at.bitfire.davdroid.databinding.ActivityPermissionsBinding
import at.bitfire.davdroid.util.PermissionUtils
import at.bitfire.davdroid.util.PermissionUtils.CALENDAR_PERMISSIONS
import at.bitfire.davdroid.util.PermissionUtils.CONTACT_PERMISSIONS
import at.bitfire.davdroid.util.PermissionUtils.havePermissions
import at.bitfire.davdroid.R
import at.bitfire.davdroid.databinding.ActivityPermissionsBinding
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.ProviderName

class PermissionsFragment: Fragment() {

    val model by viewModels<Model>()

    private lateinit var requestPermission: ActivityResultLauncher<Array<String>>

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        val binding = ActivityPermissionsBinding.inflate(inflater, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        binding.model = model

        binding.text.text = getString(R.string.permissions_text, getString(R.string.app_name))

        val requestPermission = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
        initPermissionLauncher()
        observePermissionToggles()
        binding.appSettings.setOnClickListener {
            PermissionUtils.showAppSettings(requireActivity())
        }

        return binding.root
    }

    private fun initPermissionLauncher() {
        requestPermission = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
            model.checkPermissions()
        }
    }

    private fun observePermissionToggles() {
        model.needAutoResetPermission.observe(viewLifecycleOwner) { keepPermissions ->
            if (keepPermissions == true && model.haveAutoResetPermission.value == false) {
                Toast.makeText(requireActivity(), R.string.permissions_autoreset_instruction, Toast.LENGTH_LONG).show()
@@ -95,12 +107,6 @@ class PermissionsFragment: Fragment() {
                requestPermission.launch(all.toTypedArray())
            }
        }

        binding.appSettings.setOnClickListener {
            PermissionUtils.showAppSettings(requireActivity())
        }

        return binding.root
    }

    override fun onResume() {
+6 −12
Original line number Diff line number Diff line
@@ -10,11 +10,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import at.bitfire.davdroid.util.PermissionUtils
import at.bitfire.davdroid.util.PermissionUtils.CALENDAR_PERMISSIONS
import at.bitfire.davdroid.util.PermissionUtils.CONTACT_PERMISSIONS
import at.bitfire.davdroid.R
import at.bitfire.ical4android.TaskProvider
import at.bitfire.davdroid.util.PermissionUtils
import javax.inject.Inject

class PermissionsIntroFragment : Fragment() {
@@ -26,16 +23,13 @@ class PermissionsIntroFragment : Fragment() {
    class Factory @Inject constructor(): IntroFragmentFactory {

        override fun getOrder(context: Context): Int {
            // show PermissionsFragment as intro fragment when no permissions are granted
            val permissions = CONTACT_PERMISSIONS + CALENDAR_PERMISSIONS +
                    TaskProvider.PERMISSIONS_JTX +
                    TaskProvider.PERMISSIONS_OPENTASKS +
                    TaskProvider.PERMISSIONS_TASKS_ORG
            return if (PermissionUtils.haveAnyPermission(context, permissions))
            val permissions = PermissionUtils.requiredAccountPermissions(context)
            return if (PermissionUtils.havePermissions(context, permissions)) {
                IntroFragmentFactory.DONT_SHOW
            else
            } else {
                50
            }
        }

        override fun create() = PermissionsIntroFragment()

+10 −1
Original line number Diff line number Diff line
@@ -51,7 +51,9 @@ import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.syncadapter.AccountUtils
import at.bitfire.davdroid.syncadapter.SyncAllAccountWorker
import at.bitfire.davdroid.syncadapter.SyncWorker
import at.bitfire.davdroid.ui.PermissionsActivity
import at.bitfire.davdroid.util.AuthStatePrefUtils
import at.bitfire.davdroid.util.PermissionUtils
import at.bitfire.vcard4android.GroupMethod
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.utils.AccountManagerUtils
@@ -126,7 +128,7 @@ class AccountDetailsFragment : Fragment() {
                    GroupMethod.valueOf(groupMethodName),
                ).observe(viewLifecycleOwner, Observer { success ->
                    if (success) {
                        // close Create account activity
                        requestMissingPermissionsIfNeeded()
                        requireActivity().finish()
                    } else {
                        Snackbar.make(requireActivity().findViewById(android.R.id.content), R.string.login_account_not_created, Snackbar.LENGTH_LONG).show()
@@ -186,6 +188,7 @@ class AccountDetailsFragment : Fragment() {
                    if (success) {
                        Toast.makeText(context, R.string.message_account_added_successfully, Toast.LENGTH_LONG).show()
                        requireActivity().setResult(Activity.RESULT_OK)
                        requestMissingPermissionsIfNeeded()
                        requireActivity().finish()

                        if (requireActivity().intent.hasExtra(AccountManager
@@ -216,6 +219,12 @@ class AccountDetailsFragment : Fragment() {
        return v.root
    }

    private fun requestMissingPermissionsIfNeeded() {
        if (PermissionUtils.collectMissingRequiredPermissions(requireContext()).isNotEmpty()) {
            startActivity(Intent(requireActivity(), PermissionsActivity::class.java))
        }
    }

    private fun findBasicAuthMurenaAccount(accountName: String, accountType: String?): Account? {
        val requiredAccountType = requireContext().getString(R.string.eelo_account_type)
        if (accountType != requiredAccountType) {
+27 −1
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.ui.NotificationUtils
import at.bitfire.davdroid.ui.NotificationUtils.notifyIfPossible
import at.bitfire.davdroid.ui.PermissionsActivity
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.ProviderName

object PermissionUtils {

@@ -104,6 +106,30 @@ object PermissionUtils {
    fun havePermissions(context: Context, permissions: Array<String>) =
            permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED }

    fun requiredAccountPermissions(context: Context): Array<String> {
        val requiredPermissions = mutableSetOf(*CONTACT_PERMISSIONS, *CALENDAR_PERMISSIONS)
        val packageManager = context.packageManager

        if (packageManager.resolveContentProvider(ProviderName.OpenTasks.authority, 0) != null) {
            requiredPermissions.addAll(TaskProvider.PERMISSIONS_OPENTASKS)
        }
        if (packageManager.resolveContentProvider(ProviderName.TasksOrg.authority, 0) != null) {
            requiredPermissions.addAll(TaskProvider.PERMISSIONS_TASKS_ORG)
        }
        if (packageManager.resolveContentProvider(ProviderName.JtxBoard.authority, 0) != null) {
            requiredPermissions.addAll(TaskProvider.PERMISSIONS_JTX)
        }

        return requiredPermissions.toTypedArray()
    }

    fun collectMissingRequiredPermissions(context: Context): List<String> {
        val required = requiredAccountPermissions(context)
        return required.filter {
            ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED
        }
    }

    /**
     * Shows a notification about missing permissions.
     *