Use scope storage with API >= 29 for OPML export

This commit is contained in:
Shinokuni 2020-08-23 21:51:09 +02:00
parent 594a0a67fa
commit 76d5e29589
2 changed files with 87 additions and 38 deletions

View File

@ -8,7 +8,6 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
@ -29,6 +28,7 @@ import com.readrops.app.ReadropsApp;
import com.readrops.app.activities.AddAccountActivity; import com.readrops.app.activities.AddAccountActivity;
import com.readrops.app.activities.ManageFeedsFoldersActivity; import com.readrops.app.activities.ManageFeedsFoldersActivity;
import com.readrops.app.activities.NotificationPermissionActivity; import com.readrops.app.activities.NotificationPermissionActivity;
import com.readrops.app.utils.FileUtils;
import com.readrops.app.utils.PermissionManager; import com.readrops.app.utils.PermissionManager;
import com.readrops.app.utils.SharedPreferencesManager; import com.readrops.app.utils.SharedPreferencesManager;
import com.readrops.app.utils.Utils; import com.readrops.app.utils.Utils;
@ -36,14 +36,10 @@ import com.readrops.app.viewmodels.AccountViewModel;
import com.readrops.db.entities.account.Account; import com.readrops.db.entities.account.Account;
import com.readrops.db.entities.account.AccountType; import com.readrops.db.entities.account.AccountType;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableCompletableObserver; import io.reactivex.observers.DisposableCompletableObserver;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import kotlin.Unit;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static com.readrops.api.opml.OPMLHelper.OPEN_OPML_FILE_REQUEST; import static com.readrops.api.opml.OPMLHelper.OPEN_OPML_FILE_REQUEST;
@ -231,37 +227,21 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
//region opml export //region opml export
private void exportAsOPMLFile() { private void exportAsOPMLFile() {
String fileName = "subscriptions.opml";
try { try {
String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); String path = FileUtils.writeDownloadFile(getContext(), fileName, "text/xml", outputStream -> {
File file = new File(filePath, "subscriptions.opml"); viewModel.getFoldersWithFeeds()
.flatMapCompletable(folderListMap -> OPMLParser.write(folderListMap, outputStream))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(e -> Utils.showSnackbar(getView(), e.getMessage()))
.subscribe();
final OutputStream outputStream = new FileOutputStream(file); return Unit.INSTANCE;
});
viewModel.getFoldersWithFeeds() displayNotification(fileName, path);
.flatMapCompletable(folderListMap -> OPMLParser.write(folderListMap, outputStream))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doAfterTerminate(() -> {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
Utils.showSnackbar(getView(), e.getMessage());
}
})
.subscribe(new DisposableCompletableObserver() {
@Override
public void onComplete() {
displayNotification(file);
}
@Override
public void onError(Throwable e) {
Utils.showSnackbar(getView(), e.getMessage());
}
});
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, e.getMessage()); Log.e(TAG, e.getMessage());
Utils.showSnackbar(getView(), e.getMessage()); Utils.showSnackbar(getView(), e.getMessage());
@ -269,13 +249,13 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
} }
private void displayNotification(File file) { private void displayNotification(String name, String absolutePath) {
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(file.getAbsolutePath()), "text/plain"); intent.setDataAndType(Uri.parse(absolutePath), "text/plain");
Notification notification = new NotificationCompat.Builder(getContext(), ReadropsApp.OPML_EXPORT_CHANNEL_ID) Notification notification = new NotificationCompat.Builder(getContext(), ReadropsApp.OPML_EXPORT_CHANNEL_ID)
.setContentTitle(getString(R.string.opml_export)) .setContentTitle(getString(R.string.opml_export))
.setContentText(file.getName()) .setContentText(name)
.setSmallIcon(R.drawable.ic_notif) .setSmallIcon(R.drawable.ic_notif)
.setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)) .setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
.setAutoCancel(true) .setAutoCancel(true)

View File

@ -0,0 +1,69 @@
package com.readrops.app.utils
import android.content.ContentValues
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
object FileUtils {
@JvmStatic
fun writeDownloadFile(context: Context, fileName: String, mimeType: String, listener: (OutputStream) -> Unit): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
writeFileApi29(context, fileName, mimeType, listener)
else
writeFileApi28(fileName, listener)
}
@RequiresApi(Build.VERSION_CODES.Q)
private fun writeFileApi29(context: Context, fileName: String, mimeType: String, listener: (OutputStream) -> Unit): String {
val resolver = context.contentResolver
val downloadsUri = MediaStore.Downloads
.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val fileDetails = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
put(MediaStore.Downloads.IS_PENDING, 1)
put(MediaStore.Downloads.MIME_TYPE, mimeType)
}
val contentUri = resolver.insert(downloadsUri, fileDetails)
resolver.openFileDescriptor(contentUri!!, "w", null).use { pfd ->
val outputStream = FileOutputStream(pfd?.fileDescriptor!!)
try {
listener(outputStream)
} catch (e: Exception) {
throw e
} finally {
outputStream.flush()
outputStream.close()
}
}
fileDetails.clear()
fileDetails.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(contentUri, fileDetails, null, null)
return contentUri.path!!
}
private fun writeFileApi28(fileName: String, listener: (OutputStream) -> Unit): String {
val filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath
val file = File(filePath, fileName)
val outputStream = FileOutputStream(file)
listener(outputStream)
outputStream.flush()
outputStream.close()
return file.absolutePath
}
}