Redo Permissions [1/2]

This commit is contained in:
Rahul Patel
2024-07-27 13:56:40 +05:30
parent 894e7a629f
commit 44b9a1a7b2
15 changed files with 407 additions and 475 deletions

View File

@@ -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
}
}

View File

@@ -0,0 +1,10 @@
package com.aurora.store
enum class PermissionType {
EXTERNAL_STORAGE,
STORAGE_MANAGER,
INSTALL_UNKNOWN_APPS,
POST_NOTIFICATIONS,
DOZE_WHITELIST,
APP_LINKS
}

View File

@@ -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()
}
}

View File

@@ -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<ActivityResult> {
private var context: Context
private var intentLauncher: ActivityResultLauncher<Intent>
private var permissionLauncher: ActivityResultLauncher<String>
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<PermissionType, Intent> = 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
}
}

View File

@@ -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
}

View File

@@ -0,0 +1,42 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
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<ViewTextDividerBinding>(context, attrs, defStyleAttr) {
@ModelProp
fun title(title: String) {
binding.txtTitle.text = title
}
}

View File

@@ -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<FragmentDetailsBinding>() {
@Inject
lateinit var authProvider: AuthProvider
private lateinit var bottomSheetBehavior: BottomSheetBehavior<LinearLayout>
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<LinearLayout>
private lateinit var app: App
@@ -192,6 +172,11 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
}
}
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<FragmentDetailsBinding>() {
super.onResume()
}
override fun onDestroy() {
permissionProvider.unregister()
super.onDestroy()
}
private fun attachActions() {
flip(0)
checkAndSetupInstall()
@@ -560,26 +550,10 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
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)

View File

@@ -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<FragmentAppLinksBinding>() {
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<String, MaterialButton>
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
}
}
}

View File

@@ -76,7 +76,7 @@ class OnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
}
override fun getItemCount(): Int {
return 3
return 2
}
}
@@ -122,9 +122,9 @@ class OnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
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<FragmentOnboardingBinding>() {
}
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())
}
}

View File

@@ -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<FragmentOnboardingPermissionsBinding>() {
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<Permission> {
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) }
}
}

View File

@@ -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<FragmentUpdatesBinding>() {
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<FragmentUpdatesBinding>() {
}
}
override fun onDestroy() {
permissionProvider.unregister()
super.onDestroy()
}
private fun updateController(appList: Map<Update, Download?>?) {
binding.recycler.withModels {
setFilterDuplicates(true)
@@ -204,26 +194,10 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
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)