Zen: Add notification policy management api.
- Allow apps to read and modify notification policy (currently
which items are prioritized in "priority only" mode), but only
after they've been granted access by noman.
- Access to read/modify notification policy requires a token provided
by noman. Enabled notification listeners are automatically given
tokens (new getter on NLS), but any other app can also request them.
- Currently, all requests are granted.
- Also add a new change intent when the public policy changes.
Bug: 18541928
Change-Id: I482d1c39852d0d961931515e0f0e059a8faee4ed
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e2230da..913159a 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -17,8 +17,10 @@
package android.app;
+import android.app.INotificationManagerCallback;
import android.app.ITransientNotification;
import android.app.Notification;
+import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
@@ -71,6 +73,7 @@
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
int getInterruptionFilterFromListener(in INotificationListener token);
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
+ NotificationManager.Policy.Token getPolicyTokenFromListener(in INotificationListener listener);
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
@@ -82,4 +85,8 @@
oneway void setZenMode(int mode, in Uri conditionId, String reason);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
+ oneway void requestNotificationPolicyToken(String pkg, in INotificationManagerCallback callback);
+ boolean isNotificationPolicyTokenValid(String pkg, in NotificationManager.Policy.Token token);
+ NotificationManager.Policy getNotificationPolicy(in NotificationManager.Policy.Token token);
+ void setNotificationPolicy(in NotificationManager.Policy.Token token, in NotificationManager.Policy policy);
}
diff --git a/core/java/android/app/INotificationManagerCallback.aidl b/core/java/android/app/INotificationManagerCallback.aidl
new file mode 100644
index 0000000..b9414ca
--- /dev/null
+++ b/core/java/android/app/INotificationManagerCallback.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.app.NotificationManager;
+
+/** @hide */
+oneway interface INotificationManagerCallback {
+ void onPolicyToken(in NotificationManager.Policy.Token token);
+}
diff --git a/core/java/android/app/NotificationManager.aidl b/core/java/android/app/NotificationManager.aidl
new file mode 100644
index 0000000..8380b8d
--- /dev/null
+++ b/core/java/android/app/NotificationManager.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable NotificationManager.Policy;
+parcelable NotificationManager.Policy.Token;
\ No newline at end of file
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fa61e18..7133dce 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,14 +16,19 @@
package android.app;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.app.Notification.Builder;
+import android.app.NotificationManager.Policy.Token;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -33,6 +38,8 @@
import android.service.notification.ZenModeConfig;
import android.util.Log;
+import java.util.Objects;
+
/**
* Class to notify the user of events that happen. This is how you tell
* the user that something has happened in the background. {@more}
@@ -89,6 +96,14 @@
public static final String ACTION_EFFECTS_SUPPRESSOR_CHANGED
= "android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED";
+ /**
+ * Intent that is broadcast when the state of getNotificationPolicy() changes.
+ * This broadcast is only sent to registered receivers.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_POLICY_CHANGED
+ = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+
private static INotificationManager sService;
/** @hide */
@@ -338,5 +353,293 @@
return null;
}
+ /**
+ * Requests a notification policy token for the calling package.
+ *
+ * @param callback required, used to receive the granted token or the deny signal.
+ * @param handler The handler used when receiving the result.
+ * If null, the current thread is used.
+ */
+ public void requestNotificationPolicyToken(@NonNull final Policy.Token.RequestCallback callback,
+ @Nullable Handler handler) {
+ checkRequired("callback", callback);
+ final Handler h = handler != null ? handler : new Handler();
+ INotificationManager service = getService();
+ try {
+ service.requestNotificationPolicyToken(mContext.getOpPackageName(),
+ new INotificationManagerCallback.Stub() {
+ @Override
+ public void onPolicyToken(final Token token) throws RemoteException {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ if (token != null) {
+ callback.onTokenGranted(token);
+ } else {
+ callback.onTokenDenied();
+ }
+ }
+ });
+ }
+ });
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Checks a given notification policy token.
+ *
+ * Returns true if the token is still valid for managing policy.
+ */
+ public boolean isNotificationPolicyTokenValid(@NonNull Policy.Token token) {
+ if (token == null) return false;
+ INotificationManager service = getService();
+ try {
+ return service.isNotificationPolicyTokenValid(mContext.getOpPackageName(), token);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Gets the current notification policy.
+ *
+ * @param token A valid notification policy token is required to access the current policy.
+ */
+ public Policy getNotificationPolicy(@NonNull Policy.Token token) {
+ checkRequired("token", token);
+ INotificationManager service = getService();
+ try {
+ return service.getNotificationPolicy(token);
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Sets the current notification policy.
+ *
+ * @param token A valid notification policy token is required to modify the current policy.
+ * @param policy The new desired policy.
+ */
+ public void setNotificationPolicy(@NonNull Policy.Token token, @NonNull Policy policy) {
+ checkRequired("token", token);
+ checkRequired("policy", policy);
+ INotificationManager service = getService();
+ try {
+ service.setNotificationPolicy(token, policy);
+ } catch (RemoteException e) {
+ }
+ }
+
private Context mContext;
+
+ private static void checkRequired(String name, Object value) {
+ if (value == null) {
+ throw new IllegalArgumentException(name + " is required");
+ }
+ }
+
+ /**
+ * Notification policy configuration. Represents user-preferences for notification
+ * filtering and prioritization.
+ */
+ public static class Policy implements android.os.Parcelable {
+ /** Reminder notifications are prioritized. */
+ public static final int PRIORITY_CATEGORY_REMINDERS = 1 << 0;
+ /** Event notifications are prioritized. */
+ public static final int PRIORITY_CATEGORY_EVENTS = 1 << 1;
+ /** Message notifications are prioritized. */
+ public static final int PRIORITY_CATEGORY_MESSAGES = 1 << 2;
+ /** Calls are prioritized. */
+ public static final int PRIORITY_CATEGORY_CALLS = 1 << 3;
+ /** Calls from repeat callers are prioritized. */
+ public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4;
+
+ private static final int[] ALL_PRIORITY_CATEGORIES = {
+ PRIORITY_CATEGORY_REMINDERS,
+ PRIORITY_CATEGORY_EVENTS,
+ PRIORITY_CATEGORY_MESSAGES,
+ PRIORITY_CATEGORY_CALLS,
+ PRIORITY_CATEGORY_REPEAT_CALLERS,
+ };
+
+ /** Any sender is prioritized. */
+ public static final int PRIORITY_SENDERS_ANY = 0;
+ /** Saved contacts are prioritized. */
+ public static final int PRIORITY_SENDERS_CONTACTS = 1;
+ /** Only starred contacts are prioritized. */
+ public static final int PRIORITY_SENDERS_STARRED = 2;
+
+ /** Notification categories to prioritize. Bitmask of PRIORITY_CATEGORY_* constants. */
+ public final int priorityCategories;
+
+ /** Notification senders to prioritize. One of:
+ * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+ public final int prioritySenders;
+
+ public Policy(int priorityCategories, int prioritySenders) {
+ this.priorityCategories = priorityCategories;
+ this.prioritySenders = prioritySenders;
+ }
+
+ /** @hide */
+ public Policy(Parcel source) {
+ this(source.readInt(), source.readInt());
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(priorityCategories);
+ dest.writeInt(prioritySenders);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(priorityCategories, prioritySenders);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Policy)) return false;
+ if (o == this) return true;
+ final Policy other = (Policy) o;
+ return other.priorityCategories == priorityCategories
+ && other.prioritySenders == prioritySenders;
+ }
+
+ @Override
+ public String toString() {
+ return "NotificationManager.Policy["
+ + "priorityCategories=" + priorityCategoriesToString(priorityCategories)
+ + ",prioritySenders=" + prioritySendersToString(prioritySenders)
+ + "]";
+ }
+
+ public static String priorityCategoriesToString(int priorityCategories) {
+ if (priorityCategories == 0) return "";
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < ALL_PRIORITY_CATEGORIES.length; i++) {
+ final int priorityCategory = ALL_PRIORITY_CATEGORIES[i];
+ if ((priorityCategories & priorityCategory) != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append(priorityCategoryToString(priorityCategory));
+ }
+ priorityCategories &= ~priorityCategory;
+ }
+ if (priorityCategories != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append("PRIORITY_CATEGORY_UNKNOWN_").append(priorityCategories);
+ }
+ return sb.toString();
+ }
+
+ private static String priorityCategoryToString(int priorityCategory) {
+ switch (priorityCategory) {
+ case PRIORITY_CATEGORY_REMINDERS: return "PRIORITY_CATEGORY_REMINDERS";
+ case PRIORITY_CATEGORY_EVENTS: return "PRIORITY_CATEGORY_EVENTS";
+ case PRIORITY_CATEGORY_MESSAGES: return "PRIORITY_CATEGORY_MESSAGES";
+ case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS";
+ case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS";
+ default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory;
+ }
+ }
+
+ public static String prioritySendersToString(int prioritySenders) {
+ switch (prioritySenders) {
+ case PRIORITY_SENDERS_ANY: return "PRIORITY_SENDERS_ANY";
+ case PRIORITY_SENDERS_CONTACTS: return "PRIORITY_SENDERS_CONTACTS";
+ case PRIORITY_SENDERS_STARRED: return "PRIORITY_SENDERS_STARRED";
+ default: return "PRIORITY_SENDERS_UNKNOWN_" + prioritySenders;
+ }
+ }
+
+ public static final Parcelable.Creator<Policy> CREATOR = new Parcelable.Creator<Policy>() {
+ @Override
+ public Policy createFromParcel(Parcel in) {
+ return new Policy(in);
+ }
+
+ @Override
+ public Policy[] newArray(int size) {
+ return new Policy[size];
+ }
+ };
+
+ /**
+ * Represents a client-specific token required to manage notification policy.
+ */
+ public static class Token implements Parcelable {
+ private final IBinder mBinder;
+
+ /** @hide */
+ public Token(IBinder binder) {
+ if (binder == null) throw new IllegalArgumentException("Binder required for token");
+ mBinder = binder;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBinder);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Token)) return false;
+ if (o == this) return true;
+ final Token other = (Token) o;
+ return Objects.equals(other.mBinder, mBinder);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("NotificationManager.Token[0x%08x]",
+ System.identityHashCode(mBinder));
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mBinder);
+ }
+
+ public static final Parcelable.Creator<Token> CREATOR
+ = new Parcelable.Creator<Token>() {
+ @Override
+ public Token createFromParcel(Parcel in) {
+ return new Token(in.readStrongBinder());
+ }
+
+ @Override
+ public Token[] newArray(int size) {
+ return new Token[size];
+ }
+ };
+
+ /** Callback for receiving the result of a token request. */
+ public static abstract class RequestCallback {
+ /**
+ * Received if the request was granted for this package.
+ *
+ * @param token can be used to manage notification policy.
+ */
+ public abstract void onTokenGranted(Policy.Token token);
+
+ /**
+ * Received if the request was denied for this package.
+ */
+ public abstract void onTokenDenied();
+ }
+ }
+ }
+
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index fa782e4..cc7f880 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -21,6 +21,7 @@
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Notification.Builder;
+import android.app.NotificationManager.Policy;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -501,6 +502,22 @@
}
/**
+ * Gets the notification policy token associated with this listener.
+ *
+ * <p>
+ * Returns null if this listener is not currently active.
+ */
+ public final Policy.Token getNotificationPolicyToken() {
+ if (!isBound()) return null;
+ try {
+ return getNotificationInterface().getPolicyTokenFromListener(mWrapper);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ return null;
+ }
+ }
+
+ /**
* Sets the desired {@link #getCurrentListenerHints() listener hints}.
*
* <p>
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 1ed4779..14e947c 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -16,6 +16,7 @@
package android.service.notification;
+import android.app.NotificationManager.Policy;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
@@ -470,6 +471,59 @@
}
};
+ public Policy toNotificationPolicy() {
+ int priorityCategories = 0;
+ int prioritySenders = Policy.PRIORITY_SENDERS_ANY;
+ if (allowCalls) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
+ }
+ if (allowMessages) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
+ }
+ if (allowEvents) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
+ }
+ if (allowReminders) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
+ }
+ if (allowRepeatCallers) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
+ }
+ switch (allowFrom) {
+ case SOURCE_ANYONE:
+ prioritySenders = Policy.PRIORITY_SENDERS_ANY;
+ break;
+ case SOURCE_CONTACT:
+ prioritySenders = Policy.PRIORITY_SENDERS_CONTACTS;
+ break;
+ case SOURCE_STAR:
+ prioritySenders = Policy.PRIORITY_SENDERS_STARRED;
+ break;
+ }
+ return new Policy(priorityCategories, prioritySenders);
+ }
+
+ public void applyNotificationPolicy(Policy policy) {
+ if (policy == null) return;
+ allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
+ allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
+ allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
+ allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
+ allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
+ != 0;
+ switch (policy.prioritySenders) {
+ case Policy.PRIORITY_SENDERS_CONTACTS:
+ allowFrom = SOURCE_CONTACT;
+ break;
+ case Policy.PRIORITY_SENDERS_STARRED:
+ allowFrom = SOURCE_STAR;
+ break;
+ default:
+ allowFrom = SOURCE_ANYONE;
+ break;
+ }
+ }
+
public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
final long now = System.currentTimeMillis();
final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
@@ -881,4 +935,5 @@
public interface Migration {
ZenModeConfig migrate(XmlV1 v1);
}
+
}