From df2193e31a27d1e50a37623eff5d75170c50677a Mon Sep 17 00:00:00 2001 From: D4rK7355608 Date: Wed, 10 Jul 2024 09:26:43 +0300 Subject: [PATCH] Created the new PermissionsUtils --- app/build.gradle.kts | 2 +- .../com/d4rk/cleaner/ui/home/HomeViewModel.kt | 141 +------------- .../cleaner/ui/startup/StartupActivity.kt | 21 +-- .../d4rk/cleaner/utils/PermissionsUtils.kt | 175 ++++++++++++++++++ 4 files changed, 185 insertions(+), 154 deletions(-) create mode 100644 app/src/main/kotlin/com/d4rk/cleaner/utils/PermissionsUtils.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f956b85..64e8ebe 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,7 +14,7 @@ android { applicationId = "com.d4rk.cleaner" minSdk = 26 targetSdk = 34 - versionCode = 85 + versionCode = 86 versionName = "2.0.0" archivesName = "${applicationId}-v${versionName}" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/home/HomeViewModel.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/home/HomeViewModel.kt index afb96e1..b69d8ff 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/home/HomeViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/home/HomeViewModel.kt @@ -1,27 +1,18 @@ package com.d4rk.cleaner.ui.home -import android.Manifest import android.app.Activity -import android.app.AppOpsManager import android.app.Application import android.app.usage.StorageStatsManager import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Build -import android.os.Environment import android.os.storage.StorageManager -import android.provider.Settings import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.d4rk.cleaner.data.datastore.DataStore import com.d4rk.cleaner.utils.FileScanner +import com.d4rk.cleaner.utils.PermissionsUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -123,8 +114,8 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { * @param activity The Activity instance required to request permissions. */ fun analyze(activity: Activity) { - if (!hasRequiredPermissions()) { - requestPermissions(activity) + if (!PermissionsUtils.hasStoragePermissions(getApplication())) { + PermissionsUtils.requestStoragePermissions(activity) return } @@ -157,8 +148,8 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { * @param activity The Activity instance required to request permissions. */ fun clean(activity: Activity) { - if (!hasRequiredPermissions()) { - requestPermissions(activity) + if (!PermissionsUtils.hasStoragePermissions(getApplication())) { + PermissionsUtils.requestStoragePermissions(activity) return } @@ -179,126 +170,4 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { } } } - - /** - * Checks if the app has all the necessary permissions to perform scanning and cleaning. - * - * @return True if all required permissions are granted, false otherwise. - */ - private fun hasRequiredPermissions(): Boolean { - val hasStoragePermissions = when { - Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q -> - ContextCompat.checkSelfPermission( - getApplication(), - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED - - Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 -> - ContextCompat.checkSelfPermission( - getApplication(), - Manifest.permission.READ_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED - - else -> true - } - - val hasManageStoragePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - Environment.isExternalStorageManager() - } else { - true - } - - val hasUsageStatsPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - isAccessGranted() - } else { - true - } - - val hasMediaPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - ContextCompat.checkSelfPermission( - getApplication(), - Manifest.permission.READ_MEDIA_AUDIO - ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( - getApplication(), - Manifest.permission.READ_MEDIA_IMAGES - ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( - getApplication(), - Manifest.permission.READ_MEDIA_VIDEO - ) == PackageManager.PERMISSION_GRANTED - } else { - true - } - - return hasStoragePermissions && hasManageStoragePermission && - hasUsageStatsPermission && hasMediaPermissions - } - - /** - * Checks if the app has access to usage statistics. - * - * @return True if access is granted, false otherwise. - */ - private fun isAccessGranted(): Boolean = try { - val packageManager = getApplication().packageManager - val applicationInfo = - packageManager.getApplicationInfo(getApplication().packageName, 0) - val appOpsManager = - getApplication().getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager - @Suppress("DEPRECATION") val mode: Int = appOpsManager.checkOpNoThrow( - AppOpsManager.OPSTR_GET_USAGE_STATS, - applicationInfo.uid, - applicationInfo.packageName - ) - mode == AppOpsManager.MODE_ALLOWED - } catch (e: PackageManager.NameNotFoundException) { - false - } - - /** - * Requests necessary permissions for the app to function correctly. - * - * This function checks for and requests the following permissions: - * - WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE: For accessing and managing files. - * - MANAGE_EXTERNAL_STORAGE (Android R and above): For managing all files on external storage. - * - PACKAGE_USAGE_STATS: For gathering app usage statistics. - * - READ_MEDIA_AUDIO, READ_MEDIA_IMAGES, READ_MEDIA_VIDEO (Android Tiramisu and above): - * For accessing media files. - * - * It utilizes ActivityCompat.requestPermissions to initiate the permission request process - * and handles different Android versions to ensure compatibility. - * - * @param activity The Activity instance required to request permissions. - */ - private fun requestPermissions(activity: Activity) { - val requiredPermissions = mutableListOf( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_EXTERNAL_STORAGE - ) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - if (!Environment.isExternalStorageManager()) { - val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) - val uri = Uri.fromParts("package", getApplication().packageName, null) - intent.data = uri - activity.startActivity(intent) - } - - if (!isAccessGranted()) { - val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS) - activity.startActivity(intent) - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - requiredPermissions.addAll( - listOf( - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO - ) - ) - } - - ActivityCompat.requestPermissions(activity, requiredPermissions.toTypedArray(), 1) - } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/startup/StartupActivity.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/startup/StartupActivity.kt index e0552c6..9d426bd 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/startup/StartupActivity.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/startup/StartupActivity.kt @@ -11,6 +11,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier import com.d4rk.cleaner.ui.settings.display.theme.style.AppTheme +import com.d4rk.cleaner.utils.PermissionsUtils import com.google.android.ump.ConsentForm import com.google.android.ump.ConsentInformation import com.google.android.ump.ConsentRequestParameters @@ -38,7 +39,9 @@ class StartupActivity : AppCompatActivity() { loadForm() } } , {}) - requestPermissions() + if (!PermissionsUtils.hasNotificationPermission(this)) { + PermissionsUtils.requestNotificationPermission(this) + } } /** @@ -62,20 +65,4 @@ class StartupActivity : AppCompatActivity() { } } , {}) } - - /** - * Handles the application's permission requirements. - * - * This function is responsible for checking and requesting the necessary permissions for the application. It takes into account the Android version to manage specific permission scenarios. - * For Android versions Tiramisu or later, it requests the POST_NOTIFICATIONS permission. - * - * @see android.Manifest.permission.POST_NOTIFICATIONS - * @see android.os.Build.VERSION.SDK_INT - * @see android.os.Build.VERSION_CODES.TIRAMISU - */ - private fun requestPermissions() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS) , 1) - } - } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cleaner/utils/PermissionsUtils.kt b/app/src/main/kotlin/com/d4rk/cleaner/utils/PermissionsUtils.kt new file mode 100644 index 0000000..efbaf5c --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cleaner/utils/PermissionsUtils.kt @@ -0,0 +1,175 @@ +package com.d4rk.cleaner.utils + +import android.Manifest +import android.app.Activity +import android.app.AppOpsManager +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.Settings +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + +/** + * Utility class for handling runtime permissions. + */ +object PermissionsUtils { + + // Permission constants + const val REQUEST_CODE_STORAGE_PERMISSIONS = 1 + const val REQUEST_CODE_NOTIFICATION_PERMISSION = 2 + + /** + * Checks if the app has all necessary storage permissions. + * + * @param context The application context. + * @return True if all required permissions are granted, false otherwise. + */ + fun hasStoragePermissions(context: Context): Boolean { + val hasStoragePermissions = when { + Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q -> + ContextCompat.checkSelfPermission( + context, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED + + Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 -> + ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED + + else -> true + } + + val hasManageStoragePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Environment.isExternalStorageManager() + } else { + true + } + + val hasUsageStatsPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + isAccessGranted(context) + } else { + true + } + + val hasMediaPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_MEDIA_AUDIO + ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_MEDIA_IMAGES + ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_MEDIA_VIDEO + ) == PackageManager.PERMISSION_GRANTED + } else { + true + } + + return hasStoragePermissions && hasManageStoragePermission && + hasUsageStatsPermission && hasMediaPermissions + } + + + /** + * Requests the necessary storage permissions. + * + * @param activity The Activity instance required to request permissions. + */ + fun requestStoragePermissions(activity: Activity) { + val requiredPermissions = mutableListOf( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (!Environment.isExternalStorageManager()) { + val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) + val uri = Uri.fromParts("package", activity.packageName, null) + intent.data = uri + activity.startActivity(intent) + } + + if (!isAccessGranted(activity)) { + val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS) + activity.startActivity(intent) + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requiredPermissions.addAll( + listOf( + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO + ) + ) + } + + ActivityCompat.requestPermissions( + activity, + requiredPermissions.toTypedArray(), + REQUEST_CODE_STORAGE_PERMISSIONS + ) + } + + /** + * Checks if the app has access to usage statistics. + * + * @param context The application context. + * @return True if access is granted, false otherwise. + */ + private fun isAccessGranted(context: Context): Boolean = try { + val packageManager = context.packageManager + val applicationInfo = packageManager.getApplicationInfo(context.packageName, 0) + val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + @Suppress("DEPRECATION") val mode: Int = appOpsManager.checkOpNoThrow( + AppOpsManager.OPSTR_GET_USAGE_STATS, + applicationInfo.uid, + applicationInfo.packageName + ) + mode == AppOpsManager.MODE_ALLOWED + } catch (e: PackageManager.NameNotFoundException) { + false + } + + + /** + * Checks if the app has permission to post notifications. + * + * @param context The application context. + * @return True if the permission is granted, false otherwise. + */ + fun hasNotificationPermission(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + } else { + true + } + } + + /** + * Requests the notification permission. + * + * @param activity The Activity instance required to request the permission. + */ + fun requestNotificationPermission(activity: Activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ActivityCompat.requestPermissions( + activity, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + REQUEST_CODE_NOTIFICATION_PERMISSION + ) + } + } + +} \ No newline at end of file