Andorid通话自动录音(双向)
@Author GQ 2018年05月09日
启动服务监听来去电话并自动录音自动上传,支持双向录音
service保活问题暂不考虑
效果图
后来又简单丰富了一下,效果图是最开始的效果
目前的功能
- 通过接收广播和PhoneStateListener实现
- 支持主叫和被叫录音
- 增加录音震动提示
- 自动删除时长为0的录音文件
- 可过滤,不保存15s-60s录音(根据需要自行修改成自定义时长)
- 简单处理封装6.0运行权限
- 播放录音音频调用系统音乐播放器,兼容7.0,使用FileProvider
跟踪Log打印日志看一下流程
一开始为了缕清逻辑,写了多个标记位,洁癖患者可以改造一下,对不起,对不起!
- 主叫
- 被叫
AndroidManifest中配置
<!-- 权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<service
android:name=".CallRecorderService"
android:enabled="true"
android:exported="true" />
<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/provider_paths" />
</provider>
<receiver android:name=".PhoneReceiver">
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
广播接收PhoneReceiver
/**
* 来去电 广播
*
* 主叫流程:
* 呼叫:拨出号码$phoneNumber
* 主叫:摘机状态 //开始事件,等待也算做通话
* 开始录音
* 等待拨号,然后通话
* 呼叫:挂断电话(自己或者对方挂断都会调用) //结束事件
* 停止录音
*
* 被叫流程:
* 响铃:来电号码$incomingNumber
* 等待接听,然后通话
* ...
* 摘机状态 (自己拒接或对方主动挂掉不会调用此方法) //开始事件,接听时算通话
* 挂断电话(自己或者对方挂断都会调用) //结束事件
*
* @author GQ
*/
class PhoneReceiver : BroadcastReceiver() {
var context: Context? = null
override fun onReceive(context: Context, intent: Intent) {
this.context = context
// 如果是去电
if (intent.action == Intent.ACTION_NEW_OUTGOING_CALL) {
val phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)
Log.e(TAG, "呼叫:$phoneNumber")
number = phoneNumber
isHujiao = true
isGuaduan = false
isZhujiaoZhaiji = true
} else {
val tm = context.getSystemService(Service.TELEPHONY_SERVICE) as TelephonyManager
tm.listen(callListener, PhoneStateListener.LISTEN_CALL_STATE)
}
}
companion object { //为了能缕清来去电执行顺序定义了比较复杂的标记位
val TAG = "CallAutoRecord"
lateinit var vibrator: Vibrator
val callListener = CallListener()
var isHujiao = false //呼叫
var isZhujiaoTonghua = false //主叫通话
var isGuaduan = true //挂断
var isLaiDian = false //来电
var isLaidianTonghua = false //来电通话
var isZhujiaoZhaiji = false //主叫摘机
var isLaidianZhaiji = false //来电摘机
var number: String = ""
var isRecord: Boolean = false
var recorder: MediaRecorder? = null
var file: File? = null
}
}
CallListener监听通话状态
class CallListener : PhoneStateListener() {
override fun onCallStateChanged(state: Int, incomingNumber: String) {
super.onCallStateChanged(state, incomingNumber)
when (state) {
TelephonyManager.CALL_STATE_IDLE
-> {
if (PhoneReceiver.isHujiao && !PhoneReceiver.isGuaduan) {
Log.e(PhoneReceiver.TAG, "等待拨号,然后通话")
PhoneReceiver.isHujiao = false
PhoneReceiver.isZhujiaoTonghua = true
} else if (PhoneReceiver.isZhujiaoTonghua && !PhoneReceiver.isGuaduan) {
Log.e(PhoneReceiver.TAG, "呼叫:挂断电话")
stopRecord()
PhoneReceiver.isZhujiaoTonghua = false
PhoneReceiver.isGuaduan = true
number = ""
} else if (PhoneReceiver.isLaiDian && !PhoneReceiver.isGuaduan && isLaidianZhaiji) {
Log.e(PhoneReceiver.TAG, "接听电话,然后通话")
PhoneReceiver.isLaidianTonghua = true
PhoneReceiver.isLaiDian = false
isLaidianZhaiji = false
} else if (PhoneReceiver.isLaidianTonghua && !PhoneReceiver.isGuaduan) {
Log.e(PhoneReceiver.TAG, "被叫:挂断电话")
stopRecord()
PhoneReceiver.isLaidianTonghua = false
PhoneReceiver.isGuaduan = true
number = ""
}
}
TelephonyManager.CALL_STATE_OFFHOOK -> {
if (PhoneReceiver.isZhujiaoZhaiji) {
Log.e(PhoneReceiver.TAG, "主叫:摘机状态")
isZhujiaoZhaiji = false
if (ACache.get(context).getAsObject(SetActivity.RULE) == null
|| (ACache.get(context).getAsObject(SetActivity.RULE) as Int == 0)
|| (ACache.get(context).getAsObject(SetActivity.RULE) as Int == 1)
) {
prepareRecord()
}
}
if (PhoneReceiver.isLaiDian && !isLaidianZhaiji) {
Log.e(PhoneReceiver.TAG, "被叫:摘机状态")
isLaidianZhaiji = true
if (ACache.get(context).getAsObject(SetActivity.RULE) == null
|| (ACache.get(context).getAsObject(SetActivity.RULE) as Int == 0)
|| (ACache.get(context).getAsObject(SetActivity.RULE) as Int == 2)
) {
prepareRecord()
}
}
}
TelephonyManager.CALL_STATE_RINGING -> {
// 来电状态,电话铃声响起的那段时间或正在通话又来新电,新来电话不得不等待的那段时间。
if (!PhoneReceiver.isLaiDian) {
number = incomingNumber
Log.e(PhoneReceiver.TAG, "响铃:来电号码$incomingNumber")
PhoneReceiver.isLaiDian = true
PhoneReceiver.isGuaduan = false
isLaidianZhaiji = false
}
}
}
}
//准备录音
private fun prepareRecord() {
var recordTitle = number + "_" + getCurrentDate()
file = File(MainActivity.recordPath, "$recordTitle.3gp")
FileUtil.createOrExistsFile(file)
recorder = MediaRecorder()
recorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
recorder?.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)//存储格式
recorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)//设置编码
recorder?.setOutputFile(file?.absolutePath)
try {
recorder?.prepare()
startRecord()
} catch (e: IOException) {
Log.e(TAG, "RecordService::onStart() IOException attempting recorder.prepare()\n" + e.printStackTrace())
}
}
//开始录音
private fun startRecord() {
recorder?.start()
isRecord = true
if (ACache.get(MyApplication.context).getAsObject(SetActivity.VIRBATE) != null && ACache.get(MyApplication.context).getAsObject(SetActivity.VIRBATE) as Boolean) {
vibrator.vibrate(100)
}
Log.e(TAG, "开始录音")
}
//停止录音
private fun stopRecord() {
if (isRecord && recorder != null) {
try {
recorder?.stop()
recorder?.reset()
recorder?.release()
} catch (e: Exception) {
e.printStackTrace()
}
isRecord = false
recorder = null
Log.e(TAG, "停止录音")
if (ACache.get(MyApplication.context).getAsObject(SetActivity.VIRBATE) != null && ACache.get(MyApplication.context).getAsObject(SetActivity.VIRBATE) as Boolean) {
vibrator.vibrate(100)
}
//删除0KB的文件
Log.e(TAG, "文件名称 = " + file?.name)
Log.e(TAG, "文件大小 = " + file?.length())
if (file?.length() == 0L) {
FileUtil.deleteFile(file)
}
//如果开启过滤
if (ACache.get(MyApplication.context).getAsObject(FILTER) != null && ACache.get(MyApplication.context).getAsObject(FILTER) as Boolean) {
var filterTime = ACache.get(MyApplication.context).getAsObject(FILTER_TIME) as Int
if (filterTime != null && getDuration(file?.absolutePath!!) < filterTime * 1000) {
FileUtil.deleteFile(file)
}
}
android.os.Handler().postDelayed({
if (FileUtil.isFileExists(file)) {
var item = RecordBean()
item.fileName = file!!.name
item.filePath = file!!.absolutePath
item.name = ACache.get(context).getAsString("name")
//录音文件自动上传
uploadTape(item)
}
}, 3000)
}
}
//上传文件
private fun uploadTape(record: RecordBean) {
OkGo.post<String>("$IP/uploadTape")
.isMultipart(true)
.params("file", File(record.filePath))
.execute(object : StringCallback() {
override fun onSuccess(response: Response<String>?) {
Log.e("JSON onSuccess", response?.body().toString())
var tapeUrl = response?.body().toString()
if (tapeUrl != null && !tapeUrl.isEmpty()) {
Log.e(TAG, "上传录音成功")
upLoadRecord(record, tapeUrl)
} else {
Toast.makeText(context, "上传通话成功,返回路径为空", Toast.LENGTH_LONG).show();
}
}
override fun onError(response: Response<String>?) {
Log.e("JSON onError", response?.exception?.message)
super.onError(response)
Log.e(TAG, "上传录音失败")
Toast.makeText(context, "上传通话失败", Toast.LENGTH_LONG).show();
}
})
}
/**
* 上传记录
*/
private fun upLoadRecord(recordBean: RecordBean, tapeUrl: String) {
val url = "$IP/visitRecord/save"
param.put("name", ACache.get(context).getAsString("name"))
param.put("phone", ACache.get(context).getAsString("tel"))
param.put("customerPhone", cusPhoneName)
param.put("callData", "$year-$month-$day")
param.put("callTime", "$hour:$min:$sec")
param.put("timeLength", getDuration(recordBean.filePath))
param.put("tapeUrl", tapeUrl)
OkGo.post<String>(url)
.params(param)
.execute(object : StringCallback() {
override fun onSuccess(response: Response<String>?) {
Log.e("JSON onSuccess", response?.body().toString())
if (response?.body().toString().toLowerCase() == "true") {
Toast.makeText(context, "上传成功", Toast.LENGTH_LONG).show()
Log.e(TAG, "上传记录成功")
//添加到本地上传记录
var recordList = ArrayList<RecordBean>()
if (ACache.get(context).getAsString("record") != null) {
recordList = JSON.parseArray(ACache.get(context).getAsString("record"), RecordBean::class.java) as ArrayList<RecordBean>
}
recordList.add(recordBean)
ACache.get(context).put("record", JSON.toJSONString(recordList))
//上传成功,删除本地文件
FileUtil.deleteFile(recordBean.filePath)
Log.e(TAG, "上传成功后,删除本地文件")
} else {
Toast.makeText(context, "保存记录失败", Toast.LENGTH_LONG).show()
Log.e(TAG, "保存记录失败")
}
}
override fun onError(response: Response<String>?) {
Log.e("JSON onError", response?.body().toString())
super.onError(response)
Toast.makeText(context, "保存记录错误", Toast.LENGTH_LONG).show()
}
})
}
//获取录音时长 返回毫秒
private fun getDuration(path: String): Int {
var player = MediaPlayer();
try {
player.setDataSource(path) //recordingFilePath()为音频文件的路径
player.prepare();
} catch (e: IOException) {
e.printStackTrace();
} catch (e: Exception) {
e.printStackTrace();
}
var duration = player.duration;//获取音频的时间
Log.e(TAG, "### duration: $duration");
player.release()
return duration
}
//自动生成文件名
@SuppressLint("SimpleDateFormat")
private fun getCurrentDate(): String {
val formatter = SimpleDateFormat("yyyyMMddHHmmss")
return formatter.format(Date())
}
}
CallRecorderService录音服务
class CallRecorderService : Service() {
override fun onCreate() {
super.onCreate()
val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
telephonyManager.listen(callListener, PhoneStateListener.LISTEN_CALL_STATE)
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
Log.e(TAG, "启动CallRecordService服务,监听来去电")
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
super.onDestroy()
Log.e(TAG, "电话录音服务关闭")
}
}