From d452255b623b4c6d98b5aa05848f8005f9fea3f8 Mon Sep 17 00:00:00 2001 From: Olga Miller Date: Mon, 22 Jun 2020 23:33:26 +0200 Subject: [PATCH] Support writing of wav-file also in Android 10 --- app/src/main/AndroidManifest.xml | 3 +- app/src/main/java/om/sstvencoder/Encoder.java | 14 +-- .../java/om/sstvencoder/MainActivity.java | 23 ++-- .../om/sstvencoder/MainActivityMessenger.java | 6 +- .../om/sstvencoder/Output/OutputFactory.java | 9 +- .../om/sstvencoder/Output/WaveFileOutput.java | 17 ++- .../Output/WaveFileOutputContext.java | 101 ++++++++++++++++++ app/src/main/java/om/sstvencoder/Utility.java | 24 +---- 8 files changed, 135 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/om/sstvencoder/Output/WaveFileOutputContext.java 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); }