Skip to content

Instantly share code, notes, and snippets.

@neilser
Forked from Venryx/AndroidManifest.xml
Created August 22, 2021 13:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neilser/7948e186ec5199db40c07fb7ca830adf to your computer and use it in GitHub Desktop.
Save neilser/7948e186ec5199db40c07fb7ca830adf to your computer and use it in GitHub Desktop.
Record audio on Android in the background (even when screen is off)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.myapp">
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<service android:name=".ForegroundService" android:enabled="true" android:exported="true"></service>
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
android:name="com.myapp.MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTop">
<!-- //android:launchMode="singleTask" -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/custom_url_scheme" />
</intent-filter>
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Camera, Photos, input file -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Geolocation API -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
<!-- Network API -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Navigator.getUserMedia -->
<!-- Video -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- Audio -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<!-- v-added -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
</manifest>
package com.myapp;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
public class ForegroundService extends Service {
public static final String CHANNEL_ID = "ForegroundServiceChannel";
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
// do heavy work on a background thread
StartRecorder();
//stopSelf();
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
private static String TAG = "ForegroundService";
// the audio recording options
private static final int RECORDING_RATE = 44100;
private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;
// the audio recorder
private AudioRecord recorder;
// the minimum buffer size needed for audio recording
private static int BUFFER_SIZE = AudioRecord.getMinBufferSize(RECORDING_RATE, CHANNEL, FORMAT);
// are we currently sending audio data
private boolean currentlySendingAudio = false;
public void StartRecorder() {
Log.i(TAG, "Starting the audio stream");
currentlySendingAudio = true;
startStreaming();
}
public void StopRecorder() {
Log.i(TAG, "Stopping the audio stream");
currentlySendingAudio = false;
recorder.release();
}
private void startStreaming() {
Log.i(TAG, "Starting the background thread (in this foreground service) to read the audio data");
Thread streamThread = new Thread(() -> {
try {
Log.d(TAG, "Creating the buffer of size " + BUFFER_SIZE);
//byte[] buffer = new byte[BUFFER_SIZE];
int rate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_SYSTEM);
int bufferSize = AudioRecord.getMinBufferSize(rate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
short[] buffer = new short[bufferSize];
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
Log.d(TAG, "Creating the AudioRecord");
//recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, RECORDING_RATE, CHANNEL, FORMAT, BUFFER_SIZE * 10);
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, rate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
Log.d(TAG, "AudioRecord recording...");
recorder.startRecording();
while (currentlySendingAudio == true) {
// read the data into the buffer
int readSize = recorder.read(buffer, 0, buffer.length);
double maxAmplitude = 0;
for (int i = 0; i < readSize; i++) {
if (Math.abs(buffer[i]) > maxAmplitude) {
maxAmplitude = Math.abs(buffer[i]);
}
}
double db = 0;
if (maxAmplitude != 0) {
db = 20.0 * Math.log10(maxAmplitude / 32767.0) + 90;
}
Log.d(TAG, "Max amplitude: " + maxAmplitude + " ; DB: " + db);
}
Log.d(TAG, "AudioRecord finished recording");
} catch (Exception e) {
Log.e(TAG, "Exception: " + e);
}
});
// start the thread
streamThread.start();
}
}
package com.myapp;
import android.content.Context;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.net.wifi.WifiManager;
import android.nfc.Tag;
import android.os.PowerManager;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.widget.Button;
import com.getcapacitor.JSObject;
import com.getcapacitor.NativePlugin;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.PluginResult;
import java.net.DatagramSocket;
@NativePlugin()
public class General extends Plugin {
private static String TAG = "V.General";
@PluginMethod
public void StartRecorder(PluginCall call) {
Log.i(TAG, "Starting the foreground-thread");
Intent serviceIntent = new Intent(getActivity().getApplicationContext(), ForegroundService.class);
serviceIntent.putExtra("inputExtra", "Foreground Service Example in Android");
ContextCompat.startForegroundService(getActivity(), serviceIntent);
call.resolve();
}
@PluginMethod
public void StopRecorder(PluginCall call) {
Log.i(TAG, "Stopping the foreground-thread");
Intent serviceIntent = new Intent(getActivity().getApplicationContext(), ForegroundService.class);
getActivity().getApplicationContext().stopService(serviceIntent);
call.resolve();
}
// From what I've seen you don't need the wake-lock or wifi-lock below for the audio-recorder to persist through screen-off.
// However, to be on the safe side you might want to activate them anyway. (and/or if you have other functions that need them)
private PowerManager.WakeLock wakeLock_partial = null;
public void StartPartialWakeLock() {
if (wakeLock_partial != null && wakeLock_partial.isHeld()) return;
Log.i("vmain", "Starting partial wake-lock.");
final PowerManager pm = (PowerManager) getActivity().getApplicationContext().getSystemService(Context.POWER_SERVICE);
wakeLock_partial = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "com.myapp:partial_wake_lock");
wakeLock_partial.acquire();
}
public void StopPartialWakeLock() {
if (wakeLock_partial != null && wakeLock_partial.isHeld()) {
Log.i("vmain", "Stopping partial wake-lock.");
wakeLock_partial.release();
}
}
private WifiManager.WifiLock wifiLock = null;
public void StartWifiLock() {
WifiManager wifiManager = (WifiManager) getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "LockTag");
wifiLock.acquire();
}
public void StopWifiLock() {
wifiLock.release();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment