diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9e9d219..5ff35fc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,7 +3,8 @@
package="om.sstvencoder">
+ android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="28"/>
diff --git a/app/src/main/java/om/sstvencoder/Encoder.java b/app/src/main/java/om/sstvencoder/Encoder.java
index 2dda182..8050516 100644
--- a/app/src/main/java/om/sstvencoder/Encoder.java
+++ b/app/src/main/java/om/sstvencoder/Encoder.java
@@ -17,7 +17,6 @@ package om.sstvencoder;
import android.graphics.Bitmap;
-import java.io.File;
import java.util.LinkedList;
import java.util.List;
@@ -26,6 +25,7 @@ import om.sstvencoder.ModeInterfaces.IModeInfo;
import om.sstvencoder.Modes.ModeFactory;
import om.sstvencoder.Output.IOutput;
import om.sstvencoder.Output.OutputFactory;
+import om.sstvencoder.Output.WaveFileOutputContext;
// Creates IMode instance
class Encoder {
@@ -108,21 +108,21 @@ class Encoder {
enqueue(mode);
}
- void save(Bitmap bitmap, File file) {
+ void save(Bitmap bitmap, WaveFileOutputContext context) {
if (mSaveWaveThread != null && mSaveWaveThread.isAlive())
return;
- IOutput output = OutputFactory.createOutputForSavingAsWave(file);
+ IOutput output = OutputFactory.createOutputForSavingAsWave(context);
IMode mode = ModeFactory.CreateMode(mModeClass, bitmap, output);
if (mode != null)
- save(mode, file);
+ save(mode, context);
}
- private void save(final IMode mode, final File file) {
+ private void save(final IMode mode, final WaveFileOutputContext context) {
mSaveWaveThread = new Thread() {
@Override
public void run() {
mode.init();
- mProgressBar2.begin(mode.getProcessCount(), file.getName() + " saving...");
+ mProgressBar2.begin(mode.getProcessCount(), context.getFileName() + " saving...");
while (mode.process()) {
mProgressBar2.step();
@@ -135,7 +135,7 @@ class Encoder {
mode.finish(mQuit);
mProgressBar2.end();
if (!mQuit)
- mMessenger.carrySaveAsWaveIsDoneMessage(file);
+ mMessenger.carrySaveAsWaveIsDoneMessage(context);
}
};
mSaveWaveThread.start();
diff --git a/app/src/main/java/om/sstvencoder/MainActivity.java b/app/src/main/java/om/sstvencoder/MainActivity.java
index 48cfb33..5c95905 100644
--- a/app/src/main/java/om/sstvencoder/MainActivity.java
+++ b/app/src/main/java/om/sstvencoder/MainActivity.java
@@ -19,7 +19,6 @@ import android.Manifest;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -43,10 +42,10 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
-import java.io.File;
import java.io.InputStream;
import om.sstvencoder.ModeInterfaces.IModeInfo;
+import om.sstvencoder.Output.WaveFileOutputContext;
import om.sstvencoder.TextOverlay.Label;
public class MainActivity extends AppCompatActivity {
@@ -191,7 +190,8 @@ public class MainActivity extends AppCompatActivity {
}
private boolean needsRequestWritePermission() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
return false;
String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
int state = ContextCompat.checkSelfPermission(this, permission);
@@ -443,18 +443,15 @@ public class MainActivity extends AppCompatActivity {
}
private void save() {
- File file = Utility.createWaveFilePath();
- mEncoder.save(mCropView.getBitmap(), file);
+ if (Utility.isExternalStorageWritable()) {
+ WaveFileOutputContext context
+ = new WaveFileOutputContext(getContentResolver(), Utility.createWaveFileName());
+ mEncoder.save(mCropView.getBitmap(), context);
+ }
}
- public void completeSaving(File file) {
- addFileToContentResolver(file);
- }
-
- private void addFileToContentResolver(File file) {
- ContentValues values = Utility.getWavContentValues(file);
- Uri uri = MediaStore.Audio.Media.getContentUriForPath(file.getAbsolutePath());
- getContentResolver().insert(uri, values);
+ public void completeSaving(WaveFileOutputContext context) {
+ context.clear();
}
@Override
diff --git a/app/src/main/java/om/sstvencoder/MainActivityMessenger.java b/app/src/main/java/om/sstvencoder/MainActivityMessenger.java
index e7216e8..bbc6140 100644
--- a/app/src/main/java/om/sstvencoder/MainActivityMessenger.java
+++ b/app/src/main/java/om/sstvencoder/MainActivityMessenger.java
@@ -17,7 +17,7 @@ package om.sstvencoder;
import android.os.Handler;
-import java.io.File;
+import om.sstvencoder.Output.WaveFileOutputContext;
class MainActivityMessenger {
private final MainActivity mMainActivity;
@@ -28,11 +28,11 @@ class MainActivityMessenger {
mHandler = new Handler();
}
- void carrySaveAsWaveIsDoneMessage(final File file) {
+ void carrySaveAsWaveIsDoneMessage(final WaveFileOutputContext context) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mMainActivity.completeSaving(file);
+ mMainActivity.completeSaving(context);
}
});
}
diff --git a/app/src/main/java/om/sstvencoder/Output/OutputFactory.java b/app/src/main/java/om/sstvencoder/Output/OutputFactory.java
index d5a25d9..ad63cc1 100644
--- a/app/src/main/java/om/sstvencoder/Output/OutputFactory.java
+++ b/app/src/main/java/om/sstvencoder/Output/OutputFactory.java
@@ -15,8 +15,6 @@ limitations under the License.
*/
package om.sstvencoder.Output;
-import java.io.File;
-
public final class OutputFactory {
public static IOutput createOutputForSending() {
@@ -24,11 +22,8 @@ public final class OutputFactory {
return new AudioOutput(sampleRate);
}
- public static IOutput createOutputForSavingAsWave(File filePath) {
+ public static IOutput createOutputForSavingAsWave(WaveFileOutputContext context) {
double sampleRate = 44100.0;
-
- if (filePath == null)
- return null;
- return new WaveFileOutput(filePath, sampleRate);
+ return new WaveFileOutput(context, sampleRate);
}
}
diff --git a/app/src/main/java/om/sstvencoder/Output/WaveFileOutput.java b/app/src/main/java/om/sstvencoder/Output/WaveFileOutput.java
index 9b4fdfd..e627408 100644
--- a/app/src/main/java/om/sstvencoder/Output/WaveFileOutput.java
+++ b/app/src/main/java/om/sstvencoder/Output/WaveFileOutput.java
@@ -16,17 +16,15 @@ limitations under the License.
package om.sstvencoder.Output;
import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
class WaveFileOutput implements IOutput {
private final double mSampleRate;
- private File mFile;
+ private WaveFileOutputContext mContext;
private BufferedOutputStream mOutputStream;
private int mSamples, mWrittenSamples;
- WaveFileOutput(File file, double sampleRate) {
- mFile = file;
+ WaveFileOutput(WaveFileOutputContext context, double sampleRate) {
+ mContext = context;
mSampleRate = sampleRate;
}
@@ -67,7 +65,7 @@ class WaveFileOutput implements IOutput {
private void InitOutputStream() {
try {
- mOutputStream = new BufferedOutputStream(new FileOutputStream(mFile));
+ mOutputStream = new BufferedOutputStream(mContext.createWaveOutputStream());
} catch (Exception ignore) {
}
}
@@ -98,11 +96,8 @@ class WaveFileOutput implements IOutput {
} catch (Exception ignore) {
}
- if (mFile != null) {
- if (cancel)
- mFile.delete();
- mFile = null;
- }
+ if (cancel)
+ mContext.deleteFile();
}
private void padWithZeros(int count) {
diff --git a/app/src/main/java/om/sstvencoder/Output/WaveFileOutputContext.java b/app/src/main/java/om/sstvencoder/Output/WaveFileOutputContext.java
new file mode 100644
index 0000000..74a4a96
--- /dev/null
+++ b/app/src/main/java/om/sstvencoder/Output/WaveFileOutputContext.java
@@ -0,0 +1,101 @@
+/*
+Copyright 2020 Olga Miller
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package om.sstvencoder.Output;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.MediaStore;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+public class WaveFileOutputContext {
+ private ContentResolver mContentResolver;
+ private String mFileName;
+ private File mFile;
+ private Uri mUri;
+ private ContentValues mValues;
+
+ public WaveFileOutputContext(ContentResolver contentResolver, String fileName) {
+ mContentResolver = contentResolver;
+ mFileName = fileName;
+ }
+
+ public String getFileName() {
+ return mFileName;
+ }
+
+ public OutputStream createWaveOutputStream() {
+ if (init()) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ return mContentResolver.openOutputStream(mUri);
+ else
+ return new FileOutputStream(mFile);
+ } catch (Exception ignore) {
+ }
+ }
+ return null;
+ }
+
+ private boolean init() {
+ String album = "SSTV Encoder";
+ mValues = new ContentValues();
+ mValues.put(MediaStore.Audio.Media.ALBUM, album);
+ mValues.put(MediaStore.Audio.Media.MIME_TYPE, "audio/wav");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ mValues.put(MediaStore.Audio.Media.DISPLAY_NAME, mFileName);
+ mValues.put(MediaStore.Audio.Media.RELATIVE_PATH, (new File(Environment.DIRECTORY_MUSIC, album)).getPath());
+ mValues.put(MediaStore.Audio.Media.IS_PENDING, 1);
+ mUri = mContentResolver.insert(MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), mValues);
+ if (mUri != null) {
+ String path = mUri.getPath();
+ if (path != null)
+ mFile = new File(path);
+ }
+ } else {
+ mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC), mFileName);
+ mValues.put(MediaStore.Audio.Media.DATA, mFile.toString());
+ mValues.put(MediaStore.Audio.Media.TITLE, mFileName);
+ mValues.put(MediaStore.Audio.Media.IS_MUSIC, true);
+ mUri = MediaStore.Audio.Media.getContentUriForPath(mFile.getAbsolutePath());
+ if (mUri != null)
+ mContentResolver.insert(mUri, mValues);
+ }
+ return mUri != null;
+ }
+
+ public void clear() {
+ if (mUri != null && mValues != null) {
+ mValues.clear();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ mValues.put(MediaStore.Audio.Media.IS_PENDING, 0);
+ mContentResolver.update(mUri, mValues, null, null);
+ }
+ }
+
+ public void deleteFile() {
+ try {
+ if (mFile != null)
+ mFile.delete();
+ } catch (Exception ignore) {
+ }
+ }
+}
diff --git a/app/src/main/java/om/sstvencoder/Utility.java b/app/src/main/java/om/sstvencoder/Utility.java
index 40df99b..04acd4b 100644
--- a/app/src/main/java/om/sstvencoder/Utility.java
+++ b/app/src/main/java/om/sstvencoder/Utility.java
@@ -15,7 +15,6 @@ limitations under the License.
*/
package om.sstvencoder;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
@@ -23,7 +22,7 @@ import androidx.exifinterface.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
-import android.provider.MediaStore;
+
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
@@ -89,18 +88,6 @@ final class Utility {
return 0;
}
- @NonNull
- static ContentValues getWavContentValues(File file) {
- ContentValues values = new ContentValues();
- values.put(MediaStore.Audio.Media.ALBUM, "SSTV Encoder");
- values.put(MediaStore.Audio.Media.ARTIST, "");
- values.put(MediaStore.Audio.Media.DATA, file.toString());
- values.put(MediaStore.Audio.Media.IS_MUSIC, true);
- values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/wav");
- values.put(MediaStore.Audio.Media.TITLE, file.getName());
- return values;
- }
-
static Uri createImageUri(Context context) {
if (!isExternalStorageWritable())
return null;
@@ -114,18 +101,15 @@ final class Utility {
return FileProvider.getUriForFile(context, "om.sstvencoder", file); // content:// URI
}
- static File createWaveFilePath() {
- if (!isExternalStorageWritable())
- return null;
- File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
- return new File(dir, createFileName() + ".wav");
+ static String createWaveFileName() {
+ return createFileName() + ".wav";
}
private static String createFileName() {
return new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
}
- private static boolean isExternalStorageWritable() {
+ static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}