From 3fcc34cd2b79e8a72f3ae8954388c2410af0211d Mon Sep 17 00:00:00 2001 From: Allen Schober Date: Sun, 26 Mar 2017 23:38:19 -0500 Subject: [PATCH] Initial commit - Service for MediaRecorder to take default input - Starts HTTP server to send InputStream of audio in AAC stream --- .gitignore | 128 +++++++++++ .idea/compiler.xml | 22 ++ .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 6 + .idea/misc.xml | 62 ++++++ .idea/modules.xml | 9 + .idea/runConfigurations.xml | 12 + app/.gitignore | 1 + app/build.gradle | 47 ++++ app/proguard-rules.pro | 17 ++ .../schober/vinylcast/ApplicationTest.java | 13 ++ app/src/main/AndroidManifest.xml | 30 +++ .../java/com/schober/vinylcast/Helpers.java | 38 ++++ .../com/schober/vinylcast/MainActivity.java | 154 +++++++++++++ .../vinylcast/MediaRecorderService.java | 206 ++++++++++++++++++ .../main/res/drawable/ic_album_black_24dp.xml | 9 + .../main/res/drawable/ic_stop_black_24dp.xml | 9 + app/src/main/res/layout/activity_main.xml | 25 +++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes app/src/main/res/values-w820dp/dimens.xml | 6 + app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/dimens.xml | 5 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 11 + .../schober/vinylcast/ExampleUnitTest.java | 15 ++ build.gradle | 23 ++ gradle.properties | 18 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 ++++++++++++++ gradlew.bat | 90 ++++++++ settings.gradle | 1 + 36 files changed, 1135 insertions(+) create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/schober/vinylcast/ApplicationTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/schober/vinylcast/Helpers.java create mode 100644 app/src/main/java/com/schober/vinylcast/MainActivity.java create mode 100644 app/src/main/java/com/schober/vinylcast/MediaRecorderService.java create mode 100644 app/src/main/res/drawable/ic_album_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_stop_black_24dp.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/values-w820dp/dimens.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/com/schober/vinylcast/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore index f6b286c..7af8e9d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ + +# Created by https://www.gitignore.io/api/android,androidstudio + +### Android ### # Built application files *.apk *.ap_ @@ -35,6 +39,130 @@ captures/ # Intellij *.iml .idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/dictionaries +.idea/libraries # Keystore files *.jks + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +### Android Patch ### +gen-external-apklibs + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files + +# Files for the ART/Dalvik VM + +# Java class files + +# Generated files + +# Gradle files +.gradle + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) + +# Proguard folder generated by Eclipse + +# Log Files + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +*.ipr +*~ +*.swp + +# Android Patch + +# External native build folder generated in Android Studio 2.2 and later + +# NDK +obj/ + +# IntelliJ IDEA +*.iws +/out/ + +# User-specific configurations +.idea/libraries/ +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Keystore files + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# End of https://www.gitignore.io/api/android,androidstudio \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..bd04605 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Android API 23 Platform + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..adb1bca --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..396fc51 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,47 @@ +buildscript { + repositories { + jcenter() + mavenCentral() + } + + dependencies { + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' + } +} +apply plugin: 'com.android.application' +apply plugin: 'com.neenbedankt.android-apt' + +android { + compileSdkVersion 24 + buildToolsVersion '24.0.3' + + defaultConfig { + applicationId "com.schober.vinylcast" + minSdkVersion 21 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:24.2.1' + // Dagger 2 + compile 'com.google.dagger:dagger:2.0.2' + apt 'com.google.dagger:dagger-compiler:2.0.2' + provided 'org.glassfish:javax.annotation:10.0-b28' + // Butterknife + compile 'com.jakewharton:butterknife:7.0.1' + // HTTP Server + compile 'org.nanohttpd:nanohttpd:2.3.0' + // Apache Commons IO + compile 'commons-io:commons-io:2.5' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..ffe7b40 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/Allen/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/com/schober/vinylcast/ApplicationTest.java b/app/src/androidTest/java/com/schober/vinylcast/ApplicationTest.java new file mode 100644 index 0000000..2e87388 --- /dev/null +++ b/app/src/androidTest/java/com/schober/vinylcast/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.schober.vinylcast; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3098557 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/schober/vinylcast/Helpers.java b/app/src/main/java/com/schober/vinylcast/Helpers.java new file mode 100644 index 0000000..2f55093 --- /dev/null +++ b/app/src/main/java/com/schober/vinylcast/Helpers.java @@ -0,0 +1,38 @@ +package com.schober.vinylcast; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; + +/** + * Created by Allen on 3/26/17. + */ + +class Helpers { + + private Helpers(){} // not to be instantiated + + static void createStopNotification(String title, String stopText, Service con, Class serviceClass, int NOTIFICATION_ID) { + + PendingIntent stopIntent = PendingIntent + .getService(con, 0, getIntent(MediaRecorderService.REQUEST_TYPE_STOP, con, serviceClass), + PendingIntent.FLAG_CANCEL_CURRENT); + + // Start foreground service to avoid unexpected kill + Notification notification = new Notification.Builder(con) + .setContentTitle(title) + .setContentText("").setSmallIcon(R.drawable.ic_album_black_24dp) + .addAction(new Notification.Action(R.drawable.ic_stop_black_24dp, stopText, stopIntent)) + .build(); + con.startForeground(NOTIFICATION_ID, notification); + } + + static Intent getIntent(String requestType, Context con, Class serviceClass) { + Intent intent = new Intent(con, serviceClass); + intent.putExtra(MediaRecorderService.REQUEST_TYPE, requestType); + return intent; + } + +} diff --git a/app/src/main/java/com/schober/vinylcast/MainActivity.java b/app/src/main/java/com/schober/vinylcast/MainActivity.java new file mode 100644 index 0000000..b05a63a --- /dev/null +++ b/app/src/main/java/com/schober/vinylcast/MainActivity.java @@ -0,0 +1,154 @@ +package com.schober.vinylcast; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.Button; + +import java.io.IOException; +import java.io.InputStream; + +import fi.iki.elonen.NanoHTTPD; + +public class MainActivity extends AppCompatActivity { + private static final String TAG = "MainActivity"; + + private Button mLoopbackButton; + + boolean serviceBound = false; + MediaRecorderService.MediaRecorderBinder binder = null; + + private HttpServer server; + + + //https://github.com/srubin/cs160-audio-examples/blob/master/LoopbackLive/src/com/example/loopbacklive/LoopbackLive.java + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + setupHttpServer(); + + // button to initialize loopback + mLoopbackButton = (Button)findViewById(R.id.loopbackButton); + mLoopbackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent startIntent = new Intent(MainActivity.this, MediaRecorderService.class); + + if (isServiceRunning(MediaRecorderService.class)) { + if (serviceBound) { + unbindService(serviceConnection); + serviceBound = false; + } + startIntent.putExtra(MediaRecorderService.REQUEST_TYPE, MediaRecorderService.REQUEST_TYPE_STOP); + mLoopbackButton.setText("Start loopback"); + } else { + // start service + startIntent.putExtra(MediaRecorderService.REQUEST_TYPE, MediaRecorderService.REQUEST_TYPE_START); + mLoopbackButton.setText("Stop loopback"); + + if (!serviceBound) { + // Bind to LocalService + Intent bindIntent = new Intent(MainActivity.this, MediaRecorderService.class); + bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); + } + } + + MainActivity.this.startService(startIntent); + } + }); + } + + @Override + protected void onStop() { + super.onStop(); + // Unbind from the service + if (serviceBound) { + unbindService(serviceConnection); + serviceBound = false; + } + } + + private void setupHttpServer() { + try { + server = new HttpServer(); + } catch (IOException e) { + Log.e(TAG, "Exception creating webserver", e); + } + } + + public class HttpServer extends NanoHTTPD { + private final static int PORT = 5000; + + public HttpServer() throws IOException { + super(PORT); + start(); + Log.d(TAG, "Start webserver on port: " + PORT); + } + + @Override + public Response serve(IHTTPSession session) { + String path = session.getUri(); + + if (path.equals("/vinyl")) { + Log.d(TAG, "Received Request: " + session); + if (binder == null) { + Log.e(TAG, "MediaRecorderService Binder not available"); + return newFixedLengthResponse(Response.Status.NO_CONTENT, "audio/aac", "Input Stream not available"); + } + + InputStream inputStream = binder.getInputStream(); + if (inputStream == null) { + Log.e(TAG, "Input Stream not available"); + return newFixedLengthResponse(Response.Status.NO_CONTENT, "audio/aac", "Input Stream not available"); + } + + return newChunkedResponse(Response.Status.OK, "audio/aac", inputStream); + } else { + return super.serve(session); + } + } + } + + /** Defines callbacks for service binding, passed to bindService() */ + private ServiceConnection serviceConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, + IBinder service) { + Log.d(TAG, "onServiceConnected"); + // We've bound to LocalService, cast the IBinder and get LocalService instance + binder = (MediaRecorderService.MediaRecorderBinder) service; + serviceBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + Log.d(TAG, "onServiceDisconnected"); + binder = null; + serviceBound = false; + } + }; + + private boolean isServiceRunning(Class serviceClass) { + /* from http://stackoverflow.com/a/5921190 + * used to check if BackgroundVideoRecorder Service is already running + * when user tries to log in. + */ + ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if (serviceClass.getName().equals(service.service.getClassName())) { + return true; + } + } + return false; + } +} diff --git a/app/src/main/java/com/schober/vinylcast/MediaRecorderService.java b/app/src/main/java/com/schober/vinylcast/MediaRecorderService.java new file mode 100644 index 0000000..62ca3b4 --- /dev/null +++ b/app/src/main/java/com/schober/vinylcast/MediaRecorderService.java @@ -0,0 +1,206 @@ +package com.schober.vinylcast; + +import android.app.Service; +import android.content.Intent; +import android.media.MediaRecorder; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.util.Log; + +import org.apache.commons.io.input.TeeInputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +import static java.lang.Thread.sleep; + +public class MediaRecorderService extends Service { + private static final String TAG = "MediaRecorderService"; + + private static final int AUDIO_SOURCE = MediaRecorder.AudioSource.DEFAULT; + private static final int SAMPLE_RATE = 48000; // Hz + private static final int BIT_RATE = 192000; + + private int NOTIFICATION_ID = 321; + + static final String REQUEST_TYPE = "REQUEST_TYPE"; + static final String REQUEST_TYPE_START = "REQUEST_TYPE_START"; + static final String REQUEST_TYPE_PLAY = "REQUEST_TYPE_PLAY"; + static final String REQUEST_TYPE_PAUSE = "REQUEST_TYPE_PAUSE"; + static final String REQUEST_TYPE_STOP = "REQUEST_TYPE_STOP"; + + private final IBinder binder = new MediaRecorderBinder(); + + private MediaRecorder mediaRecorder = null; + private ParcelFileDescriptor[] dataPipe = null; + private InputStream mediaRecorderInputStream = null; + + private Thread mediaRecorderReadThread = null; + + public MediaRecorderService() { + } + + /** + * Class used for the client Binder. Because we know this service always + * runs in the same process as its clients, we don't need to deal with IPC. + */ + public class MediaRecorderBinder extends Binder { + //MediaRecorderService getService() { + // Return this instance of MediaRecorderService so clients can call public methods + // return MediaRecorderService.this; + //} + + InputStream getInputStream() { + Log.d(TAG, "getInputStream()"); + + try { + PipedInputStream inputStream = new PipedInputStream(); + PipedOutputStream outputStream = new PipedOutputStream(inputStream); + mediaRecorderInputStream = new TeeInputStream(mediaRecorderInputStream, outputStream, true); + return inputStream; + } catch (IOException e) { + Log.e(TAG, "Exception splitting InputStream", e); + return null; + } + } + } + + class MediaRecorderReadRunnable implements Runnable { + + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); + try { + byte[] buffer = new byte[1024]; + while (true) { + int bytesAvailable = mediaRecorderInputStream.available(); + if (bytesAvailable > buffer.length) bytesAvailable = buffer.length; + if (bytesAvailable > 0) { + mediaRecorderInputStream.read(buffer, 0, bytesAvailable); + } + sleep(10); + } + } catch (IOException | InterruptedException e) { + Log.e(TAG, "Exception in MediaRecorderReadRunnable", e); + } + } + } + + @Override + public IBinder onBind(Intent intent) { + Log.d(TAG, "onBind"); + return binder; + } + + @Override + public void onCreate() { + Log.d(TAG, "onCreate"); + super.onCreate(); + + try { + dataPipe = ParcelFileDescriptor.createPipe(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG, "onStartCommand"); + + // Based on https://github.com/columbia/helios_android + String requestType = intent.getStringExtra(MediaRecorderService.REQUEST_TYPE); + Log.d(TAG, "onStartCommand received request: " + requestType); + + if (requestType.equals(MediaRecorderService.REQUEST_TYPE_START)) { + Log.i(TAG, "Started service"); + Helpers.createStopNotification("Vinyl Cast", "Stop", this, MediaRecorderService.class, NOTIFICATION_ID); + startRecording(); + } + + if (requestType.equals(MediaRecorderService.REQUEST_TYPE_STOP)) { + Log.i(TAG, "Stopping service"); + stopRecording(); + } + + return START_STICKY; + } + + @Override + public void onDestroy() { + Log.d(TAG, "onDestroy"); + super.onDestroy(); + } + + private void startRecording() { + try { + ParcelFileDescriptor parcelWrite = new ParcelFileDescriptor(dataPipe[1]); + ParcelFileDescriptor parcelRead = new ParcelFileDescriptor(dataPipe[0]); + + ParcelFileDescriptor.AutoCloseInputStream autoCloseInputStream = new ParcelFileDescriptor.AutoCloseInputStream(parcelRead); + mediaRecorderInputStream = autoCloseInputStream; + + mediaRecorder = new MediaRecorder(); + mediaRecorder.setAudioSource(AUDIO_SOURCE); + + mediaRecorder.setAudioChannels(2); + mediaRecorder.setAudioEncodingBitRate(BIT_RATE); + mediaRecorder.setAudioSamplingRate(SAMPLE_RATE); + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS); + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + mediaRecorder.setOutputFile(parcelWrite.getFileDescriptor()); + mediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { + @Override + public void onInfo(MediaRecorder mr, int what, int extra) { + Log.d(TAG, "MediaRecorder onInfo: " + what + ", " + extra); + } + }); + mediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() { + @Override + public void onError(MediaRecorder mr, int what, int extra) { + Log.d(TAG, "MediaRecorder onError: " + what + ", " + extra); + } + }); + + mediaRecorder.prepare(); + mediaRecorderReadThread = new Thread(new MediaRecorderReadRunnable()); + + Log.d(TAG, "mediaRecorder.start()"); + mediaRecorder.start(); + mediaRecorderReadThread.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void stopRecording() { + + if (mediaRecorder != null) { + mediaRecorder.stop(); + mediaRecorder.release(); + mediaRecorder = null; + } + + if (mediaRecorderReadThread != null) { + mediaRecorderReadThread.interrupt(); + mediaRecorderReadThread = null; + } + + if (mediaRecorderInputStream != null) { + try { + mediaRecorderInputStream.close(); + } catch (IOException e) { + Log.e(TAG, "Exception closing input stream", e); + } + mediaRecorderInputStream = null; + } + + stopForeground(true); + stopSelf(); + } +} diff --git a/app/src/main/res/drawable/ic_album_black_24dp.xml b/app/src/main/res/drawable/ic_album_black_24dp.xml new file mode 100644 index 0000000..9cc85ec --- /dev/null +++ b/app/src/main/res/drawable/ic_album_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_stop_black_24dp.xml b/app/src/main/res/drawable/ic_stop_black_24dp.xml new file mode 100644 index 0000000..c428d72 --- /dev/null +++ b/app/src/main/res/drawable/ic_stop_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..6a841b2 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,25 @@ + + + + + +