Android Q (API:29)以上でgetExternalStorageDirectoryに保存ができない

Googleさんのポリシーの変更によりファイルの書き込みや読み込みの権限が厳しくなった

一応暫定処理でAndroidManifest.xmlに

android:requestLegacyExternalStorage="true"

を追加することで使うことはできたのだが、とうとうAndroid API 30以上では完全に書き込みが出来なくなってしまった。

もしAndroid API 30以上で今までのコードのままで使う場合はAndroidManifest.xmlに

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

を追加し、全てのファイルのアクセス権限をもらえば、getExternalStorageDirectoryに書き込める。でもこれだとGoogle Playにアップロードする場合MANAGE_EXTERNAL_STORAGEの権限は基本的にもらえないのでAPKの更新が出来なくってしまう(申請したが弾かれた)

非常に困った。

とりあえず今のところ、MediaStore経由で保存するしかなさそう

ということでJavaコードで対応させてみた

@RequiresApi(Build.VERSION_CODES.Q)
private String saveFileUsingMediaStore(Context context , String url , String fileName, String mimeType) {
    //https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
    ContentValues contentValues = new ContentValues();
    contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
    contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
    contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);

    ContentResolver resolver = context.getContentResolver();
    Uri uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
    if (uri != null) {
        InputStream is = null;
        OutputStream os = null;
        try {
            is = new FileInputStream(url);
            os = resolver.openOutputStream(uri);
            inputCopy(is, os);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // ignore
                }
            }
            if(os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    // ignore
                }
            }
            new File(url).delete();//delete original file
        }
        return getPathURI(context,uri);
    }
    return null;
}
public static String getPathURI(Context context, Uri uri) {
    ContentResolver contentResolver = context.getContentResolver();
    String[] columns = { MediaStore.Downloads.DATA };
    Cursor cursor = contentResolver.query(uri, columns, null, null, null);
    cursor.moveToFirst();
    String path = cursor.getString(0);
    cursor.close();
    return path;
}
void inputCopy(InputStream source, OutputStream target) throws IOException {
    byte[] buf = new byte[8192];
    int length;
    while ((length = source.read(buf)) > 0) {
        target.write(buf, 0, length);
    }
}