안드로이드

Android Q 파일 삭제 ScopeStorage 삽질

코딩하는후운 2024. 3. 20. 13:12
반응형

이미지를 서버에 업로드후에 삭제해야 하는 이슈.

* 이미지를 업로드 후에 다른 Task(runnable)에서 파일을 삭제 시켜주는 로직이 들어가 있음.(해당 액티비티에서 삭제가 아님)

나의 삽질을 적어둔 것 하단에 결론만 보셔도 됩니다.

 

 

1. SAF로 Uri를 가져옴.

: content://com.android.providers.media.documents/document/image%3A328

 

2. DocumentsContract.deleteDocument를 이용해 삭제 시도. - 실패
: NullPointerException: authority 익셉션 떨어짐.

처음에 가져온 경로를 File(path)하여 삭제 중 이었음. (content:// Uri였기 때문에 경로가 바뀐듯.)

바뀐 경로 : /content:/com.android.providers.media.documents/document/image%3A328
맞는 경로 : content://com.android.providers.media.documents/document/image%3A328

File()로 만들어서 제대로 된 테스트가 되지 않은듯.

 

3. File로 하지않고 Uri로 바로 접근하여 삭제 시도. - 실패

java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord

 

4. ContentResolver로 다시 query를 해서 uri를 가져와서 삭제 해보면 어떨까 해서 query해 봄. - 실패

Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord

: 뭔가 권한 문제인가.. 의심

 

5. 이미지를 가져온 액티비티에서 삭제를 해보니 삭제가 된다. (Activity - onActivityResult)

: 하지만 Task(runnable)에서 삭제는 안됌

java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord

 

6. DocumentFile.fromSingleUri(MyApp.get(), Uri.parse(fileOriginPath))로 DocumentFile얻어와서 delete해도 안된다..

: Activity에선 삭제가 되고, UploadTask에서 삭제하는건 안된다.

 

*그래서 액티비티로 한번 경로를 줘보기로 함

7. EventBus를 이용하여 FileUtil -> Activity로 fileUri를 전달.

: contentResolver.delete -> Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord
: DocumentsContract.deleteDocument -> java.lang.SecurityException: Permission Denial
: 뭔가 해당 이미지를 가져온 액티비티에서만 삭제가 가능한듯(?)

 

7-1) Android Q이상 에서는 SecurityException가 발생하면서 RecoverableSecurityException로 사용자에게 파일 삭제 할건지 직접 허용 받는 것이 있는데, 이 문제인줄 알고 해봤지만 안되었음!

 

8. 검색!!!

검색 : Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord
참조 : https://stackoverflow.com/questions/43430743/permission-denial-opening-provider-com-android-providers-media-mediadocumentspr
: takePersistableUriPermission 이런걸 발견

 

9. 검색!!!

검색 : takePersistableUriPermission

참조 : https://www.python2.net/questions-1265076.htm
https://dnight.tistory.com/entry/Storage-Access-Framwork-%EB%8B%A4%EB%A3%A8%EA%B8%B0-Android-ScopedStorage

: SAF로 사용자가 선택한 파일 또는 폴더를 Root로하여 uri를 onActivityResult로 응답.
응답받은 uri는 사용자가 선택한 파일 또는 폴더의 uri 이며 해당 uri 상위로는 접근이 불가능 합니다.


*파일 삭제의 경우 삭제하고자하는 uri를 contentResolver 를 통해서 삭제 할수 있습니다.
다만 파일읽기/탐색에서 지정한 범위를 벗어나거나 앱에서 생성한 파일 이외에는 하위의 에러가 발생합니다.
java.lang.SecurityException: Permission Denial: writing com.android.externalstorage.ExternalStorageProvider uri content://co

 

[파일 권한 저장]
파일을 생성하거나 열거나 할 경우 매번 Intent를 통해서 System UI를 표시할경우 사용자가 앱을 이용하는데 불편할수 있습니다.

그래서 최초에 사용자가 SystemUI를 통해서 지정한 뒤 onActivityResult 를 통해서 응답받은 Uri를
contentResolver.takePersistableUriPermission 통해서 권한을 얻은뒤 SharedPreferences 등의 저장소에 Uri를 저장하여
매번 Intent 요청없이 파일을 제어할 수 있습니다.
*contentResolver.takePersistableUriPermission(directoryUri,Intent.FLAG_GRANT_READ_URI_PERMISSION)

 

라는데 뭐가 어려움..

 

10. 검색!!!
검색 : No persistable permission grants found for UID 10164

참조 : https://www.python2.net/questions-279643.htm
https://www.python2.net/questions-1265076.htm
Android 파일 트리를 볼 수 있으며, 주 저장소를 선택한 상태에서 클릭하여 권한을 부여 할 수 있습니다.
"앱 이름-이 위치에 현재 저장된 모든 파일과 여기에 저장된 향후 콘텐츠에 대한 전체 액세스 권한을 가질 수 있습니다."-> 허용하다

 

느낌이 SAF로 가져온 onActivityResult에서 받아온 Uri에 takePersistableUriPermission로 권한 부여를 하는듯 하다.

추후 앱에서 권한을 가질 수 있도록(?) 앱을 삭제하고 다시 설치하면 그 권한은 사라지는 듯

 

 

 

결론 : takePersistableUriPermission로 권한을 부여하니 Task(Runnable)에서 삭제가 된다.

Uri uri = data.getData();
int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    getContentResolver().takePersistableUriPermission(uri, takeFlags);
}

 

궁금 :

1. SAF로 가져온 이미지는 

- contentResolver.delete로 는 삭제가 되지 않았다.

: java.lang.UnsupportedOperationException: Delete not supported 발생

 

-DocumentsContract.deleteDocument, DocumentFile.fromSingleUri를 이용하니 삭제는 되었다.

 

2. ScopeStorage적용하면서 

SecurityException 발생하면 RecoverableSecurityException으로 intent 허용 받는 처리가 있는데,

SAF로 가져온것은 적용이 안되는건가(?)

 

아직도 잘 모르겠다.. 공부 해야지.. 

 

삽질 이후..

드디어 RecoverableSecurityException가 발생 했다.

contentResolver로 가져온 uri : content://media/external/images/media/249

uri로 contentResolver로 '_data'로 파일경로 가져오기 : /storage/emulated/0/DCIM/Camera/JPEG_20210628_153320_.jpg

 

contentResolver로 가져온 uri를 통하여 contentResolver.delete()를 실행하면 SecurityException가 발생하면서  Android Q이상에서 RecoverableSecurityException작업을 할 수 있다.

 

1. Intent(Intent.ACTION_PICK, null).setType("video/*")를 통하여 가져 왔을 경우

가져온 uri : content://com.google.android.apps.photos.contentprovider/-1/2/content%3A%2F%2Fmedia%2Fexternal%2Fvideo%2Fmedia%2F32/ORIGINAL/NONE/2117276171

 

Intent.ACTION_PICK으로 가져온 uri로 contentResolver.delete()하면 Permission Denied가 발생 

그래서, contentResolver로 전체 검색 후 DisplayName이 같은 것으로 가져와 다시 content://Uri로 작업해줌.

 

2. 사진 삭제 하기(contentResolver로 가져온 uri)

try{

    //여기서 contentResolver.delete()실행

} catch (securityException: SecurityException) {

    //Android Q이상으로 분기하여 RecoverableSecurityException로 처리.

    .

    .

    val intentSender = recoverableSecurityException.userAction.actionIntent.intentSender
    //startActivityForResult deprecate로 인한 launch작업.
    checkFilePermissionLauncher.launch(
        IntentSenderRequest.Builder(intentSender).build()
    )

}

 

3. 사진 여러장 삭제하기

상황) 사진 여러장 삭제할 경우 여러장의 파일 권한을 받아야 하는 상황.

 

나는 listUri를 만들어서 권한을 확인해야 하는 uri를 add 시켰다.

이것 또한 try/catch로 감싸서 작업.

 

  • 파일 권한 체크
    context.contentResolver
        ?.openFileDescriptor(uri, "w", null).use {
        }
    : for문으로 권한 체크를 하나씩 해줌 exception이 떨어지면 listUri 권한 허용 팝업 띄우기
    : 처음에 5번으로 권한 허용 팝업을 띄우면서 했는데 소스진행이 exception으로 안떨어지고 진행이 되어 위의소스로 권한 확인함.
  • 파일 권한 여러개 띄우기
    : 처음에 createDeleteRequest로 띄웠었는데 이건 허용이나 거부시 바로 파일이 삭제되는 것 같다.
    : createWriteRequest로 교체하여 권한을 허용함.

문제가 생겼다.. createWriteRequest로 여러개의 파일 권한을 허용하는 것은 성공 했는데 API LEVEL 30에서 새로 추가된 함수이다..

Android 29에선 NosuchMethod(?)Exception이 발생..

 

그래서 WRITE_EXTERNAL_STORAGE를 주고

<uses-permission
     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
     android:maxSdkVersion="29" />

권한을 준뒤에 삭제하니깐 되었다.

 

 

 

 

반응형