欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android查看当前应用通知开启状态

程序员文章站 2024-03-17 18:41:58
...

需求背景:  在我的项目里需要显示应用的通知状态,比如开启、关闭,看似一个小小的需求,发现调用常见的AppOpsManager在4.4以下手机因为没有这个api而闪退,为解决4.4以下手机的状态做了一些探索,下面是一个博主写得很好的例子拿来学习,另外在api 22版本里面可直接使用NotificationManagerCompat.areNotificationEnable()来获取开启状态值。最后遗憾地总结,在4.4以下版本及时用反射来调用状态实际也是不可行的,因为api源码的这句话,因此中断了我们的读取值,so,在4.4以下版本我写的状态值是"未知"

throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());


1、查阅资料

有问题找度娘,找到了这个帖子 
https://segmentfault.com/q/1010000002508523 
然后顺藤摸瓜找到了这个帖子 
http://*.com/questions/11649151/android-4-1-how-to-check-notifications-are-disabled-for-the-application 
大致意思就是系统不想让你获取这个开关的状态,但是我们可以使用发射来获取这个值。

2、编码测试

直接使用大神代码片段,发现是反射了AppOpsManager这个类里面的checkOpNoThrow方法,

private static boolean up43(Context context) {
        AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);

        ApplicationInfo appInfo = context.getApplicationInfo();

        String pkg = context.getApplicationContext().getPackageName();

        int uid = appInfo.uid;

        Class appOpsClass = null; /* Context.APP_OPS_MANAGER */

        try {

            appOpsClass = Class.forName(AppOpsManager.class.getName());

            Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE, String.class);

            Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
            int value = (int) opPostNotificationValue.get(Integer.class);

            boolean boo = ((int) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
            return boo;

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

尝试运行是可以获取到的。

但是我们查阅官方文档可以发现,AppOpsManager这个类是api19以上才添加的,也就是说android4.3以下这个方法就失效了,代码测试下 
Android查看当前应用通知开启状态
果然…..是获取不到的

那么4.3以下怎么办呢,这边先说下结果吧,4.3以下是获取不到。尝试过程如下: 
首先我们下下来setting源码,打开到手机到app的应用详情页,然后adb一把

adb shell dumpsys activity | grep mFocus
  • 1
  • 1

会发现栈顶是om.Android.settings/.applications.InstalledAppDetails 
好,那我们到setting里面找到InstalledAppDetails这个类,看代码会发现还是挺好理解的,看到这方法:

private void initNotificationButton() {
        INotificationManager nm = INotificationManager.Stub.asInterface(
                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
        boolean enabled = true; // default on
        try {
            enabled = nm.areNotificationsEnabledForPackage(mAppEntry.info.packageName);
        } catch (android.os.RemoteException ex) {
            // this does not bode well
        }
        mNotificationSwitch.setChecked(enabled);
        if (isThisASystemPackage()) {
            mNotificationSwitch.setEnabled(false);
        } else {
            mNotificationSwitch.setEnabled(true);
            mNotificationSwitch.setOnCheckedChangeListener(this);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

应用详情里面的 ‘显示通知’ 按钮是由initNotificationButton这个方法来处理的,在这里我们可以看到,最终代码是nm.areNotificationsEnabledForPackage(mAppEntry.info.packageName) 这句话,那么重点来了,着重的看下INotificationManager这个类里面的areNotificationsEnabledForPackage这个方法,下面以这个nm为突破口,看到了INotificationManager,

既然这些是Notification的开关,那么NotificationManger肯定就会有应用,那我们就去看看NotificationManager:

    /** @hide */
    static public INotificationManager getService()
    {
        if (sService != null) {
            return sService;
        }
        IBinder b = ServiceManager.getService("notification");
        sService = INotificationManager.Stub.asInterface(b);
        return sService;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

果然NotificationManager里面有这么个方法,看下这个IBinder的b对象,是获取的系统的notification的service,对比setting源码看下

Context源码如下

   /**
     * Use with {@link #getSystemService} to retrieve a
     * {@link android.app.NotificationManager} for informing the user of
     * background events.
     *
     * @see #getSystemService
     * @see android.app.NotificationManager
     */
    public static final String NOTIFICATION_SERVICE = "notification";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

InstalledAppDetails.Java源码如下

INotificationManager nm = INotificationManager.Stub.asInterface(
                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
  • 1
  • 2
  • 1
  • 2

好了,代码看到这里就发现 NotificationManager里面getService获取的nm对象跟InstalledAppDetails里的nm是获取的同一个实例,那么方法就来了。

思考一下,这边分两个步骤来得到我们想到的值

  • 1、反射NotificationManager的getService()方法得到INotificationManager对象
  • 2、反射INotificationManager的areNotificationsEnabledForPackage()方法得到状态值

通过上面的两步我们就能获取到最终的状态值了。 
废话不多说,上代码:

    /**
      *4.3以下
      */
    public static boolean low43() {

        boolean boo = true;
        Context context = DemoApp.getInstance();

        NotificationManager nm = (NotificationManager)               
        context.getSystemService(android.content.Context.NOTIFICATION_SERVICE);

        String pkg = context.getApplicationContext().getPackageName();

        try {
            Class NotificationManagerClass;
            // step1
            NotificationManagerClass = Class.forName(NotificationManager.class.getName());

            Method getServiceMethod = NotificationManagerClass.getMethod("getService");
            Logs.LOGD(TAG, "getServiceMethod: " + getServiceMethod);
            Object obj_inm = getServiceMethod.invoke(nm);
            Logs.LOGD(TAG, "obj_inm: " + obj_inm);

            // step2
            Class INotificationManagerClass;
            INotificationManagerClass = Class.forName("android.app.INotificationManager");

             // 多余步骤,看看nm里面有哪些方法,打出来看看
            Method[] list = INotificationManagerClass.getMethods();
            for (int i = 0; i < list.length; i++) {
                Logs.LOGD(TAG, i + ": " + list[i].toString());
            }

            Method areNotificationsEnabledForPackage_Method = INotificationManagerClass.getMethod("areNotificationsEnabledForPackage", String.class);
            Logs.LOGD(TAG, "areNotificationsEnabledForPackage_Method: " + areNotificationsEnabledForPackage_Method);
            boo = (boolean) areNotificationsEnabledForPackage_Method.invoke(obj_inm, pkg);
            Logs.LOGD(TAG, "low43_invoke boo: " + boo);

        } catch (Exception e) {
            e.printStackTrace();
        }


//        NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(DemoApp.getInstance());
//        boolean boo = notificationManagerCompat.areNotificationsEnabled();
        Logs.LOGD(TAG, "low43: " + boo);
        return boo;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

(方法命名欠妥当)迫不及待的跑一把看看,最终会发现: 
Android查看当前应用通知开启状态
为什么会这样呢? 
INotificaitonManager源码是没有看到了,不过从NotificationManagerService入手可以发现 INotificationManager.Stub第一个binder实例,查看代码发现

        /**
         * Use this when you just want to know if notifications are OK for this package.
         */
        @Override
        public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
            checkCallerIsSystem();
            return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
                    == AppOpsManager.MODE_ALLOWED);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

重点在这个方法 checkCallerIsSystem():

    private static void checkCallerIsSystem() {
        if (isCallerSystem()) {
            return;
        }
        throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
    }

    private static boolean isCallerSystem() {
        return isUidSystem(Binder.getCallingUid());
    }

    private static boolean isUidSystem(int uid) {
        final int appid = UserHandle.getAppId(uid);
        return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
Android查看当前应用通知开启状态
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

看到这里相信大家都能看明白了,人家就是不想让非系统级的app用,就是这么任性。

                                                      备注: 参考博客地址 http://blog.csdn.net/zcllige/article/details/52444258