My application has a foreground service to record audio from the microphone, now i want my app to capture the screen after x seconds, my idea is to create one more foregound service to run in parallel when i start service, don't know if such a solution is correct, i'm a beginner so apologize for having a dumb question. Here is my code to start the foreground service first(endlessservice
):
Actions.kt
enum class Actions {
START,
STOP
}
DeviceAdmin.kt
import android.app.admin.DeviceAdminReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
class DeviceAdmin : DeviceAdminReceiver(){
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
Log.i("DeviceAdmin","Enable")
}
override fun onDisabled(context: Context, intent: Intent) {
super.onDisabled(context, intent)
Log.i("DeviceAdmin","disable")
}
}
EndlessService.kt
package com.robertohuertas.endless
import android.app.*
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.os.SystemClock
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.extensions.jsonBody
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.deepspeech.libdeepspeech.DeepSpeechModel
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
class EndlessService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
private var isServiceStarted = false
private var model: DeepSpeechModel? = null
private var transcriptionThread: Thread? = null
private var isRecording: AtomicBoolean = AtomicBoolean(false)
lateinit var deviceManger: DevicePolicyManager
lateinit var compName: ComponentName
private val TFLITE_MODEL_FILENAME = "deepspeech-0.9.3-models.tflite"
private val SCORER_FILENAME = "deepspeech-0.9.3-models.scorer"
override fun onBind(intent: Intent): IBinder? {
log("Some component want to bind with the service")
// We don't provide binding, so return null
return null
}
// private fun checkAudioPermission() {
// // Permission is automatically granted on SDK < 23 upon installation.
// if (Build.VERSION.SDK_INT >= 23) {
// val permission = Manifest.permission.RECORD_AUDIO
//
// if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
// ActivityCompat.requestPermissions(this@Activity, arrayOf(permission), 3)
// }
// }
// }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
log("onStartCommand executed with startId: $startId")
if (intent != null) {
val action = intent.action
log("using an intent with action $action")
when (action) {
Actions.START.name -> startService()
Actions.STOP.name -> stopService()
else -> log("This should never happen. No action in the received intent")
}
} else {
log(
"with a null intent. It has been probably restarted by the system."
)
}
// by returning this we make sure the service is restarted if the system kills the service
return START_STICKY
}
override fun onCreate() {
super.onCreate()
log("The service has been created".toUpperCase())
val notification = createNotification()
startForeground(1, notification)
// checkAudioPermission()
}
override fun onDestroy() {
super.onDestroy()
log("The service has been destroyed".toUpperCase())
Toast.makeText(this, "Service destroyed", Toast.LENGTH_SHORT).show()
}
override fun onTaskRemoved(rootIntent: Intent) {
val restartServiceIntent = Intent(applicationContext, EndlessService::class.java).also {
it.setPackage(packageName)
};
val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT);
applicationContext.getSystemService(Context.ALARM_SERVICE);
val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager;
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent);
}
private fun transcribe() {
// We read from the recorder in chunks of 2048 shorts. With a model that expects its input
// at 16000Hz, this corresponds to 2048/16000 = 0.128s or 128ms.
val audioBufferSize = 2048
val audioData = ShortArray(audioBufferSize)
deviceManger=getSystemService(AppCompatActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
compName= ComponentName(this, DeviceAdmin::class.java)
val enable=deviceManger.isAdminActive(compName)
// runOnUiThread { btnStartInference.text = "Stop Recording" }
model?.let { model ->
var streamContext = model.createStream()
val recorder = AudioRecord(
MediaRecorder.AudioSource.VOICE_RECOGNITION,
model.sampleRate(),
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
audioBufferSize
)
recorder.startRecording()
var startt = System.currentTimeMillis()
var endd =startt+ 10*1000
while (isRecording.get()) {
recorder.read(audioData, 0, audioBufferSize)
model.feedAudioContent(streamContext, audioData, audioData.size)
val decoded = model.intermediateDecode(streamContext)
Log.d("decoded2",decoded)
writeToFile(decoded)
val array: Array<String> = decoded.split(" ").toTypedArray()
// for (i in array){
// Log.d("aaa",i)}
val found = Arrays.stream(array).anyMatch { t -> t == "just" }
if (found){
streamContext = model.createStream()
deviceManger.lockNow()}
// runOnUiThread { transcription.text = decoded }
if(System.currentTimeMillis()>endd){endd =System.currentTimeMillis()+ 10*1000
streamContext = model.createStream()}
// runOnUiThread { transcription.text = decoded }
}
// runOnUiThread {
// btnStartInference.text = "Start Recording"
// transcription.text = decoded
// }
recorder.stop()
recorder.release()
}
}
private fun writeToFile(data : String) {
try {
// Creates a FileOutputStream
val dd = getExternalFilesDir(null).toString()
val file = FileOutputStream("$dd/output.txt")
// Creates an OutputStreamWriter
val output = OutputStreamWriter(file)
// Writes string to the file
output.write(data)
// Closes the writer
output.close()
} catch (e: java.lang.Exception) {
e.stackTrace
}
}
private fun startService() {
if (isServiceStarted) return
log("Starting the foreground service task")
Toast.makeText(this, "Service starting its task", Toast.LENGTH_SHORT).show()
isServiceStarted = true
setServiceState(this, ServiceState.STARTED)
// we need this lock so our service gets not affected by Doze Mode
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply {
acquire()
}
}
// we're starting a loop in a coroutine
GlobalScope.launch(Dispatchers.IO) {
if (isServiceStarted) {
launch(Dispatchers.IO) {
onRecordClick()
}
}
log("End of the loop for the service")
}
}
fun onRecordClick() {
if (model == null) {
if (!createModel()) {
return
}
}
startListening()
}
private fun createModel(): Boolean {
val modelsPath = getExternalFilesDir(null).toString()
val tfliteModelPath = "$modelsPath/$TFLITE_MODEL_FILENAME"
val scorerPath = "$modelsPath/$SCORER_FILENAME"
Log.d("aaa","$modelsPath")
for (path in listOf(tfliteModelPath, scorerPath)) {
if (!File(path).exists()) {
// status.append("Model creation failed: $path does not exist.\n")
return false
}
}
model = DeepSpeechModel(tfliteModelPath)
model?.enableExternalScorer(scorerPath)
return true
}
private fun startListening() {
if (isRecording.compareAndSet(false, true)) {
// transcriptionThread = Thread(Runnable { transcribe() }, "Transcription Thread")
// transcriptionThread?.start()
transcribe()
}
}
private fun stopService() {
log("Stopping the foreground service")
Toast.makeText(this, "Service stopping", Toast.LENGTH_SHORT).show()
try {
wakeLock?.let {
if (it.isHeld) {
it.release()
}
}
stopForeground(true)
stopSelf()
} catch (e: Exception) {
log("Service stopped without being started: ${e.message}")
}
isServiceStarted = false
setServiceState(this, ServiceState.STOPPED)
}
private fun pingFakeServer() {
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.mmmZ")
val gmtTime = df.format(Date())
val deviceId = Settings.Secure.getString(applicationContext.contentResolver, Settings.Secure.ANDROID_ID)
val json =
"""
{
"deviceId": "$deviceId",
"createdAt": "$gmtTime"
}
"""
try {
Fuel.post("https://jsonplaceholder.typicode.com/posts")
.jsonBody(json)
.response { _, _, result ->
val (bytes, error) = result
if (bytes != null) {
log("[response bytes] ${String(bytes)}")
} else {
log("[response error] ${error?.message}")
}
}
} catch (e: Exception) {
log("Error making the request: ${e.message}")
}
}
private fun createNotification(): Notification {
val notificationChannelId = "ENDLESS SERVICE CHANNEL"
// depending on the Android API that we're dealing with we will have
// to use a specific method to create the notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(
notificationChannelId,
"Endless Service notifications channel",
NotificationManager.IMPORTANCE_HIGH
).let {
it.description = "Endless Service channel"
it.enableLights(true)
it.lightColor = Color.RED
it.enableVibration(true)
it.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
it
}
notificationManager.createNotificationChannel(channel)
}
val pendingIntent: PendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
val builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Notification.Builder(
this,
notificationChannelId
) else Notification.Builder(this)
return builder
.setContentTitle("Endless Service")
.setContentText("This is your favorite endless service working")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker("Ticker text")
.setPriority(Notification.PRIORITY_HIGH) // for under android 26 compatibility
.build()
}
}
ManiActivity.kt
package com.robertohuertas.endless
import android.Manifest
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "Endless Service"
findViewById<Button>(R.id.btnStartService).let {
it.setOnClickListener {
log("START THE FOREGROUND SERVICE ON DEMAND")
actionOnService(Actions.START)
}
}
findViewById<Button>(R.id.btnStopService).let {
it.setOnClickListener {
log("STOP THE FOREGROUND SERVICE ON DEMAND")
actionOnService(Actions.STOP)
}
}
}
private fun actionOnService(action: Actions) {
if (getServiceState(this) == ServiceState.STOPPED && action == Actions.STOP) return
Intent(this, EndlessService::class.java).also {
it.action = action.name
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
log("Starting the service in >=26 Mode")
startForegroundService(it)
val permission = Manifest.permission.RECORD_AUDIO
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(permission), 3)
return
}
log("Starting the service in < 26 Mode")
startService(it)
}
}
}
}
ServiceTracker.kt
package com.robertohuertas.endless
import android.content.Context
import android.content.SharedPreferences
enum class ServiceState {
STARTED,
STOPPED,
}
private const val name = "SPYSERVICE_KEY"
private const val key = "SPYSERVICE_STATE"
fun setServiceState(context: Context, state: ServiceState) {
val sharedPrefs = getPreferences(context)
sharedPrefs.edit().let {
it.putString(key, state.name)
it.apply()
}
}
fun getServiceState(context: Context): ServiceState {
val sharedPrefs = getPreferences(context)
val value = sharedPrefs.getString(key, ServiceState.STOPPED.name)
return ServiceState.valueOf(value)
}
private fun getPreferences(context: Context): SharedPreferences {
return context.getSharedPreferences(name, 0)
}
StartReceiver.kt
package com.robertohuertas.endless
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
class StartReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED && getServiceState(context) == ServiceState.STARTED) {
Intent(context, EndlessService::class.java).also {
it.action = Actions.START.name
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
log("Starting the service in >=26 Mode from a BroadcastReceiver")
context.startForegroundService(it)
return
}
log("Starting the service in < 26 Mode from a BroadcastReceiver")
context.startService(it)
}
}
}
}
Utils.kt
package com.robertohuertas.endless
import android.util.Log
fun log(msg: String) {
Log.d("ENDLESS-SERVICE", msg)
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.robertohuertas.endless">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<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=".EndlessService"
android:enabled="true"
android:exported="false">
</service>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:enabled="true" android:name=".StartReceiver" android:description="@string/app_name" android:label="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/policies" />
</receiver>
</application>
</manifest>