diff options
author | Daniel Lublin <daniel@lublin.se> | 2022-02-20 13:21:08 +0300 |
---|---|---|
committer | Daniel Lublin <daniel@lublin.se> | 2022-02-21 15:44:04 +0300 |
commit | b798eec10ec610dda8b21d98e0eecd792a96036b (patch) | |
tree | 4de6722868ce320905104d5e9759ec2745477a54 | |
parent | b482ee03cd7a8210a0a5c9b463d34cac52d73a24 (diff) |
Use SAF on Android >= 30, to deal with Scoped storage
-rw-r--r-- | app/build.gradle | 2 | ||||
-rw-r--r-- | app/src/main/AndroidManifest.xml | 12 | ||||
-rw-r--r-- | app/src/main/java/se/lublin/mumla/preference/CertificateExportActivity.java | 76 |
3 files changed, 66 insertions, 24 deletions
diff --git a/app/build.gradle b/app/build.gradle index 26a5865..c1198f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -137,6 +137,8 @@ dependencies { implementation project(":libraries:humla") implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.documentfile:documentfile:1.0.1' + implementation 'androidx.fragment:fragment:1.3.4' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'org.jsoup:jsoup:1.13.1' implementation 'info.guardianproject.netcipher:netcipher:2.1.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b55176a..30b8373 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" - package="se.lublin.mumla" > + package="se.lublin.mumla"> <uses-feature android:name="android.hardware.microphone" @@ -26,7 +26,9 @@ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission + android:name="android.permission.WRITE_EXTERNAL_STORAGE" + android:maxSdkVersion="29" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.VIBRATE" /> @@ -45,13 +47,13 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/Theme.Mumla" - android:requestLegacyExternalStorage="true" > - <!-- TODO ^^^ fix, on sdk 30, no more requestlegacyexternalstorage ! --> + android:requestLegacyExternalStorage="true"> + <!-- requestLegacyExternalStorage still true: we keep using old code if Android < 30 --> <activity android:name=".wizard.WizardActivity" /> <activity android:name=".preference.Preferences" - android:parentActivityName=".app.MumlaActivity" > + android:parentActivityName=".app.MumlaActivity"> <intent-filter> <action android:name="se.lublin.mumla.app.PREFS_GENERAL" /> diff --git a/app/src/main/java/se/lublin/mumla/preference/CertificateExportActivity.java b/app/src/main/java/se/lublin/mumla/preference/CertificateExportActivity.java index 5dba6a6..5446e79 100644 --- a/app/src/main/java/se/lublin/mumla/preference/CertificateExportActivity.java +++ b/app/src/main/java/se/lublin/mumla/preference/CertificateExportActivity.java @@ -17,25 +17,33 @@ package se.lublin.mumla.preference; +import static android.os.Build.VERSION.SDK_INT; + import android.Manifest; import android.content.DialogInterface; import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.CreateDocument; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import androidx.documentfile.provider.DocumentFile; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.List; import se.lublin.mumla.Constants; @@ -56,8 +64,10 @@ public class CertificateExportActivity extends AppCompatActivity implements Dial private MumlaDatabase mDatabase; private List<DatabaseCertificate> mCertificates; + private final ActivityResultLauncher<String> documentCreator = + registerForActivityResult(new CreateDocument(), this::onDocumentCreated); private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 2; - private DatabaseCertificate mCertificatePendingPerm = null; + private DatabaseCertificate mCertificatePending = null; @Override protected void onCreate(Bundle savedInstanceState) { @@ -91,26 +101,46 @@ public class CertificateExportActivity extends AppCompatActivity implements Dial @Override public void onClick(DialogInterface dialog, int which) { DatabaseCertificate certificate = mCertificates.get(which); - saveCertificate(certificate); + if (SDK_INT >= Build.VERSION_CODES.R) { + // TODO Should always use this method? + mCertificatePending = certificate; + documentCreator.launch(certificate.getName()); + } else { + saveCertificateClassic(certificate); + } } - private void saveCertificate(DatabaseCertificate certificate) { + private void onDocumentCreated(Uri uri) { + if (uri != null && mCertificatePending != null) { + try { + OutputStream os = getContentResolver().openOutputStream(uri); + DocumentFile df = DocumentFile.fromSingleUri(this, uri); + writeCertificate(os, mCertificatePending, df != null ? df.getName() : "<unknown>"); + } catch (FileNotFoundException e) { + showErrorDialog(R.string.externalStorageUnavailable); + Log.w(Constants.TAG, "FileNotFound on output file picked by user?!"); + } + } else if (mCertificatePending == null) { + Log.w(Constants.TAG, "No pending certificate after user picked output file"); + } + finish(); + } + + private void saveCertificateClassic(DatabaseCertificate certificate) { if (ContextCompat.checkSelfPermission(CertificateExportActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(CertificateExportActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); - mCertificatePendingPerm = certificate; + mCertificatePending = certificate; return; } - byte[] data = mDatabase.getCertificateData(certificate.getId()); if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { showErrorDialog(R.string.externalStorageUnavailable); return; } - File storageDirectory = Environment.getExternalStorageDirectory(); File mumlaDirectory = new File(storageDirectory, EXTERNAL_STORAGE_DIR); if (!mumlaDirectory.exists() && !mumlaDirectory.mkdir()) { @@ -118,20 +148,15 @@ public class CertificateExportActivity extends AppCompatActivity implements Dial return; } File outputFile = new File(mumlaDirectory, certificate.getName()); + FileOutputStream fos; try { - FileOutputStream fos = new FileOutputStream(outputFile); - BufferedOutputStream bos = new BufferedOutputStream(fos); - bos.write(data); - bos.close(); - - Toast.makeText(this, getString(R.string.export_success, outputFile.getAbsolutePath()), Toast.LENGTH_LONG).show(); - finish(); + fos = new FileOutputStream(outputFile); } catch (FileNotFoundException e) { showErrorDialog(R.string.externalStorageUnavailable); - } catch (IOException e) { - e.printStackTrace(); - showErrorDialog(R.string.error_writing_to_storage); + return; } + writeCertificate(fos, certificate, outputFile.getAbsolutePath()); + finish(); } @Override @@ -140,8 +165,8 @@ public class CertificateExportActivity extends AppCompatActivity implements Dial super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (mCertificatePendingPerm != null) { - saveCertificate(mCertificatePendingPerm); + if (mCertificatePending != null) { + saveCertificateClassic(mCertificatePending); } else { Log.w(Constants.TAG, "No pending certificate after permission was granted"); } @@ -149,7 +174,20 @@ public class CertificateExportActivity extends AppCompatActivity implements Dial Toast.makeText(CertificateExportActivity.this, getString(R.string.grant_perm_storage), Toast.LENGTH_LONG).show(); } - mCertificatePendingPerm = null; + mCertificatePending = null; + } + } + + private void writeCertificate(OutputStream fos, DatabaseCertificate cert, String path) { + byte[] data = mDatabase.getCertificateData(cert.getId()); + try { + BufferedOutputStream bos = new BufferedOutputStream(fos); + bos.write(data); + bos.close(); + Toast.makeText(this, getString(R.string.export_success, path), Toast.LENGTH_LONG).show(); + } catch (IOException e) { + e.printStackTrace(); + showErrorDialog(R.string.error_writing_to_storage); } } |