refactor: streamline voicemail retrieval and playback handling with improved error messaging
All checks were successful
/ mirror (push) Successful in 5s
/ build-stealth (push) Successful in 9m41s
/ build (push) Successful in 9m46s

This commit is contained in:
AlexisDanlos 2025-04-24 10:34:37 +02:00
parent ec3f13a34b
commit ea132dd348
2 changed files with 68 additions and 110 deletions

View File

@ -3,9 +3,7 @@ package com.icing.dialer.services
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.VoicemailContract
import android.telecom.TelecomManager
import android.util.Log
@ -23,29 +21,16 @@ class VoicemailService(private val context: Context) : MethodChannel.MethodCallH
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getVoicemails" -> {
if (!isDefaultDialer() || !hasVoicemailPermissions()) {
result.error("NO_ACCESS", "Need defaultdialer & voicemail perms", null)
return
}
try {
Log.d(TAG, "Getting voicemails...")
// Check if app is default dialer
if (!isDefaultDialer()) {
Log.e(TAG, "App is not the default dialer, cannot access voicemails")
result.error("NOT_DEFAULT_DIALER", "App must be set as default dialer to access voicemails", null)
return
}
// Check permissions
if (!hasVoicemailPermissions()) {
Log.e(TAG, "Missing voicemail permissions")
result.error("MISSING_PERMISSIONS", "App doesn't have required voicemail permissions", null)
return
}
val voicemails = getVoicemails()
Log.d(TAG, "Found ${voicemails.size} voicemails")
result.success(voicemails)
val list = getVoicemails()
result.success(list)
} catch (e: Exception) {
Log.e(TAG, "Error getting voicemails", e)
result.error("VOICEMAIL_ERROR", e.message, null)
Log.e(TAG, "getVoicemails failed", e)
result.error("ERROR", e.message, null)
}
}
"markVoicemailAsRead" -> {
@ -82,92 +67,74 @@ class VoicemailService(private val context: Context) : MethodChannel.MethodCallH
}
private fun hasVoicemailPermissions(): Boolean {
val permissions = arrayOf(
return listOf(
Manifest.permission.READ_VOICEMAIL,
Manifest.permission.WRITE_VOICEMAIL,
Manifest.permission.ADD_VOICEMAIL // Added this permission
)
return permissions.all {
Manifest.permission.ADD_VOICEMAIL
).all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
}
private fun isDefaultDialer(): Boolean {
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
return telecomManager.defaultDialerPackage == context.packageName
val tm = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
return tm.defaultDialerPackage == context.packageName
}
private fun getVoicemails(): List<Map<String, Any?>> { // Changed return type to Any?
val voicemails = mutableListOf<Map<String, Any?>>() // Changed type to Any?
private fun getVoicemails(): List<Map<String, Any?>> {
val out = mutableListOf<Map<String, Any?>>()
val uri = VoicemailContract.Voicemails.CONTENT_URI
val projection = arrayOf(
val proj = arrayOf(
VoicemailContract.Voicemails._ID,
VoicemailContract.Voicemails.NUMBER,
VoicemailContract.Voicemails.DATE,
VoicemailContract.Voicemails.DURATION,
VoicemailContract.Voicemails.HAS_CONTENT,
VoicemailContract.Voicemails.IS_READ,
VoicemailContract.Voicemails.SOURCE_PACKAGE
VoicemailContract.Voicemails.IS_READ
)
context.contentResolver.query(
uri,
projection,
null,
null,
"${VoicemailContract.Voicemails.DATE} DESC"
)?.use { cursor ->
while (cursor.moveToNext()) {
val id = cursor.getString(cursor.getColumnIndexOrThrow(VoicemailContract.Voicemails._ID))
val number = cursor.getString(cursor.getColumnIndexOrThrow(VoicemailContract.Voicemails.NUMBER))
val date = cursor.getLong(cursor.getColumnIndexOrThrow(VoicemailContract.Voicemails.DATE))
val duration = cursor.getLong(cursor.getColumnIndexOrThrow(VoicemailContract.Voicemails.DURATION))
val hasContent = cursor.getInt(cursor.getColumnIndexOrThrow(VoicemailContract.Voicemails.HAS_CONTENT)) == 1
val isRead = cursor.getInt(cursor.getColumnIndexOrThrow(VoicemailContract.Voicemails.IS_READ)) == 1
context.contentResolver.query(uri, proj, null, null,
"${VoicemailContract.Voicemails.DATE} DESC")?.use { c ->
while (c.moveToNext()) {
val id = c.getString(c.getColumnIndexOrThrow(VoicemailContract.Voicemails._ID))
val number = c.getString(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.NUMBER))
val date = c.getLong(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.DATE))
val dur = c.getLong(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.DURATION))
val hasBlob = c.getInt(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.HAS_CONTENT)) == 1
val isRead = c.getInt(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.IS_READ)) == 1
var filePath: String? = null
if (hasContent) {
filePath = saveVoicemailToFile(id)
}
val filePath = if (hasBlob) saveVoicemailToFile(id) else null
val voicemail: Map<String, Any?> = mapOf( // Added explicit type
"id" to id,
"phoneNumber" to (number ?: "Unknown"),
"timestamp" to date,
"duration" to (duration / 1000).toInt(), // Convert to seconds
"filePath" to filePath,
"isRead" to isRead
out += mapOf(
"id" to id,
"phoneNumber" to (number ?: ""),
"timestamp" to date,
"duration" to dur.toInt(),
"filePath" to filePath,
"isRead" to isRead
)
voicemails.add(voicemail)
}
}
return voicemails
return out
}
private fun saveVoicemailToFile(voicemailId: String): String? {
val voicemailUri = Uri.withAppendedPath(VoicemailContract.Voicemails.CONTENT_URI, voicemailId)
try {
val inputStream = context.contentResolver.openInputStream(voicemailUri) ?: return null
val cacheDir = context.cacheDir
val voicemailFile = File(cacheDir, "voicemail_$voicemailId.3gp")
FileOutputStream(voicemailFile).use { outputStream ->
val buffer = ByteArray(4096)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
val vUri = Uri.withAppendedPath(VoicemailContract.Voicemails.CONTENT_URI, voicemailId)
return try {
context.contentResolver.openInputStream(vUri)?.use { inp ->
val cache = File(context.cacheDir, "vm_$voicemailId.3gp")
FileOutputStream(cache).use { out ->
val buf = ByteArray(4096)
var r: Int
while (inp.read(buf).also { r = it } > 0) {
out.write(buf, 0, r)
}
}
cache.absolutePath
}
inputStream.close()
return voicemailFile.absolutePath
} catch (e: Exception) {
Log.e(TAG, "Error saving voicemail to file", e)
return null
Log.e(TAG, "saveVoicemailToFile failed", e)
null
}
}

View File

@ -92,41 +92,32 @@ class _VoicemailPageState extends State<VoicemailPage> {
}
}
Future<void> _togglePlayPause(Voicemail voicemail) async {
if (_currentPlayingId == voicemail.id && _isPlaying) {
Future<void> _togglePlayPause(Voicemail vm) async {
if (vm.filePath == null) {
return _showSnack('Voicemail file not available');
}
final src = DeviceFileSource(vm.filePath!);
if (_currentPlayingId == vm.id && _isPlaying) {
await _audioPlayer.pause();
setState(() => _isPlaying = false);
} else {
// If we're playing a different voicemail, stop current one
if (_isPlaying && _currentPlayingId != voicemail.id) {
await _audioPlayer.stop();
}
// Start playing the new voicemail
if (voicemail.filePath != null) {
try {
await _audioPlayer.play(DeviceFileSource(voicemail.filePath!));
setState(() {
_isPlaying = true;
_currentPlayingId = voicemail.id;
});
// Mark as read if not already
if (!voicemail.isRead) {
await _voicemailService.markAsRead(voicemail.id);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error playing voicemail: $e')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Voicemail file not available')),
);
if (_isPlaying) await _audioPlayer.stop();
try {
await _audioPlayer.play(src);
setState(() {
_isPlaying = true;
_currentPlayingId = vm.id;
});
if (!vm.isRead) await _voicemailService.markAsRead(vm.id);
} catch (e) {
_showSnack('Error playing voicemail: $e');
}
}
}
void _showSnack(String msg) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
}
void _makeCall(String phoneNumber) {
_callService.makeGsmCall(context, phoneNumber: phoneNumber);