BroadcastReceiver File
class CallReceiver : BroadcastReceiver() {
private var mediaRecorder : MediaRecorder? = null
override fun onReceive(context: Context?, intent: Intent?) {
val state : String
if(intent?.action.equals("android.intent.action.PHONE_STATE")){
if(intent?.extras != null){
state = intent.getStringExtra(TelephonyManager.EXTRA_STATE).toString()
when(state){
TelephonyManager.EXTRA_STATE_RINGING -> {
Log.d("CallRec", "Ringing......")
}
TelephonyManager.EXTRA_STATE_OFFHOOK -> {
startRecording(context)
}
TelephonyManager.EXTRA_STATE_IDLE ->{
stopRecording()
}
}
}
}
}
private fun startRecording(context: Context?) {
if(context != null){
mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) MediaRecorder(context) else MediaRecorder()
}
mediaRecorder?.apply {
setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
setOutputFormat(MediaRecorder.OutputFormat.AMR_NB)
setOutputFile(getRecordingFilePath())
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
}
try {
mediaRecorder?.prepare()
mediaRecorder?.start()
}
catch (e : IOException){
Log.d("CallError", e.toString())
}
Log.d("CallRec", "Call Recording Start......")
}
private fun stopRecording(){
try {
mediaRecorder?.stop()
mediaRecorder?.release()
}
catch (e : Exception){
Log.d("CallError", e.toString())
}
mediaRecorder = null
Log.d("CallRec", "Call Recording Stopped......")
}
private fun getRecordingFilePath(): String {
val directory = File(Environment.getExternalStorageDirectory(), "TestRecording")
if(!directory.exists()){
directory.mkdir()
}
val filePath = File(directory, "testing_${System.currentTimeMillis()}" + ".amr")
return filePath.path
}
}
MainActivity Class
class MainActivity : AppCompatActivity() {
private var callReceiver = CallReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
allowPermission()
val makeCall: Button = findViewById(R.id.makeCall)
makeCall.setOnClickListener {
val intent = Intent(Intent.ACTION_DIAL)
startActivity(intent)
}
if(hasPermission()) {
val intentFilter = IntentFilter()
intentFilter.addAction("android.intent.action.PHONE_STATE")
this.registerReceiver(callReceiver, intentFilter)
}
else{
allowPermission()
}
}
override fun onDestroy() {
super.onDestroy()
this.unregisterReceiver(callReceiver)
}
private fun allowPermission() {
ActivityCompat.requestPermissions(
this, arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.RECORD_AUDIO
), 2
)
}
private fun hasPermission() : Boolean{
val phoneStatePermission = Manifest.permission.READ_PHONE_STATE
val writeFilePermission = Manifest.permission.WRITE_EXTERNAL_STORAGE
val recordAudioPermission = Manifest.permission.RECORD_AUDIO
return ContextCompat.checkSelfPermission(this, phoneStatePermission) == PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, writeFilePermission) == PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, recordAudioPermission) == PackageManager.PERMISSION_GRANTED
}
}
Manifest File
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CallRecording"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
LogCat
java.io.FileNotFoundException: /storage/emulated/0/TestRecording/testing_1694590203887.amr: open failed: ENOENT (No such file or directory)
I am building a call recording app and I want to store recorded audio file in a custom directory. When I run this app on android 8, it is working perfectly. But for android 13, it is giving EPERM (Operation not permitted) exception. I think the issue is related to permissions.
2
Answers
Google restricted call recording feature to only system apps. It won’t work above Android 8.
[https://support.google.com/googleplay/android-developer/answer/9888170]
On an Android 13 device you should not request the usual WRITE permission as you have it by default.
But you cannot create your folder "TestRecording" in root of external storage.
Instead create your folder in one of the public directiries that are already there like Download, Documents, DCIM, Pictures, Video..