diff --git a/api/current.txt b/api/current.txt index 6d67fb962d008..e75ab1c608d34 100644 --- a/api/current.txt +++ b/api/current.txt @@ -33620,6 +33620,7 @@ package android.service.notification { method public abstract void onUnsubscribe(android.net.Uri); field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId"; field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity"; + field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit"; field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType"; field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService"; } diff --git a/api/system-current.txt b/api/system-current.txt index d4efb8c830977..49ff9482a262c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -35766,6 +35766,7 @@ package android.service.notification { method public abstract void onUnsubscribe(android.net.Uri); field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId"; field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity"; + field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit"; field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType"; field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService"; } diff --git a/api/test-current.txt b/api/test-current.txt index 57c735beb723c..68f8a41a38fc4 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -33634,6 +33634,7 @@ package android.service.notification { method public abstract void onUnsubscribe(android.net.Uri); field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId"; field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity"; + field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit"; field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType"; field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService"; } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 633f6995b9863..368b8ef17cc93 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -103,6 +103,7 @@ interface INotificationManager boolean updateAutomaticZenRule(in AutomaticZenRule automaticZenRule); boolean removeAutomaticZenRule(String id); boolean removeAutomaticZenRules(String packageName); + int getRuleInstanceCount(in ComponentName owner); byte[] getBackupPayload(int user); void applyRestore(in byte[] payload, int user); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 9a3c820363995..faf5b11951a64 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -379,6 +379,18 @@ public class NotificationManager return null; } + /** + * @hide + */ + public int getRuleInstanceCount(ComponentName owner) { + INotificationManager service = getService(); + try { + return service.getRuleInstanceCount(owner); + } catch (RemoteException e) { + } + return 0; + } + /** * Returns AutomaticZenRules owned by the caller. * diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java index 88bd283d06a12..eff09d62e56ea 100644 --- a/core/java/android/service/notification/ConditionProviderService.java +++ b/core/java/android/service/notification/ConditionProviderService.java @@ -84,6 +84,13 @@ public abstract class ConditionProviderService extends Service { public static final String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity"; + /** + * The name of the {@code meta-data} tag containing the maximum number of rule instances that + * can be created for this rule type. Omit or enter a value <= 0 to allow unlimited instances. + */ + public static final String META_DATA_RULE_INSTANCE_LIMIT = + "android.service.zen.automatic.ruleInstanceLimit"; + /** * A String rule id extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}. */ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 018bf2d2cb274..b1fe68c82c890 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1726,6 +1726,14 @@ public class NotificationManagerService extends SystemService { return mZenModeHelper.removeAutomaticZenRules(packageName, "removeAutomaticZenRules"); } + @Override + public int getRuleInstanceCount(ComponentName owner) throws RemoteException { + Preconditions.checkNotNull(owner, "Owner is null"); + enforceSystemOrSystemUI("getRuleInstanceCount"); + + return mZenModeHelper.getCurrentInstanceCount(owner); + } + @Override public void setInterruptionFilter(String pkg, int filter) throws RemoteException { enforcePolicyAccess(pkg, "setInterruptionFilter"); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index f7043a601c9d5..1d91fb7d858ae 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -27,7 +27,10 @@ import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.database.ContentObserver; @@ -45,7 +48,7 @@ import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings.Global; -import android.service.notification.IConditionListener; +import android.service.notification.ConditionProviderService; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.EventInfo; import android.service.notification.ZenModeConfig.ScheduleInfo; @@ -91,6 +94,7 @@ public class ZenModeHelper { private final ZenModeConditions mConditions; private final SparseArray mConfigs = new SparseArray<>(); private final Metrics mMetrics = new Metrics(); + private final ConditionProviders.Config mServiceConfig; private int mZenMode; private int mUser = UserHandle.USER_SYSTEM; @@ -113,6 +117,7 @@ public class ZenModeHelper { mSettingsObserver.observe(); mFiltering = new ZenModeFiltering(mContext); mConditions = new ZenModeConditions(this, conditionProviders); + mServiceConfig = conditionProviders.getConfig(); } public Looper getLooper() { @@ -197,7 +202,7 @@ public class ZenModeHelper { config.user = user; } synchronized (mConfig) { - setConfig(config, "onUserSwitched"); + setConfigLocked(config, "onUserSwitched"); } cleanUpZenRules(); } @@ -257,22 +262,34 @@ public class ZenModeHelper { } public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) { + if (!TextUtils.isEmpty(automaticZenRule.getId())) { + throw new IllegalArgumentException("Rule already exists"); + } + if (!isSystemRule(automaticZenRule)) { + ServiceInfo owner = getServiceInfo(automaticZenRule.getOwner()); + if (owner == null) { + throw new IllegalArgumentException("Owner is not a condition provider service"); + } + + final int ruleInstanceLimit = owner.metaData.getInt( + ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1); + if (ruleInstanceLimit > 0 && ruleInstanceLimit + < (getCurrentInstanceCount(automaticZenRule.getOwner()) + 1)) { + throw new IllegalArgumentException("Rule instance limit exceeded"); + } + } + ZenModeConfig newConfig; synchronized (mConfig) { if (mConfig == null) return null; if (DEBUG) { - Log.d(TAG, - "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" + reason); - } - if (!TextUtils.isEmpty(automaticZenRule.getId())) { - throw new IllegalArgumentException("Rule already exists"); + Log.d(TAG, "addAutomaticZenRule rule= " + automaticZenRule + " reason=" + reason); } newConfig = mConfig.copy(); - ZenRule rule = new ZenRule(); populateZenRule(automaticZenRule, rule, true); newConfig.automaticRules.put(rule.id, rule); - if (setConfig(newConfig, reason, true)) { + if (setConfigLocked(newConfig, reason, true)) { return createAutomaticZenRule(rule); } else { return null; @@ -302,7 +319,7 @@ public class ZenModeHelper { } populateZenRule(automaticZenRule, rule, false); newConfig.automaticRules.put(ruleId, rule); - return setConfig(newConfig, reason, true); + return setConfigLocked(newConfig, reason, true); } } @@ -320,7 +337,7 @@ public class ZenModeHelper { throw new SecurityException( "Cannot delete rules not owned by your condition provider"); } - return setConfig(newConfig, reason, true); + return setConfigLocked(newConfig, reason, true); } } @@ -336,10 +353,22 @@ public class ZenModeHelper { newConfig.automaticRules.removeAt(i); } } - return setConfig(newConfig, reason, true); + return setConfigLocked(newConfig, reason, true); } } + public int getCurrentInstanceCount(ComponentName owner) { + int count = 0; + synchronized (mConfig) { + for (ZenRule rule : mConfig.automaticRules.values()) { + if (rule.component != null && rule.component.equals(owner)) { + count++; + } + } + } + return count; + } + public boolean canManageAutomaticZenRule(ZenRule rule) { final int callingUid = Binder.getCallingUid(); if (callingUid == 0 || callingUid == Process.SYSTEM_UID) { @@ -361,6 +390,29 @@ public class ZenModeHelper { } } + private boolean isSystemRule(AutomaticZenRule rule) { + return ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName()); + } + + private ServiceInfo getServiceInfo(ComponentName owner) { + Intent queryIntent = new Intent(); + queryIntent.setComponent(owner); + List installedServices = mPm.queryIntentServicesAsUser( + queryIntent, + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, + UserHandle.getCallingUserId()); + if (installedServices != null) { + for (int i = 0, count = installedServices.size(); i < count; i++) { + ResolveInfo resolveInfo = installedServices.get(i); + ServiceInfo info = resolveInfo.serviceInfo; + if (mServiceConfig.bindPermission.equals(info.permission)) { + return info; + } + } + } + return null; + } + private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) { if (isNew) { rule.id = ZenModeConfig.newRuleId(); @@ -413,7 +465,7 @@ public class ZenModeHelper { newRule.conditionId = conditionId; newConfig.manualRule = newRule; } - setConfig(newConfig, reason, setRingerMode); + setConfigLocked(newConfig, reason, setRingerMode); } } @@ -478,7 +530,7 @@ public class ZenModeHelper { } if (DEBUG) Log.d(TAG, "readXml"); synchronized (mConfig) { - setConfig(config, "readXml"); + setConfigLocked(config, "readXml"); } } } @@ -507,7 +559,7 @@ public class ZenModeHelper { synchronized (mConfig) { final ZenModeConfig newConfig = mConfig.copy(); newConfig.applyNotificationPolicy(policy); - setConfig(newConfig, "setNotificationPolicy"); + setConfigLocked(newConfig, "setNotificationPolicy"); } } @@ -530,7 +582,7 @@ public class ZenModeHelper { } } } - setConfig(newConfig, "cleanUpZenRules"); + setConfigLocked(newConfig, "cleanUpZenRules"); } } @@ -543,30 +595,30 @@ public class ZenModeHelper { } } - public boolean setConfig(ZenModeConfig config, String reason) { - return setConfig(config, reason, true /*setRingerMode*/); + public boolean setConfigLocked(ZenModeConfig config, String reason) { + return setConfigLocked(config, reason, true /*setRingerMode*/); } public void setConfigAsync(ZenModeConfig config, String reason) { mHandler.postSetConfig(config, reason); } - private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) { + private boolean setConfigLocked(ZenModeConfig config, String reason, boolean setRingerMode) { final long identity = Binder.clearCallingIdentity(); try { if (config == null || !config.isValid()) { - Log.w(TAG, "Invalid config in setConfig; " + config); + Log.w(TAG, "Invalid config in setConfigLocked; " + config); return false; } if (config.user != mUser) { // simply store away for background users mConfigs.put(config.user, config); - if (DEBUG) Log.d(TAG, "setConfig: store config for user " + config.user); + if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user); return true; } mConditions.evaluateConfig(config, false /*processSubscriptions*/); // may modify config mConfigs.put(config.user, config); - if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable()); + if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable()); ZenLog.traceConfig(reason, mConfig, config); final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig), getNotificationPolicy(config)); @@ -1041,7 +1093,7 @@ public class ZenModeHelper { case MSG_SET_CONFIG: ConfigMessageData configData = (ConfigMessageData)msg.obj; synchronized (mConfig) { - setConfig(configData.config, configData.reason); + setConfigLocked(configData.config, configData.reason); } break; }