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.Manifest
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.provider.VoicemailContract
|
import android.provider.VoicemailContract
|
||||||
import android.telecom.TelecomManager
|
import android.telecom.TelecomManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -23,29 +21,16 @@ class VoicemailService(private val context: Context) : MethodChannel.MethodCallH
|
|||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getVoicemails" -> {
|
"getVoicemails" -> {
|
||||||
|
if (!isDefaultDialer() || !hasVoicemailPermissions()) {
|
||||||
|
result.error("NO_ACCESS", "Need default‑dialer & voicemail perms", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Getting voicemails...")
|
val list = getVoicemails()
|
||||||
|
result.success(list)
|
||||||
// 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)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error getting voicemails", e)
|
Log.e(TAG, "getVoicemails failed", e)
|
||||||
result.error("VOICEMAIL_ERROR", e.message, null)
|
result.error("ERROR", e.message, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"markVoicemailAsRead" -> {
|
"markVoicemailAsRead" -> {
|
||||||
@ -82,92 +67,74 @@ class VoicemailService(private val context: Context) : MethodChannel.MethodCallH
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun hasVoicemailPermissions(): Boolean {
|
private fun hasVoicemailPermissions(): Boolean {
|
||||||
val permissions = arrayOf(
|
return listOf(
|
||||||
Manifest.permission.READ_VOICEMAIL,
|
Manifest.permission.READ_VOICEMAIL,
|
||||||
Manifest.permission.WRITE_VOICEMAIL,
|
Manifest.permission.WRITE_VOICEMAIL,
|
||||||
Manifest.permission.ADD_VOICEMAIL // Added this permission
|
Manifest.permission.ADD_VOICEMAIL
|
||||||
)
|
).all {
|
||||||
|
|
||||||
return permissions.all {
|
|
||||||
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isDefaultDialer(): Boolean {
|
private fun isDefaultDialer(): Boolean {
|
||||||
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
val tm = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
||||||
return telecomManager.defaultDialerPackage == context.packageName
|
return tm.defaultDialerPackage == context.packageName
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVoicemails(): List<Map<String, Any?>> { // Changed return type to Any?
|
private fun getVoicemails(): List<Map<String, Any?>> {
|
||||||
val voicemails = mutableListOf<Map<String, Any?>>() // Changed type to Any?
|
val out = mutableListOf<Map<String, Any?>>()
|
||||||
val uri = VoicemailContract.Voicemails.CONTENT_URI
|
val uri = VoicemailContract.Voicemails.CONTENT_URI
|
||||||
|
val proj = arrayOf(
|
||||||
val projection = arrayOf(
|
|
||||||
VoicemailContract.Voicemails._ID,
|
VoicemailContract.Voicemails._ID,
|
||||||
VoicemailContract.Voicemails.NUMBER,
|
VoicemailContract.Voicemails.NUMBER,
|
||||||
VoicemailContract.Voicemails.DATE,
|
VoicemailContract.Voicemails.DATE,
|
||||||
VoicemailContract.Voicemails.DURATION,
|
VoicemailContract.Voicemails.DURATION,
|
||||||
VoicemailContract.Voicemails.HAS_CONTENT,
|
VoicemailContract.Voicemails.HAS_CONTENT,
|
||||||
VoicemailContract.Voicemails.IS_READ,
|
VoicemailContract.Voicemails.IS_READ
|
||||||
VoicemailContract.Voicemails.SOURCE_PACKAGE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
context.contentResolver.query(
|
context.contentResolver.query(uri, proj, null, null,
|
||||||
uri,
|
"${VoicemailContract.Voicemails.DATE} DESC")?.use { c ->
|
||||||
projection,
|
while (c.moveToNext()) {
|
||||||
null,
|
val id = c.getString(c.getColumnIndexOrThrow(VoicemailContract.Voicemails._ID))
|
||||||
null,
|
val number = c.getString(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.NUMBER))
|
||||||
"${VoicemailContract.Voicemails.DATE} DESC"
|
val date = c.getLong(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.DATE))
|
||||||
)?.use { cursor ->
|
val dur = c.getLong(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.DURATION))
|
||||||
while (cursor.moveToNext()) {
|
val hasBlob = c.getInt(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.HAS_CONTENT)) == 1
|
||||||
val id = cursor.getString(cursor.getColumnIndexOrThrow(VoicemailContract.Voicemails._ID))
|
val isRead = c.getInt(c.getColumnIndexOrThrow(VoicemailContract.Voicemails.IS_READ)) == 1
|
||||||
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
|
|
||||||
|
|
||||||
var filePath: String? = null
|
val filePath = if (hasBlob) saveVoicemailToFile(id) else null
|
||||||
if (hasContent) {
|
|
||||||
filePath = saveVoicemailToFile(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
val voicemail: Map<String, Any?> = mapOf( // Added explicit type
|
out += mapOf(
|
||||||
"id" to id,
|
"id" to id,
|
||||||
"phoneNumber" to (number ?: "Unknown"),
|
"phoneNumber" to (number ?: ""),
|
||||||
"timestamp" to date,
|
"timestamp" to date,
|
||||||
"duration" to (duration / 1000).toInt(), // Convert to seconds
|
"duration" to dur.toInt(),
|
||||||
"filePath" to filePath,
|
"filePath" to filePath,
|
||||||
"isRead" to isRead
|
"isRead" to isRead
|
||||||
)
|
)
|
||||||
voicemails.add(voicemail)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return out
|
||||||
return voicemails
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveVoicemailToFile(voicemailId: String): String? {
|
private fun saveVoicemailToFile(voicemailId: String): String? {
|
||||||
val voicemailUri = Uri.withAppendedPath(VoicemailContract.Voicemails.CONTENT_URI, voicemailId)
|
val vUri = Uri.withAppendedPath(VoicemailContract.Voicemails.CONTENT_URI, voicemailId)
|
||||||
|
return try {
|
||||||
try {
|
context.contentResolver.openInputStream(vUri)?.use { inp ->
|
||||||
val inputStream = context.contentResolver.openInputStream(voicemailUri) ?: return null
|
val cache = File(context.cacheDir, "vm_$voicemailId.3gp")
|
||||||
val cacheDir = context.cacheDir
|
FileOutputStream(cache).use { out ->
|
||||||
val voicemailFile = File(cacheDir, "voicemail_$voicemailId.3gp")
|
val buf = ByteArray(4096)
|
||||||
|
var r: Int
|
||||||
FileOutputStream(voicemailFile).use { outputStream ->
|
while (inp.read(buf).also { r = it } > 0) {
|
||||||
val buffer = ByteArray(4096)
|
out.write(buf, 0, r)
|
||||||
var bytesRead: Int
|
}
|
||||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
|
||||||
outputStream.write(buffer, 0, bytesRead)
|
|
||||||
}
|
}
|
||||||
|
cache.absolutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
inputStream.close()
|
|
||||||
return voicemailFile.absolutePath
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error saving voicemail to file", e)
|
Log.e(TAG, "saveVoicemailToFile failed", e)
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,42 +92,33 @@ class _VoicemailPageState extends State<VoicemailPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _togglePlayPause(Voicemail voicemail) async {
|
Future<void> _togglePlayPause(Voicemail vm) async {
|
||||||
if (_currentPlayingId == voicemail.id && _isPlaying) {
|
if (vm.filePath == null) {
|
||||||
|
return _showSnack('Voicemail file not available');
|
||||||
|
}
|
||||||
|
final src = DeviceFileSource(vm.filePath!);
|
||||||
|
if (_currentPlayingId == vm.id && _isPlaying) {
|
||||||
await _audioPlayer.pause();
|
await _audioPlayer.pause();
|
||||||
setState(() => _isPlaying = false);
|
setState(() => _isPlaying = false);
|
||||||
} else {
|
} else {
|
||||||
// If we're playing a different voicemail, stop current one
|
if (_isPlaying) await _audioPlayer.stop();
|
||||||
if (_isPlaying && _currentPlayingId != voicemail.id) {
|
try {
|
||||||
await _audioPlayer.stop();
|
await _audioPlayer.play(src);
|
||||||
}
|
setState(() {
|
||||||
|
_isPlaying = true;
|
||||||
// Start playing the new voicemail
|
_currentPlayingId = vm.id;
|
||||||
if (voicemail.filePath != null) {
|
});
|
||||||
try {
|
if (!vm.isRead) await _voicemailService.markAsRead(vm.id);
|
||||||
await _audioPlayer.play(DeviceFileSource(voicemail.filePath!));
|
} catch (e) {
|
||||||
setState(() {
|
_showSnack('Error playing voicemail: $e');
|
||||||
_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')),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showSnack(String msg) {
|
||||||
|
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
|
||||||
|
}
|
||||||
|
|
||||||
void _makeCall(String phoneNumber) {
|
void _makeCall(String phoneNumber) {
|
||||||
_callService.makeGsmCall(context, phoneNumber: phoneNumber);
|
_callService.makeGsmCall(context, phoneNumber: phoneNumber);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user