Android Oreo下代码挂断电话触发SecurityPermission异常问题
Android Oreo 下挂断电话权限问题的解决方案
这是个典型的Android版本权限收紧导致的问题,我来帮你梳理清楚原因和可行的解决办法:
问题根源
Android 8.0(Oreo,API 26)开始,Google对系统隐藏API的访问做了严格限制——哪怕你已经声明并获取了CALL_PHONE权限,通过反射调用ITelephony接口的endCall方法也会被系统拦截,直接抛出SecurityException。这是因为通话控制属于敏感系统操作,Google封闭了非官方的调用路径,低版本系统没有这个限制,所以你的代码在旧设备上能正常工作。
官方推荐的合法解决方案:使用AccessibilityService
辅助服务(AccessibilityService)是目前Oreo及以上版本中唯一可靠的挂断电话方式,它属于官方提供的系统交互接口,只要用户授权就能正常使用。具体实现步骤如下:
1. 创建AccessibilityService子类
public class CallBlockerAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { int eventType = event.getEventType(); // 监听通话界面的窗口状态变化 if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { String packageName = event.getPackageName().toString(); // 匹配系统通话界面包名(不同厂商可能有差异,可根据需要调整) if (packageName.equals("com.android.incallui")) { // 执行挂断电话的系统全局操作 performGlobalAction(GLOBAL_ACTION_CALL_END); } } } @Override public void onInterrupt() { // 空实现即可 } @Override protected void onServiceConnected() { super.onServiceConnected(); // 配置服务监听参数 AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; setServiceInfo(info); } }
2. 在Manifest中注册服务
<service android:name=".CallBlockerAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service>
3. 创建辅助服务配置文件
在res/xml目录下新建accessibility_service_config.xml:
<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeWindowStateChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:canPerformGestures="true" android:description="@string/accessibility_service_desc" />
然后在strings.xml中添加服务描述(用于系统设置中展示给用户):
<string name="accessibility_service_desc">该服务用于拦截并挂断不需要的来电,请开启权限以正常使用</string>
4. 引导用户开启辅助权限
辅助权限需要用户手动在系统设置中开启,你可以在应用启动时检查权限状态,未开启则引导用户跳转设置:
// 检查辅助服务是否已启用 private boolean isAccessibilityServiceEnabled() { int accessibilityEnabled = 0; final String service = getPackageName() + "/" + CallBlockerAccessibilityService.class.getCanonicalName(); try { accessibilityEnabled = Settings.Secure.getInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED); } catch (Settings.SettingNotFoundException e) { e.printStackTrace(); } TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':'); if (accessibilityEnabled == 1) { String enabledServices = Settings.Secure.getString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); if (enabledServices != null) { splitter.setString(enabledServices); while (splitter.hasNext()) { String serviceName = splitter.next(); if (serviceName.equalsIgnoreCase(service)) { return true; } } } } return false; } // 跳转到辅助服务设置页面 private void openAccessibilitySettings() { Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); startActivity(intent); }
注意事项
- 辅助服务需要用户手动授权,你需要在应用中清晰告知用户开启权限的原因,避免用户产生不信任感;
- 不同厂商的系统通话界面包名可能有差异(比如小米是
com.android.incallui,华为可能是com.huawei.incallui),你可以根据需要扩展包名匹配逻辑; - 不要尝试使用模拟按键(如双击电源键)等非官方方式,这类方法在不同ROM中兼容性极差,且可能被系统限制。
内容的提问来源于stack exchange,提问作者Ali Akram
相关产品推荐
相关产品推荐

