// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.notifications; import android.annotation.TargetApi; import android.app.AppOpsManager; import android.content.Context; import android.os.Build; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * Utility for determining whether the user has disabled all of Chrome's notifications using the * system's per-application settings. * * Enabling developers to show notifications with their own content creates a significant product * risk: one spammy notification too many and the user might disable notifications for all of * Chrome, which is obviously very bad. While we have a strong focus on providing clear attribution * and ways of revoking notifications for a particular website, measuring this is still important. */ public class NotificationSystemStatusUtil { /** Status codes returned by {@link determineAppNotificationsEnabled}. **/ public static final int APP_NOTIFICATIONS_STATUS_UNDETERMINABLE = 0; public static final int APP_NOTIFICATIONS_STATUS_EXCEPTION = 1; public static final int APP_NOTIFICATIONS_STATUS_ENABLED = 2; public static final int APP_NOTIFICATIONS_STATUS_DISABLED = 3; /** Must be set to the maximum value of the above values, plus one. **/ public static final int APP_NOTIFICATIONS_STATUS_BOUNDARY = 4; /** Method name on the AppOpsManager class to check for a setting's value. **/ private static final String CHECK_OP_NO_THROW = "checkOpNoThrow"; /** The POST_NOTIFICATION operation understood by the AppOpsManager. **/ private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION"; /** * Determines whether notifications are enabled for the app represented by |context|. * Notifications may be disabled because either the user, or a management tool, has explicitly * disallowed the Chrome App to display notifications. * * This check requires Android KitKat or later. Earlier versions will return an INDETERMINABLE * status. When an exception occurs, an EXCEPTION status will be returned instead. * * @param context The context to check of whether it can show notifications. * @return One of the APP_NOTIFICATION_STATUS_* constants defined in this class. */ @TargetApi(Build.VERSION_CODES.KITKAT) static int determineAppNotificationStatus(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return APP_NOTIFICATIONS_STATUS_UNDETERMINABLE; } final String packageName = context.getPackageName(); final int uid = context.getApplicationInfo().uid; final AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); try { Class appOpsManagerClass = Class.forName(AppOpsManager.class.getName()); @SuppressWarnings("unchecked") final Method checkOpNoThrowMethod = appOpsManagerClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE, String.class); final Field opPostNotificationField = appOpsManagerClass.getDeclaredField(OP_POST_NOTIFICATION); int value = (int) opPostNotificationField.get(Integer.class); int status = (int) checkOpNoThrowMethod.invoke(appOpsManager, value, uid, packageName); return status == AppOpsManager.MODE_ALLOWED ? APP_NOTIFICATIONS_STATUS_ENABLED : APP_NOTIFICATIONS_STATUS_DISABLED; } catch (RuntimeException e) { } catch (Exception e) { // Silently fail here, since this is just collecting statistics. The histogram includes // a count for thrown exceptions, if that proves to be significant we can revisit. } return APP_NOTIFICATIONS_STATUS_EXCEPTION; } }