NotificationUsageStats.java revision 546bec8ebf2cf865e88d02cc8cb29563ad224967
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.server.notification; 18 19import com.android.server.notification.NotificationManagerService.NotificationRecord; 20 21import android.os.SystemClock; 22import android.service.notification.StatusBarNotification; 23import android.util.Log; 24 25import java.io.PrintWriter; 26import java.util.HashMap; 27import java.util.Map; 28 29/** 30 * Keeps track of notification activity, display, and user interaction. 31 * 32 * <p>This class receives signals from NoMan and keeps running stats of 33 * notification usage. Some metrics are updated as events occur. Others, namely 34 * those involving durations, are updated as the notification is canceled.</p> 35 * 36 * <p>This class is thread-safe.</p> 37 * 38 * {@hide} 39 */ 40public class NotificationUsageStats { 41 42 // Guarded by synchronized(this). 43 private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>(); 44 45 /** 46 * Called when a notification has been posted. 47 */ 48 public synchronized void registerPostedByApp(NotificationRecord notification) { 49 notification.stats.posttimeElapsedMs = SystemClock.elapsedRealtime(); 50 for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { 51 stats.numPostedByApp++; 52 } 53 } 54 55 /** 56 * Called when a notification has been updated. 57 */ 58 public void registerUpdatedByApp(NotificationRecord notification) { 59 for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { 60 stats.numUpdatedByApp++; 61 } 62 } 63 64 /** 65 * Called when the originating app removed the notification programmatically. 66 */ 67 public synchronized void registerRemovedByApp(NotificationRecord notification) { 68 for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { 69 stats.numRemovedByApp++; 70 stats.collect(notification.stats); 71 } 72 } 73 74 /** 75 * Called when the user dismissed the notification via the UI. 76 */ 77 public synchronized void registerDismissedByUser(NotificationRecord notification) { 78 notification.stats.onDismiss(); 79 for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { 80 stats.numDismissedByUser++; 81 stats.collect(notification.stats); 82 } 83 } 84 85 /** 86 * Called when the user clicked the notification in the UI. 87 */ 88 public synchronized void registerClickedByUser(NotificationRecord notification) { 89 notification.stats.onClick(); 90 for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { 91 stats.numClickedByUser++; 92 } 93 } 94 95 /** 96 * Called when the notification is canceled because the user clicked it. 97 * 98 * <p>Called after {@link #registerClickedByUser(NotificationRecord)}.</p> 99 */ 100 public synchronized void registerCancelDueToClick(NotificationRecord notification) { 101 // No explicit stats for this (the click has already been registered in 102 // registerClickedByUser), just make sure the single notification stats 103 // are folded up into aggregated stats. 104 for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { 105 stats.collect(notification.stats); 106 } 107 } 108 109 /** 110 * Called when the notification is canceled due to unknown reasons. 111 * 112 * <p>Called for notifications of apps being uninstalled, for example.</p> 113 */ 114 public synchronized void registerCancelUnknown(NotificationRecord notification) { 115 // Fold up individual stats. 116 for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { 117 stats.collect(notification.stats); 118 } 119 } 120 121 // Locked by this. 122 private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) { 123 StatusBarNotification n = record.sbn; 124 125 String user = String.valueOf(n.getUserId()); 126 String userPackage = user + ":" + n.getPackageName(); 127 128 // TODO: Use pool of arrays. 129 return new AggregatedStats[] { 130 getOrCreateAggregatedStatsLocked(user), 131 getOrCreateAggregatedStatsLocked(userPackage), 132 getOrCreateAggregatedStatsLocked(n.getKey()), 133 }; 134 } 135 136 // Locked by this. 137 private AggregatedStats getOrCreateAggregatedStatsLocked(String key) { 138 AggregatedStats result = mStats.get(key); 139 if (result == null) { 140 result = new AggregatedStats(key); 141 mStats.put(key, result); 142 } 143 return result; 144 } 145 146 public synchronized void dump(PrintWriter pw, String indent) { 147 for (AggregatedStats as : mStats.values()) { 148 as.dump(pw, indent); 149 } 150 } 151 152 /** 153 * Aggregated notification stats. 154 */ 155 private static class AggregatedStats { 156 public final String key; 157 158 // ---- Updated as the respective events occur. 159 public int numPostedByApp; 160 public int numUpdatedByApp; 161 public int numRemovedByApp; 162 public int numClickedByUser; 163 public int numDismissedByUser; 164 165 // ---- Updated when a notification is canceled. 166 public final Aggregate posttimeMs = new Aggregate(); 167 public final Aggregate posttimeToDismissMs = new Aggregate(); 168 public final Aggregate posttimeToFirstClickMs = new Aggregate(); 169 170 public AggregatedStats(String key) { 171 this.key = key; 172 } 173 174 public void collect(SingleNotificationStats singleNotificationStats) { 175 posttimeMs.addSample( 176 SystemClock.elapsedRealtime() - singleNotificationStats.posttimeElapsedMs); 177 if (singleNotificationStats.posttimeToDismissMs >= 0) { 178 posttimeToDismissMs.addSample(singleNotificationStats.posttimeToDismissMs); 179 } 180 if (singleNotificationStats.posttimeToFirstClickMs >= 0) { 181 posttimeToFirstClickMs.addSample(singleNotificationStats.posttimeToFirstClickMs); 182 } 183 } 184 185 public void dump(PrintWriter pw, String indent) { 186 pw.println(toStringWithIndent(indent)); 187 } 188 189 @Override 190 public String toString() { 191 return toStringWithIndent(""); 192 } 193 194 private String toStringWithIndent(String indent) { 195 return indent + "AggregatedStats{\n" + 196 indent + " key='" + key + "',\n" + 197 indent + " numPostedByApp=" + numPostedByApp + ",\n" + 198 indent + " numUpdatedByApp=" + numUpdatedByApp + ",\n" + 199 indent + " numRemovedByApp=" + numRemovedByApp + ",\n" + 200 indent + " numClickedByUser=" + numClickedByUser + ",\n" + 201 indent + " numDismissedByUser=" + numDismissedByUser + ",\n" + 202 indent + " posttimeMs=" + posttimeMs + ",\n" + 203 indent + " posttimeToDismissMs=" + posttimeToDismissMs + ",\n" + 204 indent + " posttimeToFirstClickMs=" + posttimeToFirstClickMs + ",\n" + 205 indent + "}"; 206 } 207 } 208 209 /** 210 * Tracks usage of an individual notification that is currently active. 211 */ 212 public static class SingleNotificationStats { 213 /** SystemClock.elapsedRealtime() when the notification was posted. */ 214 public long posttimeElapsedMs = -1; 215 /** Elapsed time since the notification was posted until it was first clicked, or -1. */ 216 public long posttimeToFirstClickMs = -1; 217 /** Elpased time since the notification was posted until it was dismissed by the user. */ 218 public long posttimeToDismissMs = -1; 219 220 /** 221 * Called when the user clicked the notification. 222 */ 223 public void onClick() { 224 if (posttimeToFirstClickMs < 0) { 225 posttimeToFirstClickMs = SystemClock.elapsedRealtime() - posttimeElapsedMs; 226 } 227 } 228 229 /** 230 * Called when the user removed the notification. 231 */ 232 public void onDismiss() { 233 if (posttimeToDismissMs < 0) { 234 posttimeToDismissMs = SystemClock.elapsedRealtime() - posttimeElapsedMs; 235 } 236 } 237 238 @Override 239 public String toString() { 240 return "SingleNotificationStats{" + 241 "posttimeElapsedMs=" + posttimeElapsedMs + 242 ", posttimeToFirstClickMs=" + posttimeToFirstClickMs + 243 ", posttimeToDismissMs=" + posttimeToDismissMs + 244 '}'; 245 } 246 } 247 248 /** 249 * Aggregates long samples to sum and averages. 250 */ 251 public static class Aggregate { 252 long numSamples; 253 long sum; 254 long avg; 255 256 public void addSample(long sample) { 257 numSamples++; 258 sum += sample; 259 avg = sum / numSamples; 260 } 261 262 @Override 263 public String toString() { 264 return "Aggregate{" + 265 "numSamples=" + numSamples + 266 ", sum=" + sum + 267 ", avg=" + avg + 268 '}'; 269 } 270 } 271} 272