void setPackagePriority(String pkg, int uid, int priority);
int getPackagePriority(String pkg, int uid);
+ void setPackageVisibilityOverride(String pkg, int uid, int visibility);
+ int getPackageVisibilityOverride(String pkg, int uid);
+
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
StatusBarNotification[] getActiveNotifications(String callingPkg);
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
* current {@link RankingMap}.
*/
public static class Ranking {
+ /** Value signifying that the user has not expressed a per-app visibility override value.
+ * @hide */
+ public static final int VISIBILITY_NO_OVERRIDE = -1000;
+
private String mKey;
private int mRank = -1;
private boolean mIsAmbient;
private boolean mMatchesInterruptionFilter;
+ private int mVisibilityOverride;
public Ranking() {}
}
/**
+ * Returns the user specificed visibility for the package that posted
+ * this notification, or
+ * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
+ * no such preference has been expressed.
+ * @hide
+ */
+ public int getVisibilityOverride() {
+ return mVisibilityOverride;
+ }
+
+ /**
* Returns whether the notification meets the user's interruption
* filter.
*
}
private void populate(String key, int rank, boolean isAmbient,
- boolean matchesInterruptionFilter) {
+ boolean matchesInterruptionFilter, int visibilityOverride) {
mKey = key;
mRank = rank;
mIsAmbient = isAmbient;
mMatchesInterruptionFilter = matchesInterruptionFilter;
+ mVisibilityOverride = visibilityOverride;
}
}
private final NotificationRankingUpdate mRankingUpdate;
private ArrayMap<String,Integer> mRanks;
private ArraySet<Object> mIntercepted;
+ private ArrayMap<String, Integer> mVisibilityOverrides;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
*/
public boolean getRanking(String key, Ranking outRanking) {
int rank = getRank(key);
- outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key));
+ outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key),
+ getVisibilityOverride(key));
return rank >= 0;
}
return mIntercepted.contains(key);
}
+ private int getVisibilityOverride(String key) {
+ synchronized (this) {
+ if (mVisibilityOverrides == null) {
+ buildVisibilityOverridesLocked();
+ }
+ }
+ Integer overide = mVisibilityOverrides.get(key);
+ if (overide == null) {
+ return Ranking.VISIBILITY_NO_OVERRIDE;
+ }
+ return overide.intValue();
+ }
+
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
Collections.addAll(mIntercepted, dndInterceptedKeys);
}
+ // Locked by 'this'
+ private void buildVisibilityOverridesLocked() {
+ Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
+ mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
+ for (String key: visibilityBundle.keySet()) {
+ mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
+ }
+ }
+
// ----------- Parcelable
@Override
*/
package android.service.notification;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
private final String[] mKeys;
private final String[] mInterceptedKeys;
private final int mFirstAmbientIndex;
+ private final Bundle mVisibilityOverrides;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
- int firstAmbientIndex) {
+ Bundle visibilityOverrides, int firstAmbientIndex) {
mKeys = keys;
mFirstAmbientIndex = firstAmbientIndex;
mInterceptedKeys = interceptedKeys;
+ mVisibilityOverrides = visibilityOverrides;
}
public NotificationRankingUpdate(Parcel in) {
mKeys = in.readStringArray();
mFirstAmbientIndex = in.readInt();
mInterceptedKeys = in.readStringArray();
+ mVisibilityOverrides = in.readBundle();
}
@Override
out.writeStringArray(mKeys);
out.writeInt(mFirstAmbientIndex);
out.writeStringArray(mInterceptedKeys);
+ out.writeBundle(mVisibilityOverrides);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
public String[] getInterceptedKeys() {
return mInterceptedKeys;
}
+
+ public Bundle getVisibilityOverrides() {
+ return mVisibilityOverrides;
+ }
}
<item>com.android.server.notification.ValidateNotificationPeople</item>
<item>com.android.server.notification.PackagePriorityExtractor</item>
<item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
+ <item>com.android.server.notification.PackageVisibilityExtractor</item>
</string-array>
<!-- Flag indicating that this device does not rotate and will always remain in its default
package com.android.systemui.statusbar;
import android.app.Notification;
+import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
return false;
}
+ public int getVisibilityOverride(String key) {
+ if (mRankingMap != null) {
+ mRankingMap.getRanking(key, mTmpRanking);
+ return mTmpRanking.getVisibilityOverride();
+ }
+ return NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+ }
+
private void updateRankingAndSort(RankingMap ranking) {
if (ranking != null) {
mRankingMap = ranking;
* Provides access to keyguard state and user settings dependent data.
*/
public interface Environment {
- public boolean shouldHideSensitiveContents(int userId);
+ public boolean shouldHideSensitiveContents(int userid);
public boolean isDeviceProvisioned();
public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
public String getCurrentMediaNotificationKey();
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
// Display public version of the notification if we need to redact.
final boolean hideSensitive =
!userAllowsPrivateNotificationsInPublic(ent.notification.getUserId());
- boolean sensitive = vis == Notification.VISIBILITY_PRIVATE;
- boolean showingPublic = sensitive && hideSensitive && isLockscreenPublicMode();
- ent.row.setSensitive(sensitive && hideSensitive);
+ boolean sensitiveNote = vis == Notification.VISIBILITY_PRIVATE;
+ boolean sensitivePackage = packageHasVisibilityOverride(ent.notification.getKey());
+ boolean sensitive = (sensitiveNote && hideSensitive) || sensitivePackage;
+ boolean showingPublic = sensitive && isLockscreenPublicMode();
+ ent.row.setSensitive(sensitive);
if (ent.autoRedacted && ent.legacy) {
-
// TODO: Also fade this? Or, maybe easier (and better), provide a dark redacted form
// for legacy auto redacted notifications.
if (showingPublic) {
mShadeUpdates.check();
}
+ private boolean packageHasVisibilityOverride(String key) {
+ return mNotificationData.getVisibilityOverride(key)
+ != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+ }
+
private void updateClearAll() {
boolean showDismissView =
mState != StatusBarState.KEYGUARD &&
return mRankingHelper.getPackagePriority(pkg, uid);
}
+ @Override
+ public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
+ checkCallerIsSystem();
+ mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
+ savePolicyFile();
+ }
+
+ @Override
+ public int getPackageVisibilityOverride(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mRankingHelper.getPackageVisibilityOverride(pkg, uid);
+ }
+
/**
* System-only API for getting a list of current (i.e. not cleared) notifications.
*
}
int indexBefore = findNotificationRecordIndexLocked(record);
boolean interceptBefore = record.isIntercepted();
+ int visibilityBefore = record.getPackageVisibilityOverride();
recon.applyChangesLocked(record);
applyZenModeLocked(record);
mRankingHelper.sort(mNotificationList);
int indexAfter = findNotificationRecordIndexLocked(record);
boolean interceptAfter = record.isIntercepted();
- changed = indexBefore != indexAfter || interceptBefore != interceptAfter;
+ int visibilityAfter = record.getPackageVisibilityOverride();
+ changed = indexBefore != indexAfter || interceptBefore != interceptAfter
+ || visibilityBefore != visibilityAfter;
if (interceptBefore && !interceptAfter) {
buzzBeepBlinkLocked(record);
}
synchronized (mNotificationList) {
final int N = mNotificationList.size();
ArrayList<String> orderBefore = new ArrayList<String>(N);
+ int[] visibilities = new int[N];
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
+ visibilities[i] = r.getPackageVisibilityOverride();
mRankingHelper.extractSignals(r);
}
- mRankingHelper.sort(mNotificationList);
for (int i = 0; i < N; i++) {
- if (!orderBefore.get(i).equals(mNotificationList.get(i).getKey())) {
+ mRankingHelper.sort(mNotificationList);
+ final NotificationRecord r = mNotificationList.get(i);
+ if (!orderBefore.get(i).equals(r.getKey())
+ || visibilities[i] != r.getPackageVisibilityOverride()) {
scheduleSendRankingUpdate();
return;
}
final int N = mNotificationList.size();
ArrayList<String> keys = new ArrayList<String>(N);
ArrayList<String> interceptedKeys = new ArrayList<String>(N);
+ Bundle visibilityOverrides = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
if (record.isIntercepted()) {
interceptedKeys.add(record.sbn.getKey());
}
+ if (record.getPackageVisibilityOverride()
+ != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
+ visibilityOverrides.putInt(record.sbn.getKey(),
+ record.getPackageVisibilityOverride());
+ }
if (speedBumpIndex == -1 &&
record.sbn.getNotification().priority == Notification.PRIORITY_MIN) {
speedBumpIndex = keys.size() - 1;
}
String[] keysAr = keys.toArray(new String[keys.size()]);
String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]);
- return new NotificationRankingUpdate(keysAr, interceptedKeysAr, speedBumpIndex);
+ return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
+ speedBumpIndex);
}
private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
private int mAuthoritativeRank;
private String mGlobalSortKey;
+ private int mPackageVisibility;
@VisibleForTesting
public NotificationRecord(StatusBarNotification sbn, int score)
mContactAffinity = previous.mContactAffinity;
mRecentlyIntrusive = previous.mRecentlyIntrusive;
mPackagePriority = previous.mPackagePriority;
+ mPackageVisibility = previous.mPackageVisibility;
mIntercept = previous.mIntercept;
mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
// Don't copy mGlobalSortKey, recompute it.
pw.println(prefix + " mContactAffinity=" + mContactAffinity);
pw.println(prefix + " mRecentlyIntrusive=" + mRecentlyIntrusive);
pw.println(prefix + " mPackagePriority=" + mPackagePriority);
+ pw.println(prefix + " mPackageVisibility=" + mPackageVisibility);
pw.println(prefix + " mIntercept=" + mIntercept);
pw.println(prefix + " mGlobalSortKey=" + mGlobalSortKey);
pw.println(prefix + " mRankingTimeMs=" + mRankingTimeMs);
return mPackagePriority;
}
+ public void setPackageVisibilityOverride(int packageVisibility) {
+ mPackageVisibility = packageVisibility;
+ }
+
+ public int getPackageVisibilityOverride() {
+ return mPackageVisibility;
+ }
+
public boolean setIntercepted(boolean intercept) {
mIntercept = intercept;
return mIntercept;
--- /dev/null
+/*
+* Copyright (C) 2014 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 com.android.server.notification;
+
+import android.content.Context;
+import android.util.Slog;
+
+public class PackageVisibilityExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "PackageVisibilityExtractor";
+ private static final boolean DBG = false;
+
+ private RankingConfig mConfig;
+
+ public void initialize(Context ctx) {
+ if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ }
+
+ public RankingReconsideration process(NotificationRecord record) {
+ if (record == null || record.getNotification() == null) {
+ if (DBG) Slog.d(TAG, "skipping empty notification");
+ return null;
+ }
+
+ if (mConfig == null) {
+ if (DBG) Slog.d(TAG, "missing config");
+ return null;
+ }
+
+ final int packageVisibility = mConfig.getPackageVisibilityOverride(
+ record.sbn.getPackageName(), record.sbn.getUid());
+ record.setPackageVisibilityOverride(packageVisibility);
+
+ return null;
+ }
+
+ @Override
+ public void setConfig(RankingConfig config) {
+ mConfig = config;
+ }
+}
int getPackagePriority(String packageName, int uid);
void setPackagePriority(String packageName, int uid, int priority);
+
+ int getPackageVisibilityOverride(String packageName, int uid);
+
+ void setPackageVisibilityOverride(String packageName, int uid, int visibility);
}
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseIntArray;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
public class RankingHelper implements RankingConfig {
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
private static final String ATT_PRIORITY = "priority";
+ private static final String ATT_VISIBILITY = "visibility";
private final NotificationSignalExtractor[] mSignalExtractors;
private final NotificationComparator mPreliminaryComparator = new NotificationComparator();
// Package name to uid, to priority. Would be better as Table<String, Int, Int>
private final ArrayMap<String, SparseIntArray> mPackagePriorities;
+ private final ArrayMap<String, SparseIntArray> mPackageVisibilities;
private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp;
private final Context mContext;
mContext = context;
mRankingHandler = rankingHandler;
mPackagePriorities = new ArrayMap<String, SparseIntArray>();
+ mPackageVisibilities = new ArrayMap<String, SparseIntArray>();
final int N = extractorNames.length;
mSignalExtractors = new NotificationSignalExtractor[N];
if (TAG_PACKAGE.equals(tag)) {
int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL);
int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT);
+ int vis = safeInt(parser, ATT_VISIBILITY,
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
String name = parser.getAttributeValue(null, ATT_NAME);
- if (!TextUtils.isEmpty(name) && priority != Notification.PRIORITY_DEFAULT) {
- SparseIntArray priorityByUid = mPackagePriorities.get(name);
- if (priorityByUid == null) {
- priorityByUid = new SparseIntArray();
- mPackagePriorities.put(name, priorityByUid);
+ if (!TextUtils.isEmpty(name)) {
+ if (priority != Notification.PRIORITY_DEFAULT) {
+ SparseIntArray priorityByUid = mPackagePriorities.get(name);
+ if (priorityByUid == null) {
+ priorityByUid = new SparseIntArray();
+ mPackagePriorities.put(name, priorityByUid);
+ }
+ priorityByUid.put(uid, priority);
+ }
+ if (vis != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
+ SparseIntArray visibilityByUid = mPackageVisibilities.get(name);
+ if (visibilityByUid == null) {
+ visibilityByUid = new SparseIntArray();
+ mPackageVisibilities.put(name, visibilityByUid);
+ }
+ visibilityByUid.put(uid, vis);
}
- priorityByUid.put(uid, priority);
}
}
}
out.startTag(null, TAG_RANKING);
out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
- final int N = mPackagePriorities.size();
- for (int i = 0; i < N; i ++) {
- String name = mPackagePriorities.keyAt(i);
- SparseIntArray priorityByUid = mPackagePriorities.get(name);
- final int M = priorityByUid.size();
- for (int j = 0; j < M; j++) {
- int uid = priorityByUid.keyAt(j);
- int priority = priorityByUid.get(uid);
+ final Set<String> packageNames = new ArraySet<>(mPackagePriorities.size()
+ + mPackageVisibilities.size());
+ packageNames.addAll(mPackagePriorities.keySet());
+ packageNames.addAll(mPackageVisibilities.keySet());
+ final Set<Integer> packageUids = new ArraySet<>();
+ for (String packageName : packageNames) {
+ packageUids.clear();
+ SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
+ SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
+ if (priorityByUid != null) {
+ final int M = priorityByUid.size();
+ for (int j = 0; j < M; j++) {
+ packageUids.add(priorityByUid.keyAt(j));
+ }
+ }
+ if (visibilityByUid != null) {
+ final int M = visibilityByUid.size();
+ for (int j = 0; j < M; j++) {
+ packageUids.add(visibilityByUid.keyAt(j));
+ }
+ }
+ for (Integer uid : packageUids) {
out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATT_NAME, name);
+ out.attribute(null, ATT_NAME, packageName);
+ if (priorityByUid != null) {
+ final int priority = priorityByUid.get(uid);
+ if (priority != Notification.PRIORITY_DEFAULT) {
+ out.attribute(null, ATT_PRIORITY, Integer.toString(priority));
+ }
+ }
+ if (visibilityByUid != null) {
+ final int visibility = visibilityByUid.get(uid);
+ if (visibility != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
+ out.attribute(null, ATT_VISIBILITY, Integer.toString(visibility));
+ }
+ }
out.attribute(null, ATT_UID, Integer.toString(uid));
- out.attribute(null, ATT_PRIORITY, Integer.toString(priority));
out.endTag(null, TAG_PACKAGE);
}
}
updateConfig();
}
+ @Override
+ public int getPackageVisibilityOverride(String packageName, int uid) {
+ int visibility = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+ SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
+ if (visibilityByUid != null) {
+ visibility = visibilityByUid.get(uid,
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ }
+ return visibility;
+ }
+
+ @Override
+ public void setPackageVisibilityOverride(String packageName, int uid, int visibility) {
+ if (visibility == getPackageVisibilityOverride(packageName, uid)) {
+ return;
+ }
+ SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
+ if (visibilityByUid == null) {
+ visibilityByUid = new SparseIntArray();
+ mPackageVisibilities.put(packageName, visibilityByUid);
+ }
+ visibilityByUid.put(uid, visibility);
+ updateConfig();
+ }
+
public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
if (filter == null) {
final int N = mSignalExtractors.length;