Skip to content

Commit

Permalink
Fix READ_MEDIA_IMAGES crashing in Android 15 #72
Browse files Browse the repository at this point in the history
  • Loading branch information
Hamza417 committed Sep 18, 2024
1 parent 5d595d7 commit d57278a
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 75 deletions.
4 changes: 2 additions & 2 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {

android {
namespace 'app.simple.peri'
compileSdk 34
compileSdk 35

buildFeatures {
viewBinding true
Expand All @@ -18,7 +18,7 @@ android {
defaultConfig {
applicationId "app.simple.peri"
minSdk 24
targetSdk 34
targetSdk 35
versionCode 203
versionName "2.03_stable"

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission
android:name="android.permission.READ_MEDIA_IMAGES"
tools:ignore="SelectedPhotoAccess" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
Expand Down
209 changes: 143 additions & 66 deletions app/src/main/java/app/simple/peri/compose/screens/Setup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.provider.Settings
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -23,6 +24,7 @@ import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material3.Button
Expand All @@ -41,6 +43,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
Expand Down Expand Up @@ -71,41 +74,42 @@ fun Setup(context: Context, navController: NavController? = null) {
)
}

Column(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(COMMON_PADDING)
.windowInsetsPadding(WindowInsets.safeDrawing),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TopHeader(context.getString(R.string.setup),
modifier = Modifier.padding(COMMON_PADDING), isSettings = true)
item {
TopHeader(context.getString(R.string.setup),
modifier = Modifier.padding(COMMON_PADDING), isSettings = true)

Permissions(context = context, navController = navController, modifier = Modifier
.padding(COMMON_PADDING)
.wrapContentHeight())
Permissions(context = context, navController = navController, modifier = Modifier
.padding(COMMON_PADDING)
.wrapContentHeight())

Folder(context = context, navController = navController, modifier = Modifier
.padding(COMMON_PADDING)
.weight(1F))
Folder(context = context, navController = navController, modifier = Modifier
.padding(COMMON_PADDING))

Button(
onClick = {
if (isSetupComplete(context)) {
navController?.navigate(Routes.HOME)
} else {
showSetupIncompleteDialog = true
}
},
modifier = Modifier
.padding(COMMON_PADDING)
.fillMaxWidth(),
) {
Text(text = context.getString(R.string.continue_button),
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
modifier = Modifier.padding(12.dp))
Button(
onClick = {
if (isSetupComplete(context)) {
navController?.navigate(Routes.HOME)
} else {
showSetupIncompleteDialog = true
}
},
modifier = Modifier
.padding(COMMON_PADDING)
.fillMaxWidth(),
) {
Text(text = context.getString(R.string.continue_button),
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
modifier = Modifier.padding(12.dp))
}
}
}
}
Expand All @@ -115,6 +119,7 @@ fun Permissions(modifier: Modifier, context: Context, navController: NavControll
var showExternalPermissionDialog by remember { mutableStateOf(false) }
var showBatteryOptimizationDialog by remember { mutableStateOf(false) }
var requestPermissionLauncher by remember { mutableStateOf(false) }
var requestMediaImages by remember { mutableStateOf(false) }

if (showExternalPermissionDialog) {
ShowWarningDialog(
Expand Down Expand Up @@ -142,11 +147,20 @@ fun Permissions(modifier: Modifier, context: Context, navController: NavControll
RequestStoragePermissions()
}

if (requestMediaImages) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
RequestReadMediaImagesPermission {
requestMediaImages = false
showExternalPermissionDialog = false
}
}
}

Column(
modifier = modifier
) {
Text(
text = "Permissions",
text = stringResource(R.string.permissions),
fontSize = 24.sp,
modifier = Modifier
.padding(16.dp)
Expand All @@ -158,40 +172,42 @@ fun Permissions(modifier: Modifier, context: Context, navController: NavControll
modifier = Modifier.padding(bottom = COMMON_PADDING, start = COMMON_PADDING, end = COMMON_PADDING)
)

Card(
onClick = {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
if (Environment.isExternalStorageManager()) {
showExternalPermissionDialog = true
} else {
try {
val uri = Uri.parse("package:${BuildConfig.APPLICATION_ID}")
context.startActivity(Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri))
} catch (ignored: ActivityNotFoundException) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
Card(
onClick = {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
if (Environment.isExternalStorageManager()) {
showExternalPermissionDialog = true
} else {
try {
val uri = Uri.parse("package:${BuildConfig.APPLICATION_ID}")
context.startActivity(Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri))
} catch (ignored: ActivityNotFoundException) {

}
}
}
}

else -> {
if (PermissionUtils.checkStoragePermission(context)) {
requestPermissionLauncher = false
showExternalPermissionDialog = true
} else {
requestPermissionLauncher = true
showExternalPermissionDialog = false
else -> {
if (PermissionUtils.checkStoragePermission(context)) {
requestPermissionLauncher = false
showExternalPermissionDialog = true
} else {
requestPermissionLauncher = true
showExternalPermissionDialog = false
}
}
}
}
},
colors = CardDefaults.cardColors(
containerColor = Color.Transparent
),
) {
PermissionText(
context.getString(R.string.external_storage),
context.getString(R.string.external_storage_summary))
},
colors = CardDefaults.cardColors(
containerColor = Color.Transparent
),
) {
PermissionText(
context.getString(R.string.external_storage),
context.getString(R.string.external_storage_summary))
}
}

Card(
Expand All @@ -210,6 +226,36 @@ fun Permissions(modifier: Modifier, context: Context, navController: NavControll
context.getString(R.string.battery_optimization),
context.getString(R.string.battery_optimization_summary))
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
Card(
onClick = {
if (PermissionUtils.checkMediaImagesPermission(context)) {
requestMediaImages = false
showExternalPermissionDialog = true
} else {
requestMediaImages = true
showExternalPermissionDialog = false
}
},
colors = CardDefaults.cardColors(
containerColor = Color.Transparent
),
) {
PermissionText(
context.getString(R.string.allow_media_access),
context.getString(R.string.external_storage_summary))

Text(
text = context.getString(R.string.allow_media_access_info),
fontSize = 14.sp,
modifier = Modifier
.padding(bottom = COMMON_PADDING, start = COMMON_PADDING, end = COMMON_PADDING)
.fillMaxWidth(),
fontWeight = FontWeight.Normal
)
}
}
}
}

Expand Down Expand Up @@ -338,22 +384,53 @@ fun RequestStoragePermissions() {
// Ensure the launcher is initialized before launching the permission request
LaunchedEffect(Unit) {
requestPermissionLauncher.launch(
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
}
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun RequestReadMediaImagesPermission(onCancel: () -> Unit) {
val requestPermissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGranted ->
// Handle the result of the permission request
if (isGranted) {
Log.d("Permission", "READ_EXTERNAL_STORAGE granted")
} else {
Log.d("Permission", "READ_EXTERNAL_STORAGE denied")
onCancel()
}
}

// Ensure the launcher is initialized before launching the permission request
LaunchedEffect(Unit) {
requestPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES)
}
}

fun isSetupComplete(context: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.contentResolver.persistedUriPermissions.isNotEmpty()
&& Environment.isExternalStorageManager()
&& context.isBatteryOptimizationDisabled()
} else {
context.contentResolver.persistedUriPermissions.isNotEmpty()
&& PermissionUtils.checkStoragePermission(context)
&& context.isBatteryOptimizationDisabled()
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM -> {
context.contentResolver.persistedUriPermissions.isNotEmpty()
&& context.isBatteryOptimizationDisabled()
&& PermissionUtils.checkMediaImagesPermission(context)
}

Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
context.contentResolver.persistedUriPermissions.isNotEmpty()
&& Environment.isExternalStorageManager()
&& context.isBatteryOptimizationDisabled()
}

else -> {
context.contentResolver.persistedUriPermissions.isNotEmpty()
&& PermissionUtils.checkStoragePermission(context)
&& context.isBatteryOptimizationDisabled()
}
}
}
10 changes: 5 additions & 5 deletions app/src/main/java/app/simple/peri/utils/BitmapUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object BitmapUtils {
setSaturation(saturation)
})

val ret = Bitmap.createBitmap(width, height, config)
val ret = Bitmap.createBitmap(width, height, config ?: Bitmap.Config.ARGB_8888)
val canvas = Canvas(ret)
val paint = Paint()
paint.colorFilter = ColorMatrixColorFilter(colorMatrix)
Expand Down Expand Up @@ -87,7 +87,7 @@ object BitmapUtils {
cm.setSaturation(saturation)
val paint = Paint()
paint.colorFilter = ColorMatrixColorFilter(cm)
val ret = Bitmap.createBitmap(width, height, config)
val ret = Bitmap.createBitmap(width, height, config ?: Bitmap.Config.ARGB_8888)
val canvas = Canvas(ret)
canvas.drawBitmap(this, 0f, 0f, paint)
return ret
Expand All @@ -103,7 +103,7 @@ object BitmapUtils {
))
val paint = Paint()
paint.colorFilter = ColorMatrixColorFilter(cm)
val ret = Bitmap.createBitmap(width, height, config)
val ret = Bitmap.createBitmap(width, height, config ?: Bitmap.Config.ARGB_8888)
val canvas = Canvas(ret)
canvas.drawBitmap(this, 0f, 0f, paint)
return ret
Expand All @@ -119,7 +119,7 @@ object BitmapUtils {
))
val paint = Paint()
paint.colorFilter = ColorMatrixColorFilter(cm)
val ret = Bitmap.createBitmap(width, height, config)
val ret = Bitmap.createBitmap(width, height, config ?: Bitmap.Config.ARGB_8888)
val canvas = Canvas(ret)
canvas.drawBitmap(this, 0f, 0f, paint)
return ret
Expand Down Expand Up @@ -150,7 +150,7 @@ object BitmapUtils {
}

fun Bitmap.cropBitmap(rect: Rect): Bitmap {
val ret = Bitmap.createBitmap(rect.width(), rect.height(), config)
val ret = Bitmap.createBitmap(rect.width(), rect.height(), config ?: Bitmap.Config.ARGB_8888)
val canvas = Canvas(ret)
canvas.drawBitmap(this, rect, Rect(0, 0, rect.width(), rect.height()), null)
return ret
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/app/simple/peri/utils/PermissionUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.os.Build
import android.os.Environment
import android.os.PowerManager
import android.provider.Settings
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat

object PermissionUtils {
Expand Down Expand Up @@ -40,4 +41,9 @@ object PermissionUtils {
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
}
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun checkMediaImagesPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED
}
}
Loading

0 comments on commit d57278a

Please sign in to comment.