Loading core/java/com/android/internal/statusbar/IStatusBarService.aidl +3 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.internal.statusbar; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; import com.android.internal.statusbar.NotificationVisibility; import android.service.notification.StatusBarNotification; /** @hide */ Loading Loading @@ -53,8 +54,8 @@ interface IStatusBarService int uid, int initialPid, String message, int userId); void onClearAllNotifications(int userId); void onNotificationClear(String pkg, String tag, int id, int userId); void onNotificationVisibilityChanged( in String[] newlyVisibleKeys, in String[] noLongerVisibleKeys); void onNotificationVisibilityChanged( in NotificationVisibility[] newlyVisibleKeys, in NotificationVisibility[] noLongerVisibleKeys); void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded); void setSystemUiVisibility(int vis, int mask, String cause); void setWindowState(int window, int state); Loading core/java/com/android/internal/statusbar/NotificationVisibility.aidl 0 → 100644 +20 −0 Original line number Diff line number Diff line /* * 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 com.android.internal.statusbar; parcelable NotificationVisibility; core/java/com/android/internal/statusbar/NotificationVisibility.java 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * 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 com.android.internal.statusbar; import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import java.util.ArrayDeque; import java.util.Collection; public class NotificationVisibility implements Parcelable { private static final String TAG = "NoViz"; private static final int MAX_POOL_SIZE = 25; private static ArrayDeque<NotificationVisibility> sPool = new ArrayDeque<>(MAX_POOL_SIZE); private static int sNexrId = 0; public String key; public int rank; public boolean visible = true; /*package*/ int id; private NotificationVisibility() { id = sNexrId++; } private NotificationVisibility(String key, int rank, boolean visibile) { this(); this.key = key; this.rank = rank; this.visible = visibile; } @Override public String toString() { return "NotificationVisibility(id=" + id + "key=" + key + " rank=" + rank + (visible?" visible":"") + " )"; } @Override public NotificationVisibility clone() { return obtain(this.key, this.rank, this.visible); } @Override public int hashCode() { // allow lookups by key, which _should_ never be null. return key == null ? 0 : key.hashCode(); } @Override public boolean equals(Object that) { // allow lookups by key, which _should_ never be null. if (that instanceof NotificationVisibility) { NotificationVisibility thatViz = (NotificationVisibility) that; return (key == null && thatViz.key == null) || key.equals(thatViz.key); } return false; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeString(this.key); out.writeInt(this.rank); out.writeInt(this.visible ? 1 : 0); } private void readFromParcel(Parcel in) { this.key = in.readString(); this.rank = in.readInt(); this.visible = in.readInt() != 0; } /** * Return a new NotificationVisibility instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static NotificationVisibility obtain(String key, int rank, boolean visible) { NotificationVisibility vo = obtain(); vo.key = key; vo.rank = rank; vo.visible = visible; return vo; } private static NotificationVisibility obtain(Parcel in) { NotificationVisibility vo = obtain(); vo.readFromParcel(in); return vo; } private static NotificationVisibility obtain() { synchronized (sPool) { if (!sPool.isEmpty()) { return sPool.poll(); } } return new NotificationVisibility(); } /** * Return a NotificationVisibility instance to the global pool. * <p> * You MUST NOT touch the NotificationVisibility after calling this function because it has * effectively been freed. * </p> */ public void recycle() { if (key == null) { // do nothing on multiple recycles return; } key = null; if (sPool.size() < MAX_POOL_SIZE) { synchronized (sPool) { sPool.offer(this); } } } /** * Parcelable.Creator that instantiates NotificationVisibility objects */ public static final Parcelable.Creator<NotificationVisibility> CREATOR = new Parcelable.Creator<NotificationVisibility>() { public NotificationVisibility createFromParcel(Parcel parcel) { return obtain(parcel); } public NotificationVisibility[] newArray(int size) { return new NotificationVisibility[size]; } }; } packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +52 −22 Original line number Diff line number Diff line Loading @@ -96,6 +96,7 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.statusbar.StatusBarIcon; import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.keyguard.ViewMediatorCallback; Loading Loading @@ -457,7 +458,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private int mDisabledUnmodified2; /** Keys of notifications currently visible to the user. */ private final ArraySet<String> mCurrentlyVisibleNotifications = new ArraySet<String>(); private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = new ArraySet<>(); private long mLastVisibilityReportUptimeMs; private final ShadeUpdates mShadeUpdates = new ShadeUpdates(); Loading Loading @@ -498,12 +500,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Tracks notifications currently visible in mNotificationStackScroller and // emits visibility events via NoMan on changes. private final Runnable mVisibilityReporter = new Runnable() { private final ArrayList<String> mTmpNewlyVisibleNotifications = new ArrayList<String>(); private final ArrayList<String> mTmpCurrentlyVisibleNotifications = new ArrayList<String>(); private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications = new ArraySet<>(); @Override public void run() { mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); final String mediaKey = getCurrentMediaNotificationKey(); // 1. Loop over mNotificationData entries: // A. Keep list of visible notifications. Loading @@ -518,31 +525,45 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, for (int i = 0; i < N; i++) { Entry entry = activeNotifications.get(i); String key = entry.notification.getKey(); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(key); boolean currentlyVisible = boolean isVisible = (mStackScroller.getChildLocation(entry.row) & VISIBLE_LOCATIONS) != 0; if (currentlyVisible) { NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); if (isVisible) { // Build new set of visible notifications. mTmpCurrentlyVisibleNotifications.add(key); mTmpCurrentlyVisibleNotifications.add(visObj); if (!previouslyVisible) { mTmpNewlyVisibleNotifications.add(visObj); } if (!previouslyVisible && currentlyVisible) { mTmpNewlyVisibleNotifications.add(key); } else { // release object visObj.recycle(); } } ArraySet<String> noLongerVisibleNotifications = mCurrentlyVisibleNotifications; noLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications); mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); logNotificationVisibilityChanges( mTmpNewlyVisibleNotifications, noLongerVisibleNotifications); mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications); mCurrentlyVisibleNotifications.clear(); recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); mTmpNewlyVisibleNotifications.clear(); recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); mTmpCurrentlyVisibleNotifications.clear(); mTmpNewlyVisibleNotifications.clear(); mTmpNoLongerVisibleNotifications.clear(); } }; private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { final int N = array.size(); for (int i = 0 ; i < N; i++) { array.valueAt(i).recycle(); } array.clear(); } private final View.OnClickListener mOverflowClickListener = new View.OnClickListener() { @Override public void onClick(View v) { Loading Loading @@ -2987,9 +3008,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Report all notifications as invisible and turn down the // reporter. if (!mCurrentlyVisibleNotifications.isEmpty()) { logNotificationVisibilityChanges( Collections.<String>emptyList(), mCurrentlyVisibleNotifications); mCurrentlyVisibleNotifications.clear(); logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(), mCurrentlyVisibleNotifications); recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); } mHandler.removeCallbacks(mVisibilityReporter); mStackScroller.setChildLocationsChangedListener(null); Loading @@ -3007,18 +3028,27 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void logNotificationVisibilityChanges( Collection<String> newlyVisible, Collection<String> noLongerVisible) { Collection<NotificationVisibility> newlyVisible, Collection<NotificationVisibility> noLongerVisible) { if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) { return; } String[] newlyVisibleAr = newlyVisible.toArray(new String[newlyVisible.size()]); String[] noLongerVisibleAr = noLongerVisible.toArray(new String[noLongerVisible.size()]); NotificationVisibility[] newlyVisibleAr = newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]); NotificationVisibility[] noLongerVisibleAr = noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]); try { mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr); } catch (RemoteException e) { // Ignore. } setNotificationsShown(newlyVisibleAr); final int N = newlyVisible.size(); String[] newlyVisibleKeyAr = new String[N]; for (int i = 0; i < N; i++) { newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; } setNotificationsShown(newlyVisibleKeyAr); } // State logging Loading services/core/java/com/android/server/EventLogTags.logtags +1 −1 Original line number Diff line number Diff line Loading @@ -74,7 +74,7 @@ option java_package com.android.server # when a notification has been canceled 27530 notification_canceled (key|3),(reason|1),(lifespan|1),(freshness|1),(exposure|1) # replaces 27510 with a row per notification 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1) 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1) # a notification emited noise, vibration, or light 27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1) Loading Loading
core/java/com/android/internal/statusbar/IStatusBarService.aidl +3 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.internal.statusbar; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; import com.android.internal.statusbar.NotificationVisibility; import android.service.notification.StatusBarNotification; /** @hide */ Loading Loading @@ -53,8 +54,8 @@ interface IStatusBarService int uid, int initialPid, String message, int userId); void onClearAllNotifications(int userId); void onNotificationClear(String pkg, String tag, int id, int userId); void onNotificationVisibilityChanged( in String[] newlyVisibleKeys, in String[] noLongerVisibleKeys); void onNotificationVisibilityChanged( in NotificationVisibility[] newlyVisibleKeys, in NotificationVisibility[] noLongerVisibleKeys); void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded); void setSystemUiVisibility(int vis, int mask, String cause); void setWindowState(int window, int state); Loading
core/java/com/android/internal/statusbar/NotificationVisibility.aidl 0 → 100644 +20 −0 Original line number Diff line number Diff line /* * 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 com.android.internal.statusbar; parcelable NotificationVisibility;
core/java/com/android/internal/statusbar/NotificationVisibility.java 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * 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 com.android.internal.statusbar; import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import java.util.ArrayDeque; import java.util.Collection; public class NotificationVisibility implements Parcelable { private static final String TAG = "NoViz"; private static final int MAX_POOL_SIZE = 25; private static ArrayDeque<NotificationVisibility> sPool = new ArrayDeque<>(MAX_POOL_SIZE); private static int sNexrId = 0; public String key; public int rank; public boolean visible = true; /*package*/ int id; private NotificationVisibility() { id = sNexrId++; } private NotificationVisibility(String key, int rank, boolean visibile) { this(); this.key = key; this.rank = rank; this.visible = visibile; } @Override public String toString() { return "NotificationVisibility(id=" + id + "key=" + key + " rank=" + rank + (visible?" visible":"") + " )"; } @Override public NotificationVisibility clone() { return obtain(this.key, this.rank, this.visible); } @Override public int hashCode() { // allow lookups by key, which _should_ never be null. return key == null ? 0 : key.hashCode(); } @Override public boolean equals(Object that) { // allow lookups by key, which _should_ never be null. if (that instanceof NotificationVisibility) { NotificationVisibility thatViz = (NotificationVisibility) that; return (key == null && thatViz.key == null) || key.equals(thatViz.key); } return false; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeString(this.key); out.writeInt(this.rank); out.writeInt(this.visible ? 1 : 0); } private void readFromParcel(Parcel in) { this.key = in.readString(); this.rank = in.readInt(); this.visible = in.readInt() != 0; } /** * Return a new NotificationVisibility instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static NotificationVisibility obtain(String key, int rank, boolean visible) { NotificationVisibility vo = obtain(); vo.key = key; vo.rank = rank; vo.visible = visible; return vo; } private static NotificationVisibility obtain(Parcel in) { NotificationVisibility vo = obtain(); vo.readFromParcel(in); return vo; } private static NotificationVisibility obtain() { synchronized (sPool) { if (!sPool.isEmpty()) { return sPool.poll(); } } return new NotificationVisibility(); } /** * Return a NotificationVisibility instance to the global pool. * <p> * You MUST NOT touch the NotificationVisibility after calling this function because it has * effectively been freed. * </p> */ public void recycle() { if (key == null) { // do nothing on multiple recycles return; } key = null; if (sPool.size() < MAX_POOL_SIZE) { synchronized (sPool) { sPool.offer(this); } } } /** * Parcelable.Creator that instantiates NotificationVisibility objects */ public static final Parcelable.Creator<NotificationVisibility> CREATOR = new Parcelable.Creator<NotificationVisibility>() { public NotificationVisibility createFromParcel(Parcel parcel) { return obtain(parcel); } public NotificationVisibility[] newArray(int size) { return new NotificationVisibility[size]; } }; }
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +52 −22 Original line number Diff line number Diff line Loading @@ -96,6 +96,7 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.statusbar.StatusBarIcon; import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.keyguard.ViewMediatorCallback; Loading Loading @@ -457,7 +458,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private int mDisabledUnmodified2; /** Keys of notifications currently visible to the user. */ private final ArraySet<String> mCurrentlyVisibleNotifications = new ArraySet<String>(); private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = new ArraySet<>(); private long mLastVisibilityReportUptimeMs; private final ShadeUpdates mShadeUpdates = new ShadeUpdates(); Loading Loading @@ -498,12 +500,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Tracks notifications currently visible in mNotificationStackScroller and // emits visibility events via NoMan on changes. private final Runnable mVisibilityReporter = new Runnable() { private final ArrayList<String> mTmpNewlyVisibleNotifications = new ArrayList<String>(); private final ArrayList<String> mTmpCurrentlyVisibleNotifications = new ArrayList<String>(); private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications = new ArraySet<>(); @Override public void run() { mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); final String mediaKey = getCurrentMediaNotificationKey(); // 1. Loop over mNotificationData entries: // A. Keep list of visible notifications. Loading @@ -518,31 +525,45 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, for (int i = 0; i < N; i++) { Entry entry = activeNotifications.get(i); String key = entry.notification.getKey(); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(key); boolean currentlyVisible = boolean isVisible = (mStackScroller.getChildLocation(entry.row) & VISIBLE_LOCATIONS) != 0; if (currentlyVisible) { NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); if (isVisible) { // Build new set of visible notifications. mTmpCurrentlyVisibleNotifications.add(key); mTmpCurrentlyVisibleNotifications.add(visObj); if (!previouslyVisible) { mTmpNewlyVisibleNotifications.add(visObj); } if (!previouslyVisible && currentlyVisible) { mTmpNewlyVisibleNotifications.add(key); } else { // release object visObj.recycle(); } } ArraySet<String> noLongerVisibleNotifications = mCurrentlyVisibleNotifications; noLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications); mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); logNotificationVisibilityChanges( mTmpNewlyVisibleNotifications, noLongerVisibleNotifications); mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications); mCurrentlyVisibleNotifications.clear(); recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); mTmpNewlyVisibleNotifications.clear(); recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); mTmpCurrentlyVisibleNotifications.clear(); mTmpNewlyVisibleNotifications.clear(); mTmpNoLongerVisibleNotifications.clear(); } }; private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { final int N = array.size(); for (int i = 0 ; i < N; i++) { array.valueAt(i).recycle(); } array.clear(); } private final View.OnClickListener mOverflowClickListener = new View.OnClickListener() { @Override public void onClick(View v) { Loading Loading @@ -2987,9 +3008,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Report all notifications as invisible and turn down the // reporter. if (!mCurrentlyVisibleNotifications.isEmpty()) { logNotificationVisibilityChanges( Collections.<String>emptyList(), mCurrentlyVisibleNotifications); mCurrentlyVisibleNotifications.clear(); logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(), mCurrentlyVisibleNotifications); recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); } mHandler.removeCallbacks(mVisibilityReporter); mStackScroller.setChildLocationsChangedListener(null); Loading @@ -3007,18 +3028,27 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void logNotificationVisibilityChanges( Collection<String> newlyVisible, Collection<String> noLongerVisible) { Collection<NotificationVisibility> newlyVisible, Collection<NotificationVisibility> noLongerVisible) { if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) { return; } String[] newlyVisibleAr = newlyVisible.toArray(new String[newlyVisible.size()]); String[] noLongerVisibleAr = noLongerVisible.toArray(new String[noLongerVisible.size()]); NotificationVisibility[] newlyVisibleAr = newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]); NotificationVisibility[] noLongerVisibleAr = noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]); try { mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr); } catch (RemoteException e) { // Ignore. } setNotificationsShown(newlyVisibleAr); final int N = newlyVisible.size(); String[] newlyVisibleKeyAr = new String[N]; for (int i = 0; i < N; i++) { newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; } setNotificationsShown(newlyVisibleKeyAr); } // State logging Loading
services/core/java/com/android/server/EventLogTags.logtags +1 −1 Original line number Diff line number Diff line Loading @@ -74,7 +74,7 @@ option java_package com.android.server # when a notification has been canceled 27530 notification_canceled (key|3),(reason|1),(lifespan|1),(freshness|1),(exposure|1) # replaces 27510 with a row per notification 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1) 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1) # a notification emited noise, vibration, or light 27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1) Loading