Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [ANDROAPP-6764] Review MEDIA permissions to comply with Google Play policy #3964

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

<uses-feature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ fun provideParameterSelectorItem(
resources = resources,
focusManager = focusManager,
onNextClicked = onNextClicked,
onFileSelected = {
/*Not supported for search*/
},
)
},
status = status,
Expand Down
37 changes: 34 additions & 3 deletions commons/src/main/java/org/dhis2/commons/bindings/FileExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,39 @@ fun File.rotateImage(context: Context): File {
else -> bitmap
}

return File(
FileResourceDirectoryHelper.getFileResourceDirectory(context),
val file = File(
FileResourceDirectoryHelper.getFileCacheResourceDirectory(context),
"tempFile.png",
).apply { writeBitmap(bitmap, Bitmap.CompressFormat.JPEG, 100) }
)

file.writeBitmap(bitmap, Bitmap.CompressFormat.JPEG, 100)

return file
}

fun Bitmap.rotateImageAndSave(context: Context): File {
val file = File(
FileResourceDirectoryHelper.getFileCacheResourceDirectory(context),
"tempFile.png",
)

file.writeBitmap(this, Bitmap.CompressFormat.JPEG, 100)

val ei = ExifInterface(file.path)

val orientation =
ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)

val bitmap = when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(this, 90F)
ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(this, 180F)
ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(this, 270F)
else -> this
}

file.writeBitmap(bitmap!!, Bitmap.CompressFormat.JPEG, 100)

return file
}

private fun rotateImage(source: Bitmap, angle: Float): Bitmap? {
Expand Down Expand Up @@ -107,9 +136,11 @@ private fun getFilePath(context: Context, uri: Uri): String? {
split[splitIndex].toLong(),
)
}

isExternalStorageDocument(copy) -> {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}

isMediaDocument(copy) -> {
copy = when (split[0]) {
"image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
Expand Down
2 changes: 0 additions & 2 deletions form/src/main/java/org/dhis2/form/model/UiEventType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package org.dhis2.form.model

enum class UiEventType {
REQUEST_LOCATION_BY_MAP,
ADD_PICTURE,
ADD_FILE,
OPEN_FILE,
SHARE_IMAGE,
}
9 changes: 9 additions & 0 deletions form/src/main/java/org/dhis2/form/ui/Form.kt
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ fun Form(
onNextSection,
)
},
onFileSelected = { path ->
intentHandler.invoke(
FormIntent.OnStoreFile(
uid = fieldUiModel.uid,
filePath = path,
valueType = fieldUiModel.valueType,
),
)
},
)
}
}
Expand Down
189 changes: 0 additions & 189 deletions form/src/main/java/org/dhis2/form/ui/FormView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package org.dhis2.form.ui
import android.Manifest
import android.app.Activity.RESULT_OK
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
Expand All @@ -15,7 +13,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
Expand All @@ -28,12 +25,7 @@ import androidx.fragment.app.viewModels
import androidx.paging.compose.collectAsLazyPagingItems
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.journeyapps.barcodescanner.ScanOptions
import org.dhis2.commons.ActivityResultObservable
import org.dhis2.commons.ActivityResultObserver
import org.dhis2.commons.Constants
import org.dhis2.commons.bindings.getFileFrom
import org.dhis2.commons.bindings.getFileFromGallery
import org.dhis2.commons.bindings.rotateImage
import org.dhis2.commons.data.FileHandler
import org.dhis2.commons.data.FormFileProvider
import org.dhis2.commons.date.DateUtils
Expand Down Expand Up @@ -77,7 +69,6 @@ import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialog
import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialogUiModel
import org.dhis2.ui.dialogs.bottomsheet.DialogButtonStyle
import org.dhis2.ui.dialogs.bottomsheet.FieldWithIssue
import org.hisp.dhis.android.core.arch.helpers.FileResourceDirectoryHelper
import org.hisp.dhis.android.core.common.ValueType
import org.hisp.dhis.android.core.common.ValueTypeRenderingType
import org.hisp.dhis.android.core.event.EventStatus
Expand Down Expand Up @@ -131,91 +122,6 @@ class FormView : Fragment() {
}
}

private val requestCameraPermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
if (it.values.all { isGranted -> isGranted }) {
showAddImageOptions()
(context as ActivityResultObservable?)?.subscribe(object : ActivityResultObserver {
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?,
) {
if (resultCode != RESULT_OK) {
showAddImageOptions()
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray,
) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showAddImageOptions()
}
}
})
} else {
Toast.makeText(
requireContext(),
requireContext().getString(R.string.camera_permission_denied),
Toast.LENGTH_LONG,
).show()
}
}

private val takePicture =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
val imageFile = File(
FileResourceDirectoryHelper.getFileResourceDirectory(requireContext()),
TEMP_FILE,
).rotateImage(requireContext())
onSavePicture?.invoke(imageFile.path)

viewModel.getFocusedItemUid()?.let {
viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
}
} else {
viewModel.getFocusedItemUid()?.let {
viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
}
}
}

private val pickImage =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
if (activityResult.resultCode == RESULT_OK) {
getFileFromGallery(requireContext(), activityResult.data?.data)?.also { file ->
onSavePicture?.invoke(file.path)
}
viewModel.getFocusedItemUid()?.let {
viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
}
} else {
viewModel.getFocusedItemUid()?.let {
viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
}
}
}

private val pickFile =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri != null) {
getFileFrom(requireContext(), uri)?.also { file ->
onSavePicture?.invoke(file.path)
}
viewModel.getFocusedItemUid()?.let {
viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
}
} else {
viewModel.getFocusedItemUid()?.let {
viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
}
}
}

private val requestStoragePermissions =
registerForActivityResult(
ActivityResultContracts.RequestPermission(),
Expand Down Expand Up @@ -245,21 +151,6 @@ class FormView : Fragment() {
private lateinit var formSectionMapper: FormSectionMapper
var scrollCallback: ((Boolean) -> Unit)? = null
private var displayConfErrors = true
private var onSavePicture: ((String) -> Unit)? = null

private val storagePermissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
)

@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
private val storagePermissions33 = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_AUDIO,
Manifest.permission.READ_MEDIA_VIDEO,
)

private val fileHandler = FileHandler()

Expand Down Expand Up @@ -553,9 +444,7 @@ class FormView : Fragment() {
is RecyclerViewUiEvents.DisplayQRCode -> displayQRImage(uiEvent)
is RecyclerViewUiEvents.ScanQRCode -> requestQRScan(uiEvent)
is RecyclerViewUiEvents.OpenOrgUnitDialog -> showOrgUnitDialog(uiEvent)
is RecyclerViewUiEvents.AddImage -> requestAddImage(uiEvent)
is RecyclerViewUiEvents.OpenFile -> openFile(uiEvent)
is RecyclerViewUiEvents.OpenFileSelector -> openFileSelector(uiEvent)
is RecyclerViewUiEvents.OpenChooserIntent -> openChooserIntent(uiEvent)
is RecyclerViewUiEvents.SelectPeriod -> showPeriodDialog(uiEvent)
}
Expand Down Expand Up @@ -699,84 +588,6 @@ class FormView : Fragment() {
)
}

private fun requestAddImage(event: RecyclerViewUiEvents.AddImage) {
onSavePicture = { picture ->
intentHandler(
FormIntent.OnStoreFile(
event.uid,
picture,
ValueType.IMAGE,
),
)
}
requestCameraPermissions.launch(permissions())
}

private fun permissions() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
storagePermissions33
} else {
storagePermissions
}

private fun showAddImageOptions() {
val options = arrayOf<CharSequence>(
requireContext().getString(R.string.take_photo),
requireContext().getString(R.string.from_gallery),
requireContext().getString(R.string.cancel),
)
MaterialAlertDialogBuilder(requireActivity(), R.style.MaterialDialog)
.setTitle(requireContext().getString(R.string.select_option))
.setOnCancelListener {
viewModel.getFocusedItemUid()?.let {
viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
}
}
.setItems(options) { dialog: DialogInterface, item: Int ->
run {
when (options[item]) {
requireContext().getString(R.string.take_photo) -> {
val photoUri = FileProvider.getUriForFile(
requireContext(),
FormFileProvider.fileProviderAuthority,
File(
FileResourceDirectoryHelper.getFileResourceDirectory(
requireContext(),
),
TEMP_FILE,
),
)
takePicture.launch(photoUri)
}

requireContext().getString(R.string.from_gallery) -> {
pickImage.launch(Intent(Intent.ACTION_PICK).apply { type = "image/*" })
}

requireContext().getString(R.string.cancel) -> {
viewModel.getFocusedItemUid()?.let {
viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
}
}
}
dialog.dismiss()
}
}
.show()
}

private fun openFileSelector(event: RecyclerViewUiEvents.OpenFileSelector) {
onSavePicture = { file ->
intentHandler(
FormIntent.OnStoreFile(
event.field.uid,
file,
event.field.valueType,
),
)
}
pickFile.launch("*/*")
}

private fun openFile(event: RecyclerViewUiEvents.OpenFile) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
downloadFile(event.field.displayName)
Expand Down
Loading