Andorid通话自动录音

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, "电话录音服务关闭")
    }
}

GitOSC查看Demo

https://gitee.com/madaigou/CallAutoRecord

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值