refactor: streamline voicemail retrieval and playback handling with improved error messaging
This commit is contained in:
parent
ec3f13a34b
commit
ea132dd348
@ -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 default‑dialer & 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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user