Служба записи моего телефона продолжает сбой

В моем приложении есть приведенный ниже код для записи звонков, но он продолжает вылетать после вызова «службы»!

BroadcastReceiver для обнаружения вызова и запуска AudioService:

class PhoneStateReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
        val incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)

        when(state){
            TelephonyManager.EXTRA_STATE_RINGING -> {
                            Toast.makeText(context, "Ringing $incomingNumber", Toast.LENGTH_LONG).show()
                            }
            TelephonyManager.EXTRA_STATE_OFFHOOK -> {
                            Toast.makeText(context, "On Call $incomingNumber", Toast.LENGTH_LONG).show()
                            context.startService(Intent(context, AudioService::class.java))
                            }
            TelephonyManager.EXTRA_STATE_IDLE -> {
                        Toast.makeText(context, "IDLE", Toast.LENGTH_LONG).show()
                        context.stopService(Intent(context, AudioService::class.java))
                         }
        }
    }
}

AudioService, который используется для записи разговора, но продолжает давать сбой:

class AudioService : Service(), MediaRecorder.OnInfoListener {

    lateinit var context: Context
    private var mRecorder: MediaRecorder? = null
    //setting maximum file size to be recorded
    private val Audio_MAX_FILE_SIZE: Long = 1000000//1Mb

    private var mOutputFile: File? = null
    private var mStartTime: Long = 0

    private val outputFile: File
        get() {
            val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US)
            return File(context.filesDir, //Environment.getExternalStorageDirectory(Environment.DIRECTORY_DOWNLOADS)
                  //  .absolutePath.toString()
                    "RECORDING_"  // "/Voice Recorder/RECORDING_"
                    + dateFormat.format(Date())
                    + ".m4a")
        }

    override fun onInfo(mr: MediaRecorder?, what: Int, extra: Int) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
            stopRecording(true)
        }
    }

    override fun onBind(intent: Intent?): IBinder? {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        return null
    }

    override fun onCreate() {
        context = this
      //  Toast.makeText(context,"created", Toast.LENGTH_LONG).show()
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Toast.makeText(context,"started recording", Toast.LENGTH_LONG).show()

        mRecorder = MediaRecorder()
        mRecorder!!.setOnInfoListener(this)

        mRecorder!!.apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setMaxFileSize(Audio_MAX_FILE_SIZE)
            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)

            setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC)
            setAudioEncodingBitRate(48000)

            setAudioSamplingRate(16000)
        }
        mOutputFile = outputFile
        mOutputFile!!.parentFile.mkdirs()
        mRecorder!!.setOutputFile(mOutputFile!!.absolutePath)

        try {
            mRecorder!!.apply {
                prepare()
                start()
            }

            mStartTime = SystemClock.elapsedRealtime()
        } catch (e: IOException) {
        }

        return Service.START_STICKY
    }

    private fun stopRecording(saveFile: Boolean) {
        Toast.makeText(context,"stopped recording ", Toast.LENGTH_LONG).show()
        mRecorder!!.apply {
            stop()
            release()
        }
        mRecorder = null
        mStartTime = 0
        if (!saveFile && mOutputFile != null) {
            mOutputFile!!.delete()
        }
        // to stop the service by itself
        stopSelf()

    }

    override fun onDestroy() {
        super.onDestroy()
        stopRecording(true)
    }
}

В AndroidManifest я добавил оба, как показано ниже:

    <receiver android:name = ".broadcasts.PhoneStateReceiver">
        <intent-filter>
            <action android:name = "android.intent.action.PHONE_STATE" />
            <action android:name = "android.intent.action.READ_PHONE_STATE" />
        </intent-filter>
    </receiver>

    <service android:name = ".Services.AudioService" />

Я уже предоставил необходимое разрешение на выполнение android.Manifest.permission.READ_PHONE_STATE в MainActivity

ОБНОВИТЬ

У меня в отладчике такая ошибка:

java.lang.RuntimeException: setAudioSource failed

Итак, обращаясь к это, я добавил разрешение времени выполнения Manifest.permission.RECORD_AUDIO, но теперь получаю другую ошибку, а именно:

java.lang.RuntimeException: start failed.

Не могли бы вы выложить журнал сбоев?

Elletlar 01.05.2018 20:00

Пожалуйста, используйте отладчик (входит в состав студии Android), чтобы получить журналы сбоя. Специально ищите журнал ошибок. Это подскажет, почему приложение вылетает из-за определенной ошибки и местоположения. Если вы все еще не можете это исправить, вы можете задать здесь конкретную ошибку, с которой столкнулись.

Yashovardhan99 01.05.2018 20:04

@XXZ дает java.lang.RuntimeException: setAudioSource failed

Hasan A Yousef 01.05.2018 20:16

@Yashovardhan дает java.lang.RuntimeException: setAudioSource failed

Hasan A Yousef 01.05.2018 20:16
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
4
113
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Необходимо добавить следующее разрешение:

uses-permission android:name = "android.permission.RECORD_AUDIO"

Кроме того, это «опасное» разрешение, поэтому оно должно быть предоставлено непосредственно пользователем: https://developer.android.com/training/permissions/requesting

Спасибо, я получил еще одну ошибку и обновил свой вопрос.

Hasan A Yousef 01.05.2018 20:51

В какой строке происходит «java.lang.RuntimeException: start failed»?

Elletlar 01.05.2018 20:53

`попробуйте {mRecorder !!. apply {prepare () start ()}`

Hasan A Yousef 01.05.2018 20:55

java.io.IOException: нет допустимого выходного файла

Elletlar 01.05.2018 20:57

да, оно есть: mRecorder!!.setOutputFile(mOutputFile!!.absolutePath)

Hasan A Yousef 01.05.2018 21:03

Позвольте нам продолжить обсуждение в чате.

Elletlar 01.05.2018 21:26
Ответ принят как подходящий

Я обнаружил, что причина в изменениях в Android O API. относительно услуг переднего плана и уведомления пользователя о записи мультимедиа.

Мой Manifest:

<receiver android:name = ".broadcasts.PhoneStateReceiver">
    <intent-filter>
        <action android:name = "android.intent.action.PHONE_STATE" />
        <action android:name = "android.intent.action.READ_PHONE_STATE" />
    </intent-filter>
</receiver>
<service android:name = ".Services.AudioService"
    android:enabled = "true"
    android:exported = "true"/>

Мой Broadcast Receiver:

class PhoneStateReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        prefs = context.getSharedPreferences(PREFS_FILENAME, 0)
        val record_calls = prefs!!.getBoolean("recordCalls", false)
        val service = Intent(context, AudioService::class.java)

        val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
        val incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)

        when(state){
            TelephonyManager.EXTRA_STATE_RINGING -> {
                      //      Toast.makeText(context, "Ringing $incomingNumber", Toast.LENGTH_LONG).show()
                            }
            TelephonyManager.EXTRA_STATE_OFFHOOK -> {
               // Toast.makeText(context, "IS_SERVICE_RUNNING $IS_SERVICE_RUNNING", Toast.LENGTH_LONG).show()
                            if (record_calls) {
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                                    context.startForegroundService(service)
                                } else context.startService(service)
                                IS_SERVICE_RUNNING = true
                                }
            }
            TelephonyManager.EXTRA_STATE_IDLE -> if (IS_SERVICE_RUNNING) context.stopService(service)
            else -> Toast.makeText(context, "ERROR", Toast.LENGTH_LONG).show()
        }
    }
}

Мой Service:

var IS_SERVICE_RUNNING = false

class AudioService : Service(), MediaRecorder.OnInfoListener {

    lateinit var context: Context
    private var mRecorder: MediaRecorder? = null
    //setting maximum file size to be recorded
    private val Audio_MAX_FILE_SIZE: Long = 1000000//1Mb

    private var mOutputFile: File? = null
    private var mStartTime: Long = 0

    private val outputFile: File
        get() {
            val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US)
            return File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), //  Environment.DIRECTORY_DOCUMENTS, //    context.filesDir, //
                    //  .absolutePath.toString()
                    "call_"  // "/Voice Recorder/RECORDING_"
                            + dateFormat.format(Date())
                            + ".m4a")
        }

    override fun onInfo(mr: MediaRecorder?, what: Int, extra: Int) {
        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
             stopRecording(true)
        }
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        context = this
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Toast.makeText(context,"started recording", Toast.LENGTH_LONG).show()

                val intent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
    val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
    val nManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

    val notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                Notification.Builder(context, NotificationService.CHANNEL_ID)
            } else {
                Notification.Builder(context)
            }.apply {
                setContentIntent(pendingIntent)
                setSmallIcon(R.drawable.ic_error_black_24dp)

           setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
                setAutoCancel(true)

         setContentTitle(resources.getString(R.string.recording_title))
                setStyle(Notification.BigTextStyle()                          .bigText(resources.getString(R.string.recording_body)))

          setContentText(resources.getString(R.string.recording_body))
            }.build()


    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        startForeground(1, notification);
    } else {
        nManager.notify(1, notification)
    }

        mRecorder = MediaRecorder().apply {
            // reset()
        }
        mRecorder!!.setOnInfoListener(this)

        mOutputFile = outputFile
        mOutputFile!!.parentFile.mkdirs()

        mRecorder = MediaRecorder()
        mRecorder!!.apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            //  setMaxFileSize(Audio_MAX_FILE_SIZE)
            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            // setOutputFile(mFileName)
            setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC)
            setAudioEncodingBitRate(48000)
            setAudioSamplingRate(16000)
            setOutputFile(mOutputFile!!.absolutePath)
        }

        try {
            mRecorder!!.prepare()
            mRecorder!!.start()
        } catch (ise: IllegalStateException) {
            Toast.makeText(context,"Error 1 $ise ", Toast.LENGTH_LONG).show()
        } catch (ioe: IOException) {
            Toast.makeText(context,"Error 2 $ioe ", Toast.LENGTH_LONG).show()
        }

        return Service.START_STICKY
    }

    private fun stopRecording(saveFile: Boolean) {
        Toast.makeText(context,"stopped recording ", Toast.LENGTH_LONG).show()

        mRecorder!!.apply {
            stop()
            reset()
            release()
        }
        mRecorder = null
        mStartTime = 0
        if (!saveFile && mOutputFile != null) {
            mOutputFile!!.delete()
        }
        // to stop the service by itself
        stopSelf()

    }

    override fun onDestroy() {
        super.onDestroy()
      //  Toast.makeText(context,"service destroyed ", Toast.LENGTH_LONG).show()
        stopRecording(true)
    }
}

Мой NotificationUtility:

@RequiresApi(Build.VERSION_CODES.O)
class NotificationUtils(base: Context) : ContextWrapper(base) {

    val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

    init {
        createChannels()
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private fun createChannels() {
        val myChannel = NotificationChannel(CHANNEL_ID,
                CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT).apply {
            enableLights(true)
            enableVibration(true)
            lightColor = Color.GREEN
            lockscreenVisibility = Notification.VISIBILITY_PRIVATE
        }

        nManager.createNotificationChannel(myChannel)
    }

    companion object {
        const val CHANNEL_ID = "my.CHANNEL_ID"
        const val CHANNEL_NAME = "my.Notification"
    }
}

А для разрешений времени выполнения я создал Context Extensions:

fun Context.toast(message: CharSequence) =
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()

fun Context.arePermissionsGranted(permissions: Array<String>): Boolean {
    permissions.forEach { it ->
        if (ActivityCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED)
            return false
    }
    return true
}

    fun Context.isPermissionGranted(permission: String) =
            ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED

    fun Context.batchRequestPermissions(permissions: Array<String>, requestId: Int) =
            ActivityCompat.requestPermissions(this as Activity, permissions, requestId)

    fun Context.requestPermission(permission: String, requestId: Int) =
            ActivityCompat.requestPermissions(this as Activity, arrayOf(permission), requestId)

В MainActivity есть:

val CALL_PERMISSIONS =
        arrayOf(READ_PHONE_STATE, RECORD_AUDIO, WRITE_EXTERNAL_STORAGE)
const val CALL_RECORD_PERMISSION_REQUEST_ALL = 10

class MainActivity : AppCompatActivity() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)  NotificationUtils(this)

val self = this as Context

        val dialog = AlertDialog.Builder(this).apply {
            setTitle(R.string.permissions_required)
            setIcon(R.drawable.ic_done_all_black_24dp)
            setMessage(R.string.grant_permissions_required)

            setPositiveButton("Confirm", { dialog, i ->
                self.batchRequestPermissions(CALL_PERMISSIONS, CALL_RECORD_PERMISSION_REQUEST_ALL)
            })
        }
}

Другие вопросы по теме