如何无缝监听安卓手机通知栏推送信息以及拒接来电

您所在的位置:网站首页 华为手机通知消息 如何无缝监听安卓手机通知栏推送信息以及拒接来电

如何无缝监听安卓手机通知栏推送信息以及拒接来电

2024-01-18 10:08| 来源: 网络整理| 查看: 265

作者:咕咚移动技术团队-乔瑟琳

一.监听安卓手机通知栏推送信息

最近在需求中需要实现监听安卓手机通知栏信息的功能,比如实时获取qq、微信、短信消息。一开始评估是件挺简单的事儿,实现 NotificationListenerService,直接上代码。实现步骤如下:

1.添加: 2.打开通知监听设置 try { Intent intent; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) { intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS); } else { intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); } startActivity(intent); } catch (Exception e) { e.printStackTrace(); } 3.然后重写以下这三个方法: onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) :当有新通知到来时会回调; onNotificationRemoved(StatusBarNotification sbn) :当有通知移除时会回调; onListenerConnected() :当 NotificationListenerService 是可用的并且和通知管理器连接成功时回调。 而我们要获取通知栏的信息则需要在onNotificationPosted方法内获取 ,之前在网上查了一些文章有的通过判断API是否大于18来采取不同的办法,大致是=18则利用反射获取 Notification的内容,>18则通过Notification.extras来获取通知内容,而经测试在部分安卓手机上即使API>18 Notification.extras是等于null的。因此不能通过此方法获取通知栏信息 4.过滤包名

默认开启了NotificationListenerService将收到系统所有开启了推送开关的应用的推送消息,如果想要收到指定应用消息,则需过滤该应用的包名:

String packageName = sbn.getPackageName(); if (!packageName.contains(ComeMessage.MMS) && !packageName.contains(ComeMessage.QQ) && !packageName.contains(ComeMessage.WX)) { return; }

短信、QQ、微信对应的包名则为:

public static final String QQ="com.tencent.mobileqq"; public static final String WX="com.tencent.mm"; public static final String MMS="com.android.mms"; 5.获取通知消息 String content = null; if (sbn.getNotification().tickerText != null) { content = sbn.getNotification().tickerText.toString(); }

在onNotificationPosted方法内通过上面的方法即可获取部分手机的通知栏消息,但是但是重点来了,在部分手机上,比如华为荣耀某系列sbn.getNotification().tickerText == null,经调试发现仅在StatusBarNotification对象内部的一个view的成员变量上有推送消息内容,因此不得不用上了反射去获取view上的内容

private Map getNotiInfo(Notification notification) { int key = 0; if (notification == null) return null; RemoteViews views = notification.contentView; if (views == null) return null; Class secretClass = views.getClass(); try { Map text = new HashMap(); Field outerFields[] = secretClass.getDeclaredFields(); for (int i = 0; i < outerFields.length; i++) { if (!outerFields[i].getName().equals("mActions")) continue; outerFields[i].setAccessible(true); ArrayList actions = (ArrayList) outerFields[i].get(views); for (Object action : actions) { Field innerFields[] = action.getClass().getDeclaredFields(); Object value = null; Integer type = null; for (Field field : innerFields) { field.setAccessible(true); if (field.getName().equals("value")) { value = field.get(action); } else if (field.getName().equals("type")) { type = field.getInt(action); } } // 经验所得 type 等于9 10为短信title和内容,不排除其他厂商拿不到的情况 if (type != null && (type == 9 || type == 10)) { if (key == 0) { text.put("title", value != null ? value.toString() : ""); } else if (key == 1) { text.put("text", value != null ? value.toString() : ""); } else { text.put(Integer.toString(key), value != null ? value.toString() : null); } key++; } } key = 0; } return text; } catch (Exception e) { e.printStackTrace(); } return null; }

那么经过以上方法:先获取sbn.getNotification().tickerText,如果为空,则尝试使用反射获取view上的内容,目前测试了主流机型,暂无任何兼容性问题。

6.解决杀掉进程再次启动不触发监听问题

因为 NotificationListenerService 被杀后再次启动时,并没有去 bindService ,所以导致监听效果无效。这一现象目前我在仅有的手机上并没有出现,但是一旦遇到推荐的解决办法:利用 NotificationListenerService 先 disable 再 enable ,重新触发系统的 rebind 操作。代码如下:

private void toggleNotificationListenerService() { PackageManager pm = getPackageManager(); pm.setComponentEnabledSetting(new ComponentName(this, com.fanwei.alipaynotification.ui.AlipayNotificationListenerService.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); pm.setComponentEnabledSetting(new ComponentName(this, com.fanwei.alipaynotification.ui.AlipayNotificationListenerService.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); }

整个消息推送的流程如上,重点即在解决不通手机上获取消息的兼容性问题,不能简单的通过api版本去区分获取哪个对象,实践得出的结论是通过判断tiketText是否为空,为空则试图使用反射获取消息内容。

二.实现安卓手机上拒接来电的功能

关于安卓手机上拒接来电的功能,官方并未给出api,搜索了许多资料,花样百出,有使用模拟mediaButton按键、有使用反射拿系统的endCall方法的,但经测试在目前主流的机型上都存在问题。特总结了如下的方法,亲测有效:

1.判断是否有电话权限 if(ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1000); }

这一点十分重要,这是动态申请电话相关的权限,值得注意的是不管你的targetSdk 是否高于安卓6.0,都需要动态的申请此权限,否则,我们在后面通过反射获取相应的API,部分手机也会crash,提示你没有readPhoneState等权限,虽然这与官方定义的不一致,但国内安卓手机关于权限这块儿确实是各不相同。

2.监听来电状态 public class PhoneCallListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_OFFHOOK: //电话通话的状态 break; case TelephonyManager.CALL_STATE_RINGING: //电话响铃的状态 PhoneCallUtil.endPhone(MainActivity.this); break; } super.onCallStateChanged(state, incomingNumber); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); callListener = new PhoneCallListener(); telephonyManager.listen(callListener, PhoneStateListener.LISTEN_CALL_STATE); }

这块儿是对电话状态的监听,一开始并无可注意的tip,但在自测期间发现了些奇怪的现象,比如你直接 telephonyManager.listen(new PhoneCallListener(), PhoneStateListener.LISTEN_CALL_STATE);直接new一个对象传入listene方法,在某些手机这个电话监听会在某些操作后失效。解决的办法则是该将PhoneCallListener的对象申明成成员变量,让外面的的对象所持有,这样在跨进程通信时这个回调不被回收。

3.新建aidl文件,并通过反射获取挂断电话API

按照系统iTelephony.aidl文件的路径,新建一个相同文件,其接口内方法只需要写endCall(),注意路径必须要完全相同:

package com.android.internal.telephony; interface ITelephony { boolean endCall(); }

java方法:

public static void endPhone(Context context) { TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); Method method = null; try { method = TelephonyManager.class.getDeclaredMethod("getITelephony"); method.setAccessible(true); ITelephony telephony = (ITelephony) method.invoke(telephonyManager); telephony.endCall(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }

经过以上三步,能够实现挂断电话的功能,但是经过多种机型的测试,在vivo手机上,还是因为权限的问题不能生效,vivo手机上报出的错误如下:

AndroidRuntime: FATAL EXCEPTION: main Process: com.example.yuanting.msgpushandcall, PID: 6170 java.lang.SecurityException: MODIFY_PHONE_STATE permission required. at android.os.Parcel.readException(Parcel.java:1684) at android.os.Parcel.readException(Parcel.java:1637) at com.android.internal.telephony.ITelephony$Stub$Proxy.endCall(ITelephony.java:1848) at com.example.yuanting.msgpushandcall.utils.PhoneCallUtil.endPhone(PhoneCallUtil.java:25) at com.example.yuanting.msgpushandcall.MainActivity$PhoneCallListener.onCallStateChanged(MainActivity.java:80) at android.telephony.PhoneStateListener$1.handleMessage(PhoneStateListener.java:298) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6211) at java.lang.reflect.Method.invoke(Native Method)

神奇的安卓手机,在源码内,查到了仅仅是挂断电话是不需要修改手机电话权限的,接听电话才需要MODIFY_PHONE_STATE,但是部分手机还是报没有权限,这就是安卓吧~~,因此目前该方法并没有兼容vivo手机。

三.总结

以上是近期对消息通知、来电拒接的一些总结,关于来电的拒接功能,部分手机还存在兼容性问题,后续有新的思路会持续更新。在文章中有不足之处或错误指出望予以指出,不胜感激。

git 地址 :github.com/CodoonDemo/…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3