본문 바로가기

Android

[Android,Java] 안드로이드13 , SDK33 타겟 시 파일첨부 및 퍼미션 해결

안드로이드 sdk33 으로 타겟을 해야 업데이트가 진행된다는 구글 플레이스토어 이슈로 

일주일정도 삽질후에 해결하였다 후... 

라이브러리를 사용하여 쉽게 처리하려 했지만 라이브러리가 해당 이슈에 대응되지 않아서 

직접 작성을 하였다 

 

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");

fileActivity.launch(intent);

 

우선 SAF 를 이용하여 파일을 선택하도록 작성하였다.

타입은 원하는대로 작성하여 원하는 파일만 보여지도록도 가능하다.

 

ActivityResultLauncher<Intent> fileActivity2 = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
        if (result.getResultCode() == Activity.RESULT_OK ) {

            Intent resultData = result.getData();

            Uri uri = null;
            if (resultData != null) {
                uri = resultData.getData();
                String path = Utils.getPath(this,uri);

                File file = new File(path);
                tv_file_select.setText(file.getName());
                file_path = path;
            }
        }
    });

파일을 선택한뒤 결과를 받아서 Utils.getPath 를 이용해서 해당 파일의 path를 받아오게 처리했다 

 

Utils 는 프로젝트를 진행하며 만든 유틸 클래스이며 아래가 작성된 소스이다 

여기서 핵심은 파일을 어플리케이션의 캐시 저장소에 복사를 해서 사용하는 방법으로 해보았다

(SAF 를 사용해서 파일을 선택할때 최근, 다운로드 , 문서 탭을 이용한뒤 파일을 선택하면 uri가 동일한 파일이어도 다르게 나와서

파일에 접근을 못하는 경우가 생겨서 uri를 이용해서 해당 앱의 캐시영역으로 복사해서 path를 가져오는 방식으로 처리했다)

 

String fileName = getFileName(context, uri);

final File file = new File(context.getCacheDir(), fileName);
try (final InputStream inputStream = context.getContentResolver().openInputStream(uri);
     OutputStream output = new FileOutputStream(file)) {
    // You may need to change buffer size. I use large buffer size to help loading large file , but be ware of
    //  OutOfMemory Exception
    final byte[] buffer = new byte[8 * 1024];
    int read;

    while ((read = inputStream.read(buffer)) != -1) {
        output.write(buffer, 0, read);
    }

    output.flush();
    return file.getPath();
} catch (IOException ex) {
    ex.printStackTrace();
}

위에서 fileName은 실제 파일의 이름을 사용하게 해도되 직접 입력해서 바꾸어도 된다.

캐시 영역에 해당 이름으로 선택한 파일의 uri를 복사하는 개념이다.

아래는 파일을 사용하기 위한 Utils의 소스코드이다.

 

    public static String getPath(final Context context, final Uri uri) {

        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    String[] sp = split[1].split("/");
                    final File file = new File(context.getCacheDir(),sp[sp.length-1] );
                    try (final InputStream inputStream = context.getContentResolver().openInputStream(uri);
                         OutputStream output = new FileOutputStream(file)) {
                        // You may need to change buffer size. I use large buffer size to help loading large file , but be ware of
                        //  OutOfMemory Exception
                        final byte[] buffer = new byte[8 * 1024];
                        int read;

                        while ((read = inputStream.read(buffer)) != -1) {
                            output.write(buffer, 0, read);
                        }

                        output.flush();
                        return file.getPath();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }

                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {
                String fileName = getFileName(context, uri);

                if (fileName != null) {
                    final File file = new File(context.getCacheDir(), fileName);
                    try (final InputStream inputStream = context.getContentResolver().openInputStream(uri);
                         OutputStream output = new FileOutputStream(file)) {
                        // You may need to change buffer size. I use large buffer size to help loading large file , but be ware of
                        //  OutOfMemory Exception
                        final byte[] buffer = new byte[8 * 1024];
                        int read;

                        while ((read = inputStream.read(buffer)) != -1) {
                            output.write(buffer, 0, read);
                        }

                        output.flush();
                        return file.getPath();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }

                final String id = DocumentsContract.getDocumentId(uri);
                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && id.startsWith("msf:")){
                    final String[] split = id.split(":");
                    final String selection = "_id=?";
                    final String[] selectionArgs = new String[] { split[1] };
                    return getDataColumn(context, MediaStore.Downloads.EXTERNAL_CONTENT_URI, selection, selectionArgs);

                }
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                } else if ("document".equals(type)) {
                    String fileName = getFileName(context, uri);

                    final File file = new File(context.getCacheDir(), fileName);
                    try (final InputStream inputStream = context.getContentResolver().openInputStream(uri);
                         OutputStream output = new FileOutputStream(file)) {
                        // You may need to change buffer size. I use large buffer size to help loading large file , but be ware of
                        //  OutOfMemory Exception
                        final byte[] buffer = new byte[8 * 1024];
                        int read;

                        while ((read = inputStream.read(buffer)) != -1) {
                            output.write(buffer, 0, read);
                        }

                        output.flush();
                        return file.getPath();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }


                    return getFileName(context,uri);
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }


    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }
    
    private static String failReason;
    private static String getFileName(Context context, Uri uri) {
        Cursor cursor = null;
        final String[] projection = {MediaStore.Files.FileColumns.DISPLAY_NAME};
        try {
            cursor = context.getContentResolver().query(uri, projection, null, null,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int index = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME);
                Log.e("===cursor.toString===",cursor.toString());
                return cursor.getString(index);
            }
        }catch (Exception e) {
            failReason = e.getMessage();
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

 

이렇게하니 공용폴더 , 외부저장소 의 파일들은 선택해서 업로드하는게 가능해졌다 

모든 파일 엑세스 권한이 없어도 가능하게 소스를 변경해서 다시 마켓에 업로드를 진행하였다