Я обнаружил, что причина в изменениях в Android O API. о службах Foreground и уведомлении пользователя о записи мультимедиа.
Мой 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)
})
}
}