From 44b9a1a7b25b975ebbf40977067301cdabc921f2 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Sat, 27 Jul 2024 13:56:40 +0530 Subject: [PATCH] Redo Permissions [1/2] --- .../java/com/aurora/extensions/Context.kt | 33 ++- .../java/com/aurora/store/PermissionType.kt | 10 + .../com/aurora/store/data/model/Permission.kt | 13 +- .../data/providers/PermissionProvider.kt | 122 ++++++++ .../com/aurora/store/util/PermissionUtil.kt | 13 - .../store/view/epoxy/views/TextDividerView.kt | 42 +++ .../view/ui/details/AppDetailsFragment.kt | 60 ++-- .../view/ui/onboarding/AppLinksFragment.kt | 93 ------ .../view/ui/onboarding/OnboardingFragment.kt | 11 +- .../view/ui/onboarding/PermissionsFragment.kt | 272 +++++++----------- .../store/view/ui/updates/UpdatesFragment.kt | 56 +--- .../main/res/layout/fragment_app_links.xml | 98 +------ app/src/main/res/layout/view_permission.xml | 22 +- app/src/main/res/layout/view_text_divider.xml | 35 +++ app/src/main/res/values/strings.xml | 2 + 15 files changed, 407 insertions(+), 475 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/PermissionType.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/PermissionProvider.kt delete mode 100644 app/src/main/java/com/aurora/store/util/PermissionUtil.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/TextDividerView.kt create mode 100644 app/src/main/res/layout/view_text_divider.xml diff --git a/app/src/main/java/com/aurora/extensions/Context.kt b/app/src/main/java/com/aurora/extensions/Context.kt index d33456e18..7d7b27db6 100644 --- a/app/src/main/java/com/aurora/extensions/Context.kt +++ b/app/src/main/java/com/aurora/extensions/Context.kt @@ -19,17 +19,23 @@ package com.aurora.extensions +import android.Manifest import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationUserState import android.graphics.Color import android.net.Uri import android.os.Bundle +import android.os.Environment import android.os.PowerManager import android.util.TypedValue import android.view.LayoutInflater import androidx.browser.customtabs.CustomTabsIntent +import androidx.core.app.ActivityCompat import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat import com.aurora.Constants @@ -131,7 +137,9 @@ fun Context.accentColor(): Int { fun Context.isIgnoringBatteryOptimizations(): Boolean { return if (isMAndAbove()) { - (getSystemService(Context.POWER_SERVICE) as PowerManager).isIgnoringBatteryOptimizations(packageName) + (getSystemService(Context.POWER_SERVICE) as PowerManager).isIgnoringBatteryOptimizations( + packageName + ) } else { true } @@ -148,3 +156,26 @@ fun Context.restartApp() { exitProcess(0) } + +fun Context.checkManifestPermission(permission: String): Boolean { + return ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED +} + +fun Context.isExternalStorageAccessible(): Boolean { + return if (isRAndAbove()) { + Environment.isExternalStorageManager() + } else { + checkManifestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } +} + +fun Context.isDomainVerified(domain: String): Boolean { + return if (isSAndAbove()) { + val domainVerificationManager = getSystemService(DomainVerificationManager::class.java) + val userState = domainVerificationManager.getDomainVerificationUserState(packageName) + val domainMap = userState?.hostToStateMap?.filterKeys { it == domain } + domainMap?.values?.first() == DomainVerificationUserState.DOMAIN_STATE_SELECTED + } else { + true + } +} diff --git a/app/src/main/java/com/aurora/store/PermissionType.kt b/app/src/main/java/com/aurora/store/PermissionType.kt new file mode 100644 index 000000000..c2c1f2cc6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/PermissionType.kt @@ -0,0 +1,10 @@ +package com.aurora.store + +enum class PermissionType { + EXTERNAL_STORAGE, + STORAGE_MANAGER, + INSTALL_UNKNOWN_APPS, + POST_NOTIFICATIONS, + DOZE_WHITELIST, + APP_LINKS +} diff --git a/app/src/main/java/com/aurora/store/data/model/Permission.kt b/app/src/main/java/com/aurora/store/data/model/Permission.kt index ff00f29ed..f6260b92d 100644 --- a/app/src/main/java/com/aurora/store/data/model/Permission.kt +++ b/app/src/main/java/com/aurora/store/data/model/Permission.kt @@ -19,19 +19,22 @@ package com.aurora.store.data.model +import com.aurora.store.PermissionType + data class Permission( - var id: Int, - var title: String, - var subtitle: String + val type: PermissionType, + val title: String, + val subtitle: String, + val optional: Boolean = false, ) { override fun equals(other: Any?): Boolean { return when (other) { - is Permission -> other.id == id + is Permission -> other.type == type else -> false } } override fun hashCode(): Int { - return id.hashCode() + return type.hashCode() } } diff --git a/app/src/main/java/com/aurora/store/data/providers/PermissionProvider.kt b/app/src/main/java/com/aurora/store/data/providers/PermissionProvider.kt new file mode 100644 index 000000000..2658d644b --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/PermissionProvider.kt @@ -0,0 +1,122 @@ +package com.aurora.store.data.providers + +import android.Manifest +import android.annotation.SuppressLint +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.PowerManager +import android.provider.Settings +import android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultCallback +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import com.aurora.extensions.checkManifestPermission +import com.aurora.extensions.isDomainVerified +import com.aurora.extensions.isExternalStorageAccessible +import com.aurora.extensions.isMAndAbove +import com.aurora.extensions.isOAndAbove +import com.aurora.store.BuildConfig +import com.aurora.store.PermissionType +import com.aurora.store.util.Log +import com.aurora.store.util.PackageUtil + +@SuppressLint("NewApi") +class PermissionProvider : ActivityResultCallback { + private var context: Context + private var intentLauncher: ActivityResultLauncher + private var permissionLauncher: ActivityResultLauncher + + constructor(activity: AppCompatActivity) { + context = activity + intentLauncher = activity.registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + this + ) + permissionLauncher = + activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) {} + } + + constructor(fragment: Fragment) { + context = fragment.requireContext() + intentLauncher = fragment.registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + this + ) + permissionLauncher = + fragment.registerForActivityResult(ActivityResultContracts.RequestPermission()) {} + } + + fun unregister() { + intentLauncher.unregister() + permissionLauncher.unregister() + } + + private val permissionMap: Map = mapOf( + PermissionType.STORAGE_MANAGER to PackageUtil.getStorageManagerIntent(safe = true), + PermissionType.INSTALL_UNKNOWN_APPS to Intent( + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, + Uri.parse("package:${BuildConfig.APPLICATION_ID}") + ), + PermissionType.DOZE_WHITELIST to Intent( + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:${BuildConfig.APPLICATION_ID}") + ), + PermissionType.APP_LINKS to Intent( + ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, + Uri.parse("package:${BuildConfig.APPLICATION_ID}") + ) + ) + + fun requestPermission(permissionType: PermissionType) { + try { + when (permissionType) { + PermissionType.EXTERNAL_STORAGE -> permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) + PermissionType.POST_NOTIFICATIONS -> permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + else -> { + val intent = permissionMap[permissionType] ?: return + intentLauncher.launch(intent) + } + } + } catch (e: ActivityNotFoundException) { + Log.e("PermissionProvider", "Activity not found for $permissionType") + } + } + + fun isPermissionGranted(permissionType: PermissionType): Boolean { + return when (permissionType) { + PermissionType.EXTERNAL_STORAGE, + PermissionType.STORAGE_MANAGER -> context.isExternalStorageAccessible() + + PermissionType.POST_NOTIFICATIONS -> context.checkManifestPermission(Manifest.permission.POST_NOTIFICATIONS) + PermissionType.INSTALL_UNKNOWN_APPS -> { + if (isOAndAbove()) + context.packageManager.canRequestPackageInstalls() + else + true + } + + PermissionType.DOZE_WHITELIST -> { + if (isMAndAbove()) { + val powerManager = + context.getSystemService(Context.POWER_SERVICE) as PowerManager + powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID) + } else { + true + } + } + + PermissionType.APP_LINKS -> context.isDomainVerified("play.google.com") || context.isDomainVerified( + "market.android.com" + ) + } + } + + override fun onActivityResult(result: ActivityResult) { + // We don't need to do anything here, as we anyway do permission re-checks using @isPermissionGranted + } +} diff --git a/app/src/main/java/com/aurora/store/util/PermissionUtil.kt b/app/src/main/java/com/aurora/store/util/PermissionUtil.kt deleted file mode 100644 index c42c156fd..000000000 --- a/app/src/main/java/com/aurora/store/util/PermissionUtil.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.aurora.store.util - -import android.Manifest -import android.content.Context -import android.content.pm.PackageManager -import androidx.core.app.ActivityCompat - -fun isExternalStorageAccessible(context: Context): Boolean { - return ActivityCompat.checkSelfPermission( - context, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED -} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/TextDividerView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/TextDividerView.kt new file mode 100644 index 000000000..1317b5465 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/TextDividerView.kt @@ -0,0 +1,42 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.databinding.ViewTextDividerBinding + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseModel::class +) +class TextDividerView @JvmOverloads constructor( + context: Context?, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : BaseView(context, attrs, defStyleAttr) { + + @ModelProp + fun title(title: String) { + binding.txtTitle.text = title + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt index 71119408f..cf5f82235 100644 --- a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt @@ -20,19 +20,15 @@ package com.aurora.store.view.ui.details -import android.Manifest import android.content.ActivityNotFoundException import android.content.Intent -import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle -import android.os.Environment import android.provider.Settings import android.view.View import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat import androidx.fragment.app.activityViewModels @@ -46,7 +42,6 @@ import com.aurora.Constants.EXODUS_SUBMIT_PAGE import com.aurora.extensions.browse import com.aurora.extensions.getString import com.aurora.extensions.hide -import com.aurora.extensions.isRAndAbove import com.aurora.extensions.runOnUiThread import com.aurora.extensions.share import com.aurora.extensions.show @@ -57,6 +52,7 @@ import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.store.AppStreamStash import com.aurora.store.AuroraApp +import com.aurora.store.PermissionType import com.aurora.store.R import com.aurora.store.data.event.BusEvent import com.aurora.store.data.event.Event @@ -67,6 +63,7 @@ import com.aurora.store.data.model.State import com.aurora.store.data.model.ViewState import com.aurora.store.data.model.ViewState.Loading.getDataAs import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.data.providers.PermissionProvider import com.aurora.store.databinding.FragmentDetailsBinding import com.aurora.store.util.CertUtil import com.aurora.store.util.CommonUtil @@ -103,26 +100,9 @@ class AppDetailsFragment : BaseFragment() { @Inject lateinit var authProvider: AuthProvider - private lateinit var bottomSheetBehavior: BottomSheetBehavior - private val startForStorageManagerResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (isRAndAbove() && Environment.isExternalStorageManager()) { - viewModel.download(app) - } else { - flip(0) - toast(R.string.permissions_denied) - } - } - private val startForPermissions = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { - if (it) { - viewModel.download(app) - } else { - flip(0) - toast(R.string.permissions_denied) - } - } + private lateinit var permissionProvider: PermissionProvider + private lateinit var bottomSheetBehavior: BottomSheetBehavior private lateinit var app: App @@ -192,6 +172,11 @@ class AppDetailsFragment : BaseFragment() { } } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + permissionProvider = PermissionProvider(this) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -378,6 +363,11 @@ class AppDetailsFragment : BaseFragment() { super.onResume() } + override fun onDestroy() { + permissionProvider.unregister() + super.onDestroy() + } + private fun attachActions() { flip(0) checkAndSetupInstall() @@ -560,26 +550,10 @@ class AppDetailsFragment : BaseFragment() { updateActionState(State.PROGRESS) if (PathUtil.needsStorageManagerPerm(app.fileList)) { - if (isRAndAbove()) { - if (!Environment.isExternalStorageManager()) { - try { - startForStorageManagerResult.launch(PackageUtil.getStorageManagerIntent()) - } catch (_: ActivityNotFoundException) { - startForStorageManagerResult.launch(PackageUtil.getStorageManagerIntent(true)) - } - } else { - viewModel.download(app) - } + if (permissionProvider.isPermissionGranted(PermissionType.STORAGE_MANAGER)) { + viewModel.download(app) } else { - if (ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED - ) { - viewModel.download(app) - } else { - startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } + permissionProvider.requestPermission(PermissionType.STORAGE_MANAGER) } } else { viewModel.download(app) diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/AppLinksFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/AppLinksFragment.kt index e1248cefe..13bbc6cf8 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/AppLinksFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/AppLinksFragment.kt @@ -1,108 +1,15 @@ package com.aurora.store.view.ui.onboarding -import android.content.Intent -import android.content.pm.verify.domain.DomainVerificationManager -import android.content.pm.verify.domain.DomainVerificationUserState -import android.net.Uri import android.os.Bundle -import android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS -import android.util.Log import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import com.aurora.extensions.isSAndAbove -import com.aurora.extensions.toast -import com.aurora.store.R import com.aurora.store.databinding.FragmentAppLinksBinding import com.aurora.store.view.ui.commons.BaseFragment -import com.google.android.material.button.MaterialButton import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class AppLinksFragment : BaseFragment() { - private val TAG = AppLinksFragment::class.java.simpleName - private val playStoreDomain = "play.google.com" - private val marketDomain = "market.android.com" - - // AppLink buttons - private lateinit var buttons: Map - - private val startForResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (isSAndAbove() && buttons.keys.any { domainVerified(it) }) { - toast(R.string.app_link_enabled) - } - updateButtonState() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - buttons = mapOf( - playStoreDomain to binding.playStoreButton, - marketDomain to binding.marketButton, - ) - - updateButtonState() - if (isSAndAbove()) { - buttons.values.forEach { - it.setOnClickListener { - try { - val intent = Intent( - ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, - Uri.parse("package:${view.context.packageName}") - ) - startForResult.launch(intent) - } catch (exception: Exception) { - Log.e(TAG, "Failed to open app links screen", exception) - toast(R.string.failed_app_link) - } - } - } - } } - - override fun onDestroyView() { - buttons = emptyMap() - super.onDestroyView() - } - - private fun updateButtonState() { - if (isSAndAbove()) { - buttons.forEach { (domain, button) -> - button.apply { - text = if (domainVerified(domain)) { - getString(R.string.action_enabled) - } else { - getString(R.string.action_enable) - } - isEnabled = !domainVerified(domain) - } - } - } else { - buttons.forEach { (_, button) -> - button.apply { - text = getString(R.string.action_enabled) - isEnabled = false - } - } - } - } - - private fun domainVerified(domain: String): Boolean { - return if (isSAndAbove()) { - val domainVerificationManager = requireContext().getSystemService( - DomainVerificationManager::class.java - ) - val userState = domainVerificationManager.getDomainVerificationUserState( - requireContext().packageName - ) - - val domainMap = userState?.hostToStateMap?.filterKeys { it == domain } - domainMap?.values?.first() == DomainVerificationUserState.DOMAIN_STATE_SELECTED - } else { - true - } - } - } diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt index d69034c90..322b8b189 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt @@ -76,7 +76,7 @@ class OnboardingFragment : BaseFragment() { } override fun getItemCount(): Int { - return 3 + return 2 } } @@ -122,9 +122,9 @@ class OnboardingFragment : BaseFragment() { fun refreshButtonState() { binding.btnBackward.isEnabled = lastPosition != 0 - binding.btnForward.isEnabled = lastPosition != 2 + binding.btnForward.isEnabled = lastPosition != 1 - if (lastPosition == 2) { + if (lastPosition == 1) { binding.btnForward.text = getString(R.string.action_finish) binding.btnForward.isEnabled = true binding.btnForward.setOnClickListener { finishOnboarding() } @@ -177,7 +177,10 @@ class OnboardingFragment : BaseFragment() { } private fun setupAutoUpdates() { - save(PREFERENCE_UPDATES_AUTO, if (requireContext().isIgnoringBatteryOptimizations()) 2 else 1) + save( + PREFERENCE_UPDATES_AUTO, + if (requireContext().isIgnoringBatteryOptimizations()) 2 else 1 + ) UpdateWorker.scheduleAutomatedCheck(requireContext()) } } diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/PermissionsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/PermissionsFragment.kt index 0f7408d59..f87bcf1e5 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/PermissionsFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/PermissionsFragment.kt @@ -20,223 +20,143 @@ package com.aurora.store.view.ui.onboarding -import android.Manifest -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri import android.os.Bundle -import android.os.Environment -import android.os.PowerManager -import android.provider.Settings import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.app.ActivityCompat import com.aurora.extensions.isMAndAbove -import com.aurora.extensions.isOAndAbove import com.aurora.extensions.isRAndAbove +import com.aurora.extensions.isSAndAbove import com.aurora.extensions.isTAndAbove -import com.aurora.extensions.toast -import com.aurora.store.BuildConfig +import com.aurora.store.PermissionType import com.aurora.store.R import com.aurora.store.data.model.Permission +import com.aurora.store.data.providers.PermissionProvider import com.aurora.store.databinding.FragmentOnboardingPermissionsBinding -import com.aurora.store.util.PackageUtil -import com.aurora.store.util.isExternalStorageAccessible +import com.aurora.store.view.epoxy.views.TextDividerViewModel_ import com.aurora.store.view.epoxy.views.preference.PermissionViewModel_ import com.aurora.store.view.ui.commons.BaseFragment import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class PermissionsFragment : BaseFragment() { + private lateinit var permissionProvider: PermissionProvider - private val startForDozeResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - context?.let { - val powerManager = it.getSystemService(Context.POWER_SERVICE) as PowerManager - if (isMAndAbove() && powerManager.isIgnoringBatteryOptimizations(it.packageName)) { - toast(R.string.toast_permission_granted) - binding.epoxyRecycler.requestModelBuild() - } - } - } - private val startForPackageManagerResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (isOAndAbove() && requireContext().packageManager.canRequestPackageInstalls()) { - toast(R.string.toast_permission_granted) - binding.epoxyRecycler.requestModelBuild() - } - } - private val startForStorageManagerResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (isRAndAbove() && Environment.isExternalStorageManager()) { - toast(R.string.toast_permission_granted) - binding.epoxyRecycler.requestModelBuild() - } - } - private val startForPermissions = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { - if (it) { - toast(R.string.toast_permission_granted) - binding.epoxyRecycler.requestModelBuild() - } else { - toast(R.string.permissions_denied) - } - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + permissionProvider = PermissionProvider(this) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + updateController() + } - // RecyclerView - val installerList = mutableListOf( + override fun onResume() { + super.onResume() + updateController() + } + + override fun onDestroy() { + permissionProvider.unregister() + super.onDestroy() + } + + private fun permissionList(): List { + val permissions = mutableListOf( Permission( - 2, + PermissionType.INSTALL_UNKNOWN_APPS, getString(R.string.onboarding_permission_installer), getString(R.string.onboarding_permission_installer_desc) ) ) - if (isMAndAbove()) { - installerList.add( - Permission( - 4, - getString(R.string.onboarding_permission_doze), - getString(R.string.onboarding_permission_doze_desc) - ) - ) - } - if (isRAndAbove()) { - installerList.add( + permissions.add( Permission( - 1, + PermissionType.STORAGE_MANAGER, getString(R.string.onboarding_permission_esm), - getString(R.string.onboarding_permission_esa_desc) + getString(R.string.onboarding_permission_esa_desc), + false ) ) } else { - installerList.add( + permissions.add( Permission( - 0, + PermissionType.EXTERNAL_STORAGE, getString(R.string.onboarding_permission_esa), - getString(R.string.onboarding_permission_esa_desc) + getString(R.string.onboarding_permission_esa_desc), + false ) ) } - if (isTAndAbove()) { - installerList.add( - Permission( - 3, - getString(R.string.onboarding_permission_notifications), - getString(R.string.onboarding_permission_notifications_desc) - ) - ) - } - - binding.epoxyRecycler.withModels { - val dozeDisabled = if (isMAndAbove()) { - val powerManager = context?.getSystemService(Context.POWER_SERVICE) as PowerManager - powerManager.isIgnoringBatteryOptimizations(requireContext().packageName) - } else { - true - } - - val writeExternalStorage = - if (!isRAndAbove()) isExternalStorageAccessible(requireContext()) else true - - val postNotifications = if (isTAndAbove()) ActivityCompat.checkSelfPermission( - requireContext(), - Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED else true - - val storageManager = if (isRAndAbove()) Environment.isExternalStorageManager() else true - - setFilterDuplicates(true) - installerList.forEach { - add( - PermissionViewModel_() - .id(it.id) - .permission(it) - .isGranted( - when (it.id) { - 0 -> writeExternalStorage - 1 -> storageManager - 2 -> canInstallPackages() - 3 -> postNotifications - 4 -> dozeDisabled - else -> false - } - ) - .click { _ -> - when (it.id) { - 0 -> checkStorageAccessPermission() - 1 -> requestStorageManagerPermission() - 2 -> requestPackageManagerPermission() - 3 -> checkPostNotificationsPermission() - 4 -> requestDozePermission() - } - } - ) - } - } - } - - private fun canInstallPackages(): Boolean { - if (isOAndAbove()) { - return requireContext().packageManager.canRequestPackageInstalls() - } else { - return true - } - } - - private fun requestDozePermission() { if (isMAndAbove()) { - val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { - data = Uri.parse("package:${requireContext().packageName}") - } - startForDozeResult.launch(intent) - } - } - - private fun checkStorageAccessPermission() { - if (canInstallPackages()) { - startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else { - toast(R.string.toast_permission_installer_required) - } - } - - private fun checkPostNotificationsPermission() { - if (isTAndAbove()) { - startForPermissions.launch(Manifest.permission.POST_NOTIFICATIONS) - } - } - - private fun requestStorageManagerPermission() { - if (isRAndAbove()) { - if (canInstallPackages()) { - try { - startForStorageManagerResult.launch(PackageUtil.getStorageManagerIntent()) - } catch (_: ActivityNotFoundException) { - startForStorageManagerResult.launch(PackageUtil.getStorageManagerIntent(true)) - } - } else { - toast(R.string.toast_permission_installer_required) - } - } - } - - private fun requestPackageManagerPermission() { - if (isOAndAbove()) { - startForPackageManagerResult.launch( - Intent( - Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, - Uri.parse("package:${BuildConfig.APPLICATION_ID}") + permissions.add( + Permission( + PermissionType.DOZE_WHITELIST, + getString(R.string.onboarding_permission_doze), + getString(R.string.onboarding_permission_doze_desc), + true ) ) } + + if (isTAndAbove()) { + permissions.add( + Permission( + PermissionType.POST_NOTIFICATIONS, + getString(R.string.onboarding_permission_notifications), + getString(R.string.onboarding_permission_notifications_desc), + true + ) + ) + } + + if (isSAndAbove()) { + permissions.add( + Permission( + PermissionType.APP_LINKS, + "App Links", + "Enable Aurora Store to open links from the Play Store", + optional = true + ), + ) + } + + return permissions } + private fun updateController() { + binding.epoxyRecycler.withModels { + setFilterDuplicates(true) + + add( + TextDividerViewModel_() + .id("required_divider") + .title(getString(R.string.item_required)) + ) + + permissionList() + .filterNot { it.optional } + .forEach { add(renderPermissionView(it)) } + + val optionalPermissions = permissionList().filter { it.optional } + if (optionalPermissions.isNotEmpty()) { + add( + TextDividerViewModel_() + .id("optional_divider") + .title(getString(R.string.item_optional)) + ) + + optionalPermissions.forEach { add(renderPermissionView(it)) } + } + } + } + + private fun renderPermissionView(permission: Permission): PermissionViewModel_ { + return PermissionViewModel_() + .id(permission.type.name) + .permission(permission) + .isGranted(permissionProvider.isPermissionGranted(permission.type)) + .click { _ -> permissionProvider.requestPermission(permission.type) } + } } diff --git a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt index 1bb7be17a..e62b8ffe3 100644 --- a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt @@ -19,28 +19,21 @@ package com.aurora.store.view.ui.updates -import android.Manifest -import android.content.ActivityNotFoundException -import android.content.pm.PackageManager import android.os.Bundle -import android.os.Environment import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.aurora.Constants import com.aurora.extensions.browse -import com.aurora.extensions.isRAndAbove -import com.aurora.extensions.toast import com.aurora.store.MobileNavigationDirections +import com.aurora.store.PermissionType import com.aurora.store.R import com.aurora.store.data.model.MinimalApp +import com.aurora.store.data.providers.PermissionProvider import com.aurora.store.data.room.download.Download import com.aurora.store.data.room.update.Update import com.aurora.store.databinding.FragmentUpdatesBinding -import com.aurora.store.util.PackageUtil import com.aurora.store.util.PathUtil import com.aurora.store.view.epoxy.views.UpdateHeaderViewModel_ import com.aurora.store.view.epoxy.views.app.AppUpdateViewModel_ @@ -56,23 +49,15 @@ import kotlinx.coroutines.launch @AndroidEntryPoint class UpdatesFragment : BaseFragment() { + private lateinit var permissionProvider: PermissionProvider private lateinit var update: Update private val viewModel: UpdatesViewModel by viewModels() - private val startForStorageManagerResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (isRAndAbove() && Environment.isExternalStorageManager()) { - viewModel.download(update) - } else { - toast(R.string.permissions_denied) - } - } - - private val startForPermissions = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { perm -> - if (perm) viewModel.download(update) else toast(R.string.permissions_denied) - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + permissionProvider = PermissionProvider(this) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -124,6 +109,11 @@ class UpdatesFragment : BaseFragment() { } } + override fun onDestroy() { + permissionProvider.unregister() + super.onDestroy() + } + private fun updateController(appList: Map?) { binding.recycler.withModels { setFilterDuplicates(true) @@ -204,26 +194,10 @@ class UpdatesFragment : BaseFragment() { viewModel.updateAllEnqueued = updateAll if (PathUtil.needsStorageManagerPerm(update.fileList)) { - if (isRAndAbove()) { - if (!Environment.isExternalStorageManager()) { - try { - startForStorageManagerResult.launch(PackageUtil.getStorageManagerIntent()) - } catch (_: ActivityNotFoundException) { - startForStorageManagerResult.launch(PackageUtil.getStorageManagerIntent(true)) - } - } else { - viewModel.download(update) - } + if (permissionProvider.isPermissionGranted(PermissionType.STORAGE_MANAGER)) { + viewModel.download(update) } else { - if (ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED - ) { - viewModel.download(update) - } else { - startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } + permissionProvider.requestPermission(PermissionType.STORAGE_MANAGER) } } else { viewModel.download(update) diff --git a/app/src/main/res/layout/fragment_app_links.xml b/app/src/main/res/layout/fragment_app_links.xml index c02cc74fb..6d8e1e1e3 100644 --- a/app/src/main/res/layout/fragment_app_links.xml +++ b/app/src/main/res/layout/fragment_app_links.xml @@ -1,9 +1,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="match_parent" + android:clipToPadding="false" + android:overScrollMode="never" + android:padding="@dimen/padding_medium" + tools:listitem="@layout/view_permission_switch" /> + diff --git a/app/src/main/res/layout/view_permission.xml b/app/src/main/res/layout/view_permission.xml index d5c6b71a2..50910bf5a 100644 --- a/app/src/main/res/layout/view_permission.xml +++ b/app/src/main/res/layout/view_permission.xml @@ -17,12 +17,11 @@ ~ --> - @@ -31,24 +30,25 @@ style="@style/AuroraTextStyle.Subtitle" android:layout_width="match_parent" android:layout_height="wrap_content" - tools:text="Title" /> + android:layout_toStartOf="@id/btn_action" + tools:text="Permission Title" /> + android:layout_below="@id/line1" + android:layout_toStartOf="@id/btn_action" + android:maxLines="3" + tools:text="Permission Description" /> - + app:iconPadding="@dimen/padding_small" /> + diff --git a/app/src/main/res/layout/view_text_divider.xml b/app/src/main/res/layout/view_text_divider.xml new file mode 100644 index 000000000..9ac907296 --- /dev/null +++ b/app/src/main/res/layout/view_text_divider.xml @@ -0,0 +1,35 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5bcf676a1..0807d589d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -348,6 +348,8 @@ Menu Expand %1$s • API %2$s + Required + Optional Required permissions were denied. Please grant them in order to continue the action Search for Apps & Games Requesting new session