add MediaStore.Audio.Media.RECORD_SOUND_ACTION handler, thanks @pvagner for examples!

This commit is contained in:
Alexey Kuznetsov 2019-11-02 19:02:48 +03:00
commit 1b2f5a5eb2
7 changed files with 176 additions and 63 deletions

View file

@ -53,6 +53,6 @@ android {
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'com.github.axet:android-audio-library:1.0.169' // implementation project(':android-audio-library')
implementation 'com.github.axet:android-audio-library:1.0.171' // implementation project(':android-audio-library')
assets('com.google.android.exoplayer:exoplayer:2.7.3') { exclude group: 'com.android.support' }
}

View file

@ -58,7 +58,12 @@
android:configChanges="orientation|keyboardHidden|screenSize"
android:exported="true"
android:launchMode="singleInstance"
android:showOnLockScreen="true" />
android:showOnLockScreen="true">
<intent-filter>
<action android:name="android.provider.MediaStore.RECORD_SOUND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<receiver
android:name=".services.OnBootReceiver"

View file

@ -32,10 +32,10 @@ import com.github.axet.androidlibrary.activities.AppCompatThemeActivity;
import com.github.axet.androidlibrary.widgets.ErrorDialog;
import com.github.axet.androidlibrary.preferences.OptimizationPreferenceCompat;
import com.github.axet.androidlibrary.widgets.SearchView;
import com.github.axet.audiolibrary.app.Storage;
import com.github.axet.audiorecorder.R;
import com.github.axet.audiorecorder.app.AudioApplication;
import com.github.axet.audiorecorder.app.Recordings;
import com.github.axet.audiorecorder.app.Storage;
import com.github.axet.audiorecorder.services.RecordingService;
public class MainActivity extends AppCompatThemeActivity {

View file

@ -4,17 +4,20 @@ import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.media.AudioFormat;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.support.v4.media.session.MediaButtonReceiver;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
@ -34,9 +37,11 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.github.axet.androidlibrary.animations.MarginBottomAnimation;
import com.github.axet.androidlibrary.sound.AudioTrack;
import com.github.axet.androidlibrary.activities.AppCompatThemeActivity;
import com.github.axet.androidlibrary.animations.MarginBottomAnimation;
import com.github.axet.androidlibrary.services.FileProvider;
import com.github.axet.androidlibrary.services.StorageProvider;
import com.github.axet.androidlibrary.sound.AudioTrack;
import com.github.axet.androidlibrary.widgets.ErrorDialog;
import com.github.axet.androidlibrary.widgets.OpenFileDialog;
import com.github.axet.androidlibrary.widgets.PopupWindowCompat;
@ -75,12 +80,13 @@ public class RecordingActivity extends AppCompatThemeActivity {
public static final String PAUSE_BUTTON = RecordingActivity.class.getCanonicalName() + ".PAUSE_BUTTON";
public static final String ACTION_FINISH_RECORDING = BuildConfig.APPLICATION_ID + ".STOP_RECORDING";
public static String START_RECORDING = RecordingService.class.getCanonicalName() + ".START_RECORDING";
public static String STOP_RECORDING = RecordingService.class.getCanonicalName() + ".STOP_RECORDING";
public static final String START_RECORDING = RecordingService.class.getCanonicalName() + ".START_RECORDING";
public static final String STOP_RECORDING = RecordingService.class.getCanonicalName() + ".STOP_RECORDING";
PhoneStateChangeListener pscl = new PhoneStateChangeListener();
FileEncoder encoder;
MediaSessionCompat msc;
Intent recordSoundIntent = null;
boolean start = true; // do we need to start recording immidiatly?
@ -208,6 +214,7 @@ public class RecordingActivity extends AppCompatThemeActivity {
touchListener(w);
}
@SuppressWarnings("RestrictedApi")
public void touchListener(final Window w) {
final Window.Callback c = w.getCallback();
w.setCallback(new WindowCallbackWrapper(c) {
@ -404,37 +411,14 @@ public class RecordingActivity extends AppCompatThemeActivity {
receiver.filter.addAction(ACTION_FINISH_RECORDING);
receiver.registerReceiver(this);
AudioApplication app = AudioApplication.from(this);
try {
if (app.recording == null)
app.recording = new AudioApplication.RecordingStorage(this, pitch.getPitchTime());
recording = app.recording;
synchronized (recording.handlers) {
recording.handlers.add(handler);
}
} catch (RuntimeException e) {
Log.d(TAG, "onCreate", e);
Toast.Error(this, e);
finish();
return;
}
sendBroadcast(new Intent(START_RECORDING));
edit(false, false);
title.setText(Storage.getName(this, recording.targetUri));
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(this);
if (shared.getBoolean(AudioApplication.PREFERENCE_CALL, false)) {
TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
tm.listen(pscl, PhoneStateListener.LISTEN_CALL_STATE);
}
recording.updateBufferSize(false);
loadSamples();
final View cancel = findViewById(R.id.recording_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
@ -482,15 +466,19 @@ public class RecordingActivity extends AppCompatThemeActivity {
if (encoder != null)
return;
String msg;
if (shared.getBoolean(AudioApplication.PREFERENCE_FLY, false)) {
if (shared.getBoolean(AudioApplication.PREFERENCE_FLY, false))
msg = getString(R.string.recording_status_recording);
} else
else
msg = getString(R.string.recording_status_encoding);
stopRecording(msg);
try {
encoding(new Runnable() {
@Override
public void run() {
if (recordSoundIntent != null) {
recordSoundIntent.setDataAndType(StorageProvider.getProvider().share(recording.targetUri), Storage.getTypeByExt(Storage.getExt(RecordingActivity.this, recording.targetUri)));
FileProvider.grantPermissions(RecordingActivity.this, recordSoundIntent, FileProvider.RW);
}
finish();
}
});
@ -500,20 +488,83 @@ public class RecordingActivity extends AppCompatThemeActivity {
}
});
onCreateRecording();
Intent intent = getIntent();
String a = intent.getAction();
if (a != null && a.equals(START_PAUSE)) { // pretend we already start it
start = false;
stopRecording(getString(R.string.recording_status_pause));
}
if (a != null && a.equals(ERROR))
muted = new ErrorDialog(this, intent.getStringExtra("msg")).setTitle(intent.getStringExtra("title")).show();
onIntent(intent);
}
public void onCreateRecording() {
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(this);
Intent intent = getIntent();
String a = intent.getAction();
AudioApplication app = AudioApplication.from(this);
try {
if (app.recording == null) {
Uri targetUri = null;
Storage storage = new Storage(this);
if (a != null && a.equals(MediaStore.Audio.Media.RECORD_SOUND_ACTION)) {
if (storage.recordingPending()) {
String file = shared.getString(AudioApplication.PREFERENCE_TARGET, null);
if (file != null) // else pending recording comes from intent recording, resume recording
throw new RuntimeException("finish pending recording first");
}
targetUri = storage.getNewIntentRecording();
recordSoundIntent = new Intent();
} else {
if (storage.recordingPending()) {
String file = shared.getString(AudioApplication.PREFERENCE_TARGET, null);
if (file != null) {
if (file.startsWith(ContentResolver.SCHEME_CONTENT))
targetUri = Uri.parse(file);
else if (file.startsWith(ContentResolver.SCHEME_FILE))
targetUri = Uri.parse(file);
else
targetUri = Uri.fromFile(new File(file));
}
}
if (targetUri == null)
targetUri = storage.getNewFile();
SharedPreferences.Editor editor = shared.edit();
editor.putString(AudioApplication.PREFERENCE_TARGET, targetUri.toString());
editor.commit();
}
Log.d(TAG, "create recording at: " + targetUri);
app.recording = new AudioApplication.RecordingStorage(this, pitch.getPitchTime(), targetUri);
}
recording = app.recording;
synchronized (recording.handlers) {
recording.handlers.add(handler);
}
} catch (RuntimeException e) {
Toast.Error(this, e);
finish();
return;
}
sendBroadcast(new Intent(START_RECORDING));
title.setText(Storage.getName(this, recording.targetUri));
recording.updateBufferSize(false);
loadSamples();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
onIntent(intent);
}
public void onIntent(Intent intent) {
String a = intent.getAction();
if (a != null && a.equals(ERROR))
muted = new ErrorDialog(this, intent.getStringExtra("msg")).setTitle(intent.getStringExtra("title")).show();
}
void loadSamples() {
@ -1013,8 +1064,16 @@ public class RecordingActivity extends AppCompatThemeActivity {
@Override
public void finish() {
super.finish();
MainActivity.startActivity(this);
if (recordSoundIntent != null) {
if (recordSoundIntent.getData() == null)
setResult(RESULT_CANCELED);
else
setResult(Activity.RESULT_OK, recordSoundIntent);
super.finish();
} else {
super.finish();
MainActivity.startActivity(this);
}
}
public void headset(boolean b, final boolean recording) {

View file

@ -85,34 +85,15 @@ public class AudioApplication extends com.github.axet.audiolibrary.app.MainAppli
public int pitchTime; // screen width
public RecordingStorage(Context context, int pitchTime) {
public RecordingStorage(Context context, int pitchTime, Uri targetUri) {
this.context = context;
this.pitchTime = pitchTime;
this.targetUri = targetUri;
storage = new Storage(context);
sound = new Sound(context);
sampleRate = Sound.getSampleRate(context);
samplesUpdate = (int) (pitchTime * sampleRate / 1000f);
samplesUpdateStereo = samplesUpdate * Sound.getChannels(context);
final SharedPreferences shared = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
if (storage.recordingPending()) {
String file = shared.getString(AudioApplication.PREFERENCE_TARGET, null);
if (file != null) {
if (file.startsWith(ContentResolver.SCHEME_CONTENT))
targetUri = Uri.parse(file);
else if (file.startsWith(ContentResolver.SCHEME_FILE))
targetUri = Uri.parse(file);
else
targetUri = Uri.fromFile(new File(file));
}
}
if (targetUri == null)
targetUri = storage.getNewFile();
SharedPreferences.Editor editor = shared.edit();
editor.putString(AudioApplication.PREFERENCE_TARGET, targetUri.toString());
editor.commit();
}
public void startRecording() {

View file

@ -7,6 +7,8 @@ import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import com.github.axet.androidlibrary.services.StorageProvider;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
@ -15,6 +17,8 @@ import java.util.TimeZone;
public class Storage extends com.github.axet.audiolibrary.app.Storage {
public static final String SHARE = "share";
public static final SimpleDateFormat ISO8601Z = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US) {{
setTimeZone(TimeZone.getTimeZone("UTC"));
}};
@ -36,9 +40,7 @@ public class Storage extends com.github.axet.audiolibrary.app.Storage {
SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
String ext = shared.getString(AudioApplication.PREFERENCE_ENCODING, "");
String format = "%s";
format = shared.getString(AudioApplication.PREFERENCE_FORMAT, format);
String format = shared.getString(AudioApplication.PREFERENCE_FORMAT, "%s");
format = getFormatted(format, new Date());
@ -49,14 +51,75 @@ public class Storage extends com.github.axet.audiolibrary.app.Storage {
return getNextFile(context, path, format, ext);
} else if (s.equals(ContentResolver.SCHEME_FILE)) {
File f = getFile(path);
if (!f.exists() && !f.mkdirs() && !f.exists())
throw new RuntimeException("Unable to create: " + path);
if (!Storage.mkdirs(f))
throw new RuntimeException("unable to create: " + path);
return Uri.fromFile(getNextFile(f, format, ext));
} else {
throw new UnknownUri();
}
}
public File getIntentEncoding() {
File internal = getFilesDir(context, SHARE);
// Starting in KITKAT, no permissions are required to read or write to the returned path;
// it's always accessible to the calling app.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
if (!permitted(context, PERMISSIONS_RW))
return internal;
}
File external = context.getExternalFilesDir(SHARE);
if (external == null) // some old phones <15API with disabled sdcard return null
return internal;
try {
long freeI = getFree(internal);
long freeE = getFree(external);
if (freeI > freeE)
return internal;
else
return external;
} catch (RuntimeException e) { // samsung devices unable to determine external folders
return internal;
}
}
public Uri getNewIntentRecording() {
SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
String ext = shared.getString(AudioApplication.PREFERENCE_ENCODING, "");
String format = shared.getString(AudioApplication.PREFERENCE_FORMAT, "%s");
format = getFormatted(format, new Date());
File f = getIntentEncoding();
if (!Storage.mkdirs(f))
throw new RuntimeException("unable to create: " + f);
return Uri.fromFile(getNextFile(f, format, ext));
}
public void deleteTmp() {
File internal = getFilesDir(context, SHARE);
deleteTmp(internal);
File external = context.getExternalFilesDir(SHARE);
deleteTmp(external);
}
public void deleteTmp(File dir) {
if (dir == null)
return;
long now = System.currentTimeMillis();
File[] ff = dir.listFiles();
if (ff == null)
return;
for (File f : ff) {
if (f.isFile() && f.lastModified() + StorageProvider.TIMEOUT < now)
Storage.delete(f);
}
}
public File getNewFile(File f, String ext) {
SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
@ -66,9 +129,14 @@ public class Storage extends com.github.axet.audiolibrary.app.Storage {
format = getFormatted(format, new Date());
if (!f.exists() && !f.mkdirs() && !f.exists())
if (!Storage.mkdirs(f))
throw new RuntimeException("Unable to create: " + f);
return getNextFile(f, format, ext);
}
@Override
public void migrateLocalStorage() {
super.migrateLocalStorage();
deleteTmp();
}
}

View file

@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.android.tools.build:gradle:3.5.1'
classpath 'com.github.axet:gradle-android-dx:0.0.4'
}
}