为何AudioManager切换铃声模式时崩溃?已授予PHONE_STATE权限
Hey there! Let's dig into why your app is crashing when trying to override the silent mode for specific incoming calls. Based on your setup and the code snippet you shared, here are the most common issues and fixes to get this working smoothly:
The biggest culprit here is likely missing permissions—especially for newer Android versions. Modifying the ringer mode isn't just about PHONE_STATE:
- For Android 10 (API 29) and above, you need the
ACCESS_NOTIFICATION_POLICYpermission to bypass silent mode. This can't be granted via a normal runtime permission prompt; users have to enable it manually in system settings. - You also need the basic
MODIFY_AUDIO_SETTINGSpermission to adjust audio modes at all.
First, add these to your AndroidManifest.xml:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- Already have this for PHONE_STATE events -->
Then, in your code, check if the notification policy permission is granted before modifying the ringer mode:
override fun onReceive(context: Context, intent: Intent) { val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE) val incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) val targetNumber = "your-specified-number-here" // Replace with your target number if (state == TelephonyManager.EXTRA_STATE_RINGING && incomingNumber == targetNumber) { // Check permission for API 29+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (!notificationManager.isNotificationPolicyAccessGranted) { // Redirect user to settings to enable permission val settingsIntent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS) settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(settingsIntent) return // Exit until permission is granted } } // Now safely modify the ringer mode val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.ringerMode = AudioManager.RINGER_MODE_NORMAL } }
If you're using a statically registered PhoneStateReceiver (declared in AndroidManifest.xml), it won't receive the PHONE_STATE broadcast on Android 8.0 (API 26) and above—this can lead to unexpected crashes or untriggered code.
Fix this by either:
- Dynamically registering the receiver in an Activity/Service:
// In your Activity's onCreate() val receiver = PhoneStateReceiver() val filter = IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED) registerReceiver(receiver, filter) // Don't forget to unregister in onDestroy() override fun onDestroy() { super.onDestroy() unregisterReceiver(receiver) }
- Or, if you prefer static registration (Android 12+ requires
android:exported="true"):
<receiver android:name=".PhoneStateReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> </receiver>
Crash logs often point to null values—make sure you handle cases where incomingNumber or the TelephonyManager state might be null:
incomingNumber?.let { number -> if (state == TelephonyManager.EXTRA_STATE_RINGING && number == targetNumber) { // Proceed with ringer mode changes } }
Don't forget to switch back to the user's original ringer mode once the call ends (e.g., when the phone state changes to EXTRA_STATE_IDLE):
// Store original mode when you switch to normal private var originalRingerMode: Int? = null // In onReceive() if (state == TelephonyManager.EXTRA_STATE_RINGING && incomingNumber == targetNumber) { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager originalRingerMode = audioManager.ringerMode audioManager.ringerMode = AudioManager.RINGER_MODE_NORMAL } else if (state == TelephonyManager.EXTRA_STATE_IDLE && originalRingerMode != null) { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.ringerMode = originalRingerMode!! originalRingerMode = null }
内容的提问来源于stack exchange,提问作者Hasan A Yousef

