blob: c8f4d31c3726123bed92a5beb0c4ee26a12ac818 [file] [log] [blame]
Christoph Studer546bec82014-03-14 12:17:12 +01001/*
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
Julia Reynolds85769912016-10-25 09:08:57 -040019import static android.app.NotificationManager.IMPORTANCE_HIGH;
Chris Wrencdee8cd2016-01-25 17:10:30 -050020
Chris Wren93abac42015-06-23 11:23:37 -040021import android.app.Notification;
Christoph Studer1c3f81f2014-04-16 15:05:56 +020022import android.content.ContentValues;
23import android.content.Context;
24import android.database.Cursor;
25import android.database.sqlite.SQLiteDatabase;
26import android.database.sqlite.SQLiteOpenHelper;
27import android.os.Handler;
28import android.os.HandlerThread;
29import android.os.Message;
Christoph Studer546bec82014-03-14 12:17:12 +010030import android.os.SystemClock;
Chris Wren2667e482015-10-30 14:50:22 -040031import android.text.TextUtils;
Chris Wrenc8673a82016-05-17 17:11:29 -040032import android.util.ArraySet;
Christoph Studer1c3f81f2014-04-16 15:05:56 +020033import android.util.Log;
Christoph Studer546bec82014-03-14 12:17:12 +010034
Chris Wren723aa762015-04-09 15:35:23 -040035import com.android.internal.logging.MetricsLogger;
John Spurlock25e2d242014-06-27 13:58:23 -040036import com.android.server.notification.NotificationManagerService.DumpFilter;
37
Chris Wrene4b38802015-07-07 15:54:19 -040038import org.json.JSONArray;
39import org.json.JSONException;
40import org.json.JSONObject;
41
Christoph Studer546bec82014-03-14 12:17:12 +010042import java.io.PrintWriter;
Kouji Shiotaniea50f802017-03-16 15:20:49 +090043import java.lang.Math;
Chris Wren5eab2b72015-06-16 13:56:22 -040044import java.util.ArrayDeque;
Chris Wrene4b38802015-07-07 15:54:19 -040045import java.util.Calendar;
46import java.util.GregorianCalendar;
Christoph Studer546bec82014-03-14 12:17:12 +010047import java.util.HashMap;
48import java.util.Map;
Chris Wren2667e482015-10-30 14:50:22 -040049import java.util.Set;
Christoph Studer546bec82014-03-14 12:17:12 +010050
51/**
52 * Keeps track of notification activity, display, and user interaction.
53 *
54 * <p>This class receives signals from NoMan and keeps running stats of
55 * notification usage. Some metrics are updated as events occur. Others, namely
56 * those involving durations, are updated as the notification is canceled.</p>
57 *
58 * <p>This class is thread-safe.</p>
59 *
60 * {@hide}
61 */
62public class NotificationUsageStats {
Chris Wren5eab2b72015-06-16 13:56:22 -040063 private static final String TAG = "NotificationUsageStats";
Christoph Studer05e28842014-05-27 16:55:57 +020064
Chris Wren5eab2b72015-06-16 13:56:22 -040065 private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = true;
66 private static final boolean ENABLE_SQLITE_LOG = true;
Christoph Studer856b2b82014-08-22 20:45:28 +020067 private static final AggregatedStats[] EMPTY_AGGREGATED_STATS = new AggregatedStats[0];
Chris Wren5eab2b72015-06-16 13:56:22 -040068 private static final String DEVICE_GLOBAL_STATS = "__global"; // packages start with letters
69 private static final int MSG_EMIT = 1;
70
71 private static final boolean DEBUG = false;
72 public static final int TEN_SECONDS = 1000 * 10;
Chris Wren93abac42015-06-23 11:23:37 -040073 public static final int FOUR_HOURS = 1000 * 60 * 60 * 4;
74 private static final long EMIT_PERIOD = DEBUG ? TEN_SECONDS : FOUR_HOURS;
Christoph Studer856b2b82014-08-22 20:45:28 +020075
Christoph Studer546bec82014-03-14 12:17:12 +010076 // Guarded by synchronized(this).
Chris Wren5eab2b72015-06-16 13:56:22 -040077 private final Map<String, AggregatedStats> mStats = new HashMap<>();
78 private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>();
Chris Wrenc8673a82016-05-17 17:11:29 -040079 private ArraySet<String> mStatExpiredkeys = new ArraySet<>();
Christoph Studer1c3f81f2014-04-16 15:05:56 +020080 private final SQLiteLog mSQLiteLog;
Chris Wren723aa762015-04-09 15:35:23 -040081 private final Context mContext;
Chris Wren5eab2b72015-06-16 13:56:22 -040082 private final Handler mHandler;
83 private long mLastEmitTime;
Christoph Studer1c3f81f2014-04-16 15:05:56 +020084
85 public NotificationUsageStats(Context context) {
Chris Wren723aa762015-04-09 15:35:23 -040086 mContext = context;
Chris Wren5eab2b72015-06-16 13:56:22 -040087 mLastEmitTime = SystemClock.elapsedRealtime();
Christoph Studer05e28842014-05-27 16:55:57 +020088 mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null;
Chris Wren5eab2b72015-06-16 13:56:22 -040089 mHandler = new Handler(mContext.getMainLooper()) {
90 @Override
91 public void handleMessage(Message msg) {
92 switch (msg.what) {
93 case MSG_EMIT:
94 emit();
95 break;
96 default:
97 Log.wtf(TAG, "Unknown message type: " + msg.what);
98 break;
99 }
100 }
101 };
102 mHandler.sendEmptyMessageDelayed(MSG_EMIT, EMIT_PERIOD);
Christoph Studer1c3f81f2014-04-16 15:05:56 +0200103 }
Christoph Studer546bec82014-03-14 12:17:12 +0100104
105 /**
106 * Called when a notification has been posted.
107 */
Chris Wrenc8673a82016-05-17 17:11:29 -0400108 public synchronized float getAppEnqueueRate(String packageName) {
109 AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName);
110 if (stats != null) {
111 return stats.getEnqueueRate(SystemClock.elapsedRealtime());
112 } else {
113 return 0f;
114 }
115 }
116
117 /**
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400118 * Called when a notification wants to alert.
119 */
120 public synchronized boolean isAlertRateLimited(String packageName) {
121 AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName);
122 if (stats != null) {
123 return stats.isAlertRateLimited();
124 } else {
125 return false;
126 }
127 }
128
129 /**
Chris Wren888b7a82016-06-17 15:47:19 -0400130 * Called when a notification is tentatively enqueued by an app, before rate checking.
131 */
132 public synchronized void registerEnqueuedByApp(String packageName) {
133 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName);
134 for (AggregatedStats stats : aggregatedStatsArray) {
135 stats.numEnqueuedByApp++;
136 }
137 releaseAggregatedStatsLocked(aggregatedStatsArray);
138 }
139
140 /**
Chris Wrenc8673a82016-05-17 17:11:29 -0400141 * Called when a notification has been posted.
142 */
Christoph Studer546bec82014-03-14 12:17:12 +0100143 public synchronized void registerPostedByApp(NotificationRecord notification) {
Chris Wrenc8673a82016-05-17 17:11:29 -0400144 final long now = SystemClock.elapsedRealtime();
145 notification.stats.posttimeElapsedMs = now;
Chris Wren5eab2b72015-06-16 13:56:22 -0400146
147 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
148 for (AggregatedStats stats : aggregatedStatsArray) {
Christoph Studer546bec82014-03-14 12:17:12 +0100149 stats.numPostedByApp++;
Chris Wrenc8673a82016-05-17 17:11:29 -0400150 stats.updateInterarrivalEstimate(now);
Chris Wren93abac42015-06-23 11:23:37 -0400151 stats.countApiUse(notification);
Christoph Studer546bec82014-03-14 12:17:12 +0100152 }
Chris Wren5eab2b72015-06-16 13:56:22 -0400153 releaseAggregatedStatsLocked(aggregatedStatsArray);
Christoph Studer05e28842014-05-27 16:55:57 +0200154 if (ENABLE_SQLITE_LOG) {
155 mSQLiteLog.logPosted(notification);
156 }
Christoph Studer546bec82014-03-14 12:17:12 +0100157 }
158
159 /**
160 * Called when a notification has been updated.
161 */
Chris Wrene0aaeaa2016-06-21 14:38:04 -0400162 public synchronized void registerUpdatedByApp(NotificationRecord notification,
163 NotificationRecord old) {
Chris Wrencdee8cd2016-01-25 17:10:30 -0500164 notification.stats.updateFrom(old.stats);
Chris Wren5eab2b72015-06-16 13:56:22 -0400165 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
166 for (AggregatedStats stats : aggregatedStatsArray) {
Christoph Studer546bec82014-03-14 12:17:12 +0100167 stats.numUpdatedByApp++;
Chris Wrenc8673a82016-05-17 17:11:29 -0400168 stats.updateInterarrivalEstimate(SystemClock.elapsedRealtime());
Chris Wren93abac42015-06-23 11:23:37 -0400169 stats.countApiUse(notification);
Christoph Studer546bec82014-03-14 12:17:12 +0100170 }
Chris Wren5eab2b72015-06-16 13:56:22 -0400171 releaseAggregatedStatsLocked(aggregatedStatsArray);
Chris Wrencdee8cd2016-01-25 17:10:30 -0500172 if (ENABLE_SQLITE_LOG) {
173 mSQLiteLog.logPosted(notification);
174 }
Christoph Studer546bec82014-03-14 12:17:12 +0100175 }
176
177 /**
178 * Called when the originating app removed the notification programmatically.
179 */
180 public synchronized void registerRemovedByApp(NotificationRecord notification) {
Christoph Studerffeb0c32014-05-07 22:23:56 +0200181 notification.stats.onRemoved();
Chris Wren5eab2b72015-06-16 13:56:22 -0400182 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
183 for (AggregatedStats stats : aggregatedStatsArray) {
Christoph Studer546bec82014-03-14 12:17:12 +0100184 stats.numRemovedByApp++;
Christoph Studer546bec82014-03-14 12:17:12 +0100185 }
Chris Wren5eab2b72015-06-16 13:56:22 -0400186 releaseAggregatedStatsLocked(aggregatedStatsArray);
Christoph Studer05e28842014-05-27 16:55:57 +0200187 if (ENABLE_SQLITE_LOG) {
188 mSQLiteLog.logRemoved(notification);
189 }
Christoph Studer546bec82014-03-14 12:17:12 +0100190 }
191
192 /**
193 * Called when the user dismissed the notification via the UI.
194 */
195 public synchronized void registerDismissedByUser(NotificationRecord notification) {
Chris Wren723aa762015-04-09 15:35:23 -0400196 MetricsLogger.histogram(mContext, "note_dismiss_longevity",
197 (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
Christoph Studer546bec82014-03-14 12:17:12 +0100198 notification.stats.onDismiss();
Christoph Studer05e28842014-05-27 16:55:57 +0200199 if (ENABLE_SQLITE_LOG) {
200 mSQLiteLog.logDismissed(notification);
201 }
Christoph Studer546bec82014-03-14 12:17:12 +0100202 }
203
204 /**
205 * Called when the user clicked the notification in the UI.
206 */
207 public synchronized void registerClickedByUser(NotificationRecord notification) {
Chris Wren723aa762015-04-09 15:35:23 -0400208 MetricsLogger.histogram(mContext, "note_click_longevity",
209 (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
Christoph Studer546bec82014-03-14 12:17:12 +0100210 notification.stats.onClick();
Christoph Studer05e28842014-05-27 16:55:57 +0200211 if (ENABLE_SQLITE_LOG) {
212 mSQLiteLog.logClicked(notification);
213 }
Christoph Studer546bec82014-03-14 12:17:12 +0100214 }
215
Chris Wren5eab2b72015-06-16 13:56:22 -0400216 public synchronized void registerPeopleAffinity(NotificationRecord notification, boolean valid,
217 boolean starred, boolean cached) {
218 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
219 for (AggregatedStats stats : aggregatedStatsArray) {
220 if (valid) {
221 stats.numWithValidPeople++;
222 }
223 if (starred) {
224 stats.numWithStaredPeople++;
225 }
226 if (cached) {
227 stats.numPeopleCacheHit++;
228 } else {
229 stats.numPeopleCacheMiss++;
230 }
Christoph Studer546bec82014-03-14 12:17:12 +0100231 }
Chris Wren5eab2b72015-06-16 13:56:22 -0400232 releaseAggregatedStatsLocked(aggregatedStatsArray);
Christoph Studer546bec82014-03-14 12:17:12 +0100233 }
234
Chris Wren5eab2b72015-06-16 13:56:22 -0400235 public synchronized void registerBlocked(NotificationRecord notification) {
236 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
237 for (AggregatedStats stats : aggregatedStatsArray) {
238 stats.numBlocked++;
Christoph Studer546bec82014-03-14 12:17:12 +0100239 }
Chris Wren5eab2b72015-06-16 13:56:22 -0400240 releaseAggregatedStatsLocked(aggregatedStatsArray);
Christoph Studer546bec82014-03-14 12:17:12 +0100241 }
242
Andrei Stingaceanu0122f6512016-01-22 15:33:03 +0000243 public synchronized void registerSuspendedByAdmin(NotificationRecord notification) {
244 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
245 for (AggregatedStats stats : aggregatedStatsArray) {
246 stats.numSuspendedByAdmin++;
247 }
248 releaseAggregatedStatsLocked(aggregatedStatsArray);
249 }
250
Chris Wrenc8673a82016-05-17 17:11:29 -0400251 public synchronized void registerOverRateQuota(String packageName) {
252 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName);
253 for (AggregatedStats stats : aggregatedStatsArray) {
254 stats.numRateViolations++;
255 }
256 }
257
258 public synchronized void registerOverCountQuota(String packageName) {
259 AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName);
260 for (AggregatedStats stats : aggregatedStatsArray) {
261 stats.numQuotaViolations++;
262 }
263 }
264
Christoph Studer546bec82014-03-14 12:17:12 +0100265 // Locked by this.
266 private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
Chris Wrenc8673a82016-05-17 17:11:29 -0400267 return getAggregatedStatsLocked(record.sbn.getPackageName());
268 }
269
270 // Locked by this.
271 private AggregatedStats[] getAggregatedStatsLocked(String packageName) {
Christoph Studer856b2b82014-08-22 20:45:28 +0200272 if (!ENABLE_AGGREGATED_IN_MEMORY_STATS) {
273 return EMPTY_AGGREGATED_STATS;
274 }
275
Chris Wren5eab2b72015-06-16 13:56:22 -0400276 AggregatedStats[] array = mStatsArrays.poll();
277 if (array == null) {
Chris Wrenc8673a82016-05-17 17:11:29 -0400278 array = new AggregatedStats[2];
Chris Wren5eab2b72015-06-16 13:56:22 -0400279 }
280 array[0] = getOrCreateAggregatedStatsLocked(DEVICE_GLOBAL_STATS);
Chris Wrenc8673a82016-05-17 17:11:29 -0400281 array[1] = getOrCreateAggregatedStatsLocked(packageName);
Chris Wren5eab2b72015-06-16 13:56:22 -0400282 return array;
283 }
Christoph Studer546bec82014-03-14 12:17:12 +0100284
Chris Wren5eab2b72015-06-16 13:56:22 -0400285 // Locked by this.
286 private void releaseAggregatedStatsLocked(AggregatedStats[] array) {
287 for(int i = 0; i < array.length; i++) {
288 array[i] = null;
289 }
290 mStatsArrays.offer(array);
Christoph Studer546bec82014-03-14 12:17:12 +0100291 }
292
293 // Locked by this.
294 private AggregatedStats getOrCreateAggregatedStatsLocked(String key) {
295 AggregatedStats result = mStats.get(key);
296 if (result == null) {
Chris Wren5eab2b72015-06-16 13:56:22 -0400297 result = new AggregatedStats(mContext, key);
Christoph Studer546bec82014-03-14 12:17:12 +0100298 mStats.put(key, result);
299 }
Chris Wrenc8673a82016-05-17 17:11:29 -0400300 result.mLastAccessTime = SystemClock.elapsedRealtime();
Christoph Studer546bec82014-03-14 12:17:12 +0100301 return result;
302 }
303
Chris Wrene4b38802015-07-07 15:54:19 -0400304 public synchronized JSONObject dumpJson(DumpFilter filter) {
305 JSONObject dump = new JSONObject();
306 if (ENABLE_AGGREGATED_IN_MEMORY_STATS) {
307 try {
308 JSONArray aggregatedStats = new JSONArray();
309 for (AggregatedStats as : mStats.values()) {
310 if (filter != null && !filter.matches(as.key))
311 continue;
312 aggregatedStats.put(as.dumpJson());
313 }
314 dump.put("current", aggregatedStats);
315 } catch (JSONException e) {
316 // pass
317 }
318 }
319 if (ENABLE_SQLITE_LOG) {
320 try {
321 dump.put("historical", mSQLiteLog.dumpJson(filter));
322 } catch (JSONException e) {
323 // pass
324 }
325 }
326 return dump;
327 }
328
John Spurlock25e2d242014-06-27 13:58:23 -0400329 public synchronized void dump(PrintWriter pw, String indent, DumpFilter filter) {
Christoph Studer856b2b82014-08-22 20:45:28 +0200330 if (ENABLE_AGGREGATED_IN_MEMORY_STATS) {
331 for (AggregatedStats as : mStats.values()) {
332 if (filter != null && !filter.matches(as.key))
333 continue;
334 as.dump(pw, indent);
335 }
Chris Wren5eab2b72015-06-16 13:56:22 -0400336 pw.println(indent + "mStatsArrays.size(): " + mStatsArrays.size());
Chris Wrenc8673a82016-05-17 17:11:29 -0400337 pw.println(indent + "mStats.size(): " + mStats.size());
Christoph Studer546bec82014-03-14 12:17:12 +0100338 }
Christoph Studer05e28842014-05-27 16:55:57 +0200339 if (ENABLE_SQLITE_LOG) {
John Spurlock25e2d242014-06-27 13:58:23 -0400340 mSQLiteLog.dump(pw, indent, filter);
Christoph Studer05e28842014-05-27 16:55:57 +0200341 }
Christoph Studer546bec82014-03-14 12:17:12 +0100342 }
343
Chris Wren5eab2b72015-06-16 13:56:22 -0400344 public synchronized void emit() {
Chris Wren5eab2b72015-06-16 13:56:22 -0400345 AggregatedStats stats = getOrCreateAggregatedStatsLocked(DEVICE_GLOBAL_STATS);
346 stats.emit();
Chris Wren5eab2b72015-06-16 13:56:22 -0400347 mHandler.removeMessages(MSG_EMIT);
348 mHandler.sendEmptyMessageDelayed(MSG_EMIT, EMIT_PERIOD);
Chris Wrenc8673a82016-05-17 17:11:29 -0400349 for(String key: mStats.keySet()) {
350 if (mStats.get(key).mLastAccessTime < mLastEmitTime) {
351 mStatExpiredkeys.add(key);
352 }
353 }
354 for(String key: mStatExpiredkeys) {
355 mStats.remove(key);
356 }
357 mStatExpiredkeys.clear();
358 mLastEmitTime = SystemClock.elapsedRealtime();
Chris Wren5eab2b72015-06-16 13:56:22 -0400359 }
360
Christoph Studer546bec82014-03-14 12:17:12 +0100361 /**
362 * Aggregated notification stats.
363 */
364 private static class AggregatedStats {
Chris Wren5eab2b72015-06-16 13:56:22 -0400365
366 private final Context mContext;
Christoph Studer546bec82014-03-14 12:17:12 +0100367 public final String key;
Chris Wrene4b38802015-07-07 15:54:19 -0400368 private final long mCreated;
Chris Wren93abac42015-06-23 11:23:37 -0400369 private AggregatedStats mPrevious;
Christoph Studer546bec82014-03-14 12:17:12 +0100370
371 // ---- Updated as the respective events occur.
Chris Wren888b7a82016-06-17 15:47:19 -0400372 public int numEnqueuedByApp;
Christoph Studer546bec82014-03-14 12:17:12 +0100373 public int numPostedByApp;
374 public int numUpdatedByApp;
375 public int numRemovedByApp;
Chris Wren5eab2b72015-06-16 13:56:22 -0400376 public int numPeopleCacheHit;
377 public int numPeopleCacheMiss;;
378 public int numWithStaredPeople;
379 public int numWithValidPeople;
380 public int numBlocked;
Andrei Stingaceanu0122f6512016-01-22 15:33:03 +0000381 public int numSuspendedByAdmin;
Chris Wren93abac42015-06-23 11:23:37 -0400382 public int numWithActions;
383 public int numPrivate;
384 public int numSecret;
Chris Wren93abac42015-06-23 11:23:37 -0400385 public int numWithBigText;
386 public int numWithBigPicture;
387 public int numForegroundService;
388 public int numOngoing;
389 public int numAutoCancel;
390 public int numWithLargeIcon;
391 public int numWithInbox;
392 public int numWithMediaSession;
393 public int numWithTitle;
394 public int numWithText;
395 public int numWithSubText;
396 public int numWithInfoText;
397 public int numInterrupt;
Chris Wrencdee8cd2016-01-25 17:10:30 -0500398 public ImportanceHistogram noisyImportance;
399 public ImportanceHistogram quietImportance;
400 public ImportanceHistogram finalImportance;
Chris Wrenc8673a82016-05-17 17:11:29 -0400401 public RateEstimator enqueueRate;
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400402 public AlertRateLimiter alertRate;
Chris Wrenc8673a82016-05-17 17:11:29 -0400403 public int numRateViolations;
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400404 public int numAlertViolations;
Chris Wrenc8673a82016-05-17 17:11:29 -0400405 public int numQuotaViolations;
406 public long mLastAccessTime;
Christoph Studer546bec82014-03-14 12:17:12 +0100407
Chris Wren5eab2b72015-06-16 13:56:22 -0400408 public AggregatedStats(Context context, String key) {
Christoph Studer546bec82014-03-14 12:17:12 +0100409 this.key = key;
Chris Wren5eab2b72015-06-16 13:56:22 -0400410 mContext = context;
Chris Wrene4b38802015-07-07 15:54:19 -0400411 mCreated = SystemClock.elapsedRealtime();
Chris Wrencdee8cd2016-01-25 17:10:30 -0500412 noisyImportance = new ImportanceHistogram(context, "note_imp_noisy_");
413 quietImportance = new ImportanceHistogram(context, "note_imp_quiet_");
414 finalImportance = new ImportanceHistogram(context, "note_importance_");
Tony Makfd303322016-05-24 18:57:50 +0100415 enqueueRate = new RateEstimator();
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400416 alertRate = new AlertRateLimiter();
Christoph Studer546bec82014-03-14 12:17:12 +0100417 }
418
Chris Wren7c4c87b2016-03-15 12:47:26 -0400419 public AggregatedStats getPrevious() {
420 if (mPrevious == null) {
421 mPrevious = new AggregatedStats(mContext, key);
422 }
423 return mPrevious;
424 }
425
Chris Wren93abac42015-06-23 11:23:37 -0400426 public void countApiUse(NotificationRecord record) {
427 final Notification n = record.getNotification();
428 if (n.actions != null) {
429 numWithActions++;
430 }
431
432 if ((n.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
433 numForegroundService++;
434 }
435
436 if ((n.flags & Notification.FLAG_ONGOING_EVENT) != 0) {
437 numOngoing++;
438 }
439
440 if ((n.flags & Notification.FLAG_AUTO_CANCEL) != 0) {
441 numAutoCancel++;
442 }
443
444 if ((n.defaults & Notification.DEFAULT_SOUND) != 0 ||
445 (n.defaults & Notification.DEFAULT_VIBRATE) != 0 ||
446 n.sound != null || n.vibrate != null) {
447 numInterrupt++;
448 }
449
450 switch (n.visibility) {
451 case Notification.VISIBILITY_PRIVATE:
452 numPrivate++;
453 break;
454 case Notification.VISIBILITY_SECRET:
455 numSecret++;
456 break;
457 }
458
Chris Wrencdee8cd2016-01-25 17:10:30 -0500459 if (record.stats.isNoisy) {
460 noisyImportance.increment(record.stats.requestedImportance);
461 } else {
462 quietImportance.increment(record.stats.requestedImportance);
Chris Wren93abac42015-06-23 11:23:37 -0400463 }
Chris Wrencdee8cd2016-01-25 17:10:30 -0500464 finalImportance.increment(record.getImportance());
Chris Wren93abac42015-06-23 11:23:37 -0400465
Chris Wren2667e482015-10-30 14:50:22 -0400466 final Set<String> names = n.extras.keySet();
467 if (names.contains(Notification.EXTRA_BIG_TEXT)) {
468 numWithBigText++;
469 }
470 if (names.contains(Notification.EXTRA_PICTURE)) {
471 numWithBigPicture++;
472 }
473 if (names.contains(Notification.EXTRA_LARGE_ICON)) {
474 numWithLargeIcon++;
475 }
476 if (names.contains(Notification.EXTRA_TEXT_LINES)) {
477 numWithInbox++;
478 }
479 if (names.contains(Notification.EXTRA_MEDIA_SESSION)) {
480 numWithMediaSession++;
481 }
482 if (names.contains(Notification.EXTRA_TITLE) &&
483 !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_TITLE))) {
484 numWithTitle++;
485 }
486 if (names.contains(Notification.EXTRA_TEXT) &&
487 !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_TEXT))) {
488 numWithText++;
489 }
490 if (names.contains(Notification.EXTRA_SUB_TEXT) &&
491 !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_SUB_TEXT))) {
492 numWithSubText++;
493 }
494 if (names.contains(Notification.EXTRA_INFO_TEXT) &&
495 !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_INFO_TEXT))) {
496 numWithInfoText++;
Chris Wren93abac42015-06-23 11:23:37 -0400497 }
498 }
499
Chris Wren5eab2b72015-06-16 13:56:22 -0400500 public void emit() {
Chris Wren7c4c87b2016-03-15 12:47:26 -0400501 AggregatedStats previous = getPrevious();
Chris Wren888b7a82016-06-17 15:47:19 -0400502 maybeCount("note_enqueued", (numEnqueuedByApp - previous.numEnqueuedByApp));
Chris Wren7c4c87b2016-03-15 12:47:26 -0400503 maybeCount("note_post", (numPostedByApp - previous.numPostedByApp));
504 maybeCount("note_update", (numUpdatedByApp - previous.numUpdatedByApp));
505 maybeCount("note_remove", (numRemovedByApp - previous.numRemovedByApp));
506 maybeCount("note_with_people", (numWithValidPeople - previous.numWithValidPeople));
507 maybeCount("note_with_stars", (numWithStaredPeople - previous.numWithStaredPeople));
508 maybeCount("people_cache_hit", (numPeopleCacheHit - previous.numPeopleCacheHit));
509 maybeCount("people_cache_miss", (numPeopleCacheMiss - previous.numPeopleCacheMiss));
510 maybeCount("note_blocked", (numBlocked - previous.numBlocked));
511 maybeCount("note_suspended", (numSuspendedByAdmin - previous.numSuspendedByAdmin));
512 maybeCount("note_with_actions", (numWithActions - previous.numWithActions));
513 maybeCount("note_private", (numPrivate - previous.numPrivate));
514 maybeCount("note_secret", (numSecret - previous.numSecret));
515 maybeCount("note_interupt", (numInterrupt - previous.numInterrupt));
516 maybeCount("note_big_text", (numWithBigText - previous.numWithBigText));
517 maybeCount("note_big_pic", (numWithBigPicture - previous.numWithBigPicture));
518 maybeCount("note_fg", (numForegroundService - previous.numForegroundService));
519 maybeCount("note_ongoing", (numOngoing - previous.numOngoing));
520 maybeCount("note_auto", (numAutoCancel - previous.numAutoCancel));
521 maybeCount("note_large_icon", (numWithLargeIcon - previous.numWithLargeIcon));
522 maybeCount("note_inbox", (numWithInbox - previous.numWithInbox));
523 maybeCount("note_media", (numWithMediaSession - previous.numWithMediaSession));
524 maybeCount("note_title", (numWithTitle - previous.numWithTitle));
525 maybeCount("note_text", (numWithText - previous.numWithText));
526 maybeCount("note_sub_text", (numWithSubText - previous.numWithSubText));
527 maybeCount("note_info_text", (numWithInfoText - previous.numWithInfoText));
Chris Wrenc8673a82016-05-17 17:11:29 -0400528 maybeCount("note_over_rate", (numRateViolations - previous.numRateViolations));
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400529 maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations));
Chris Wrenc8673a82016-05-17 17:11:29 -0400530 maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations));
Chris Wren7c4c87b2016-03-15 12:47:26 -0400531 noisyImportance.maybeCount(previous.noisyImportance);
532 quietImportance.maybeCount(previous.quietImportance);
533 finalImportance.maybeCount(previous.finalImportance);
Chris Wren5eab2b72015-06-16 13:56:22 -0400534
Chris Wren888b7a82016-06-17 15:47:19 -0400535 previous.numEnqueuedByApp = numEnqueuedByApp;
Chris Wren7c4c87b2016-03-15 12:47:26 -0400536 previous.numPostedByApp = numPostedByApp;
537 previous.numUpdatedByApp = numUpdatedByApp;
538 previous.numRemovedByApp = numRemovedByApp;
539 previous.numPeopleCacheHit = numPeopleCacheHit;
540 previous.numPeopleCacheMiss = numPeopleCacheMiss;
541 previous.numWithStaredPeople = numWithStaredPeople;
542 previous.numWithValidPeople = numWithValidPeople;
543 previous.numBlocked = numBlocked;
544 previous.numSuspendedByAdmin = numSuspendedByAdmin;
545 previous.numWithActions = numWithActions;
546 previous.numPrivate = numPrivate;
547 previous.numSecret = numSecret;
548 previous.numInterrupt = numInterrupt;
549 previous.numWithBigText = numWithBigText;
550 previous.numWithBigPicture = numWithBigPicture;
551 previous.numForegroundService = numForegroundService;
552 previous.numOngoing = numOngoing;
553 previous.numAutoCancel = numAutoCancel;
554 previous.numWithLargeIcon = numWithLargeIcon;
555 previous.numWithInbox = numWithInbox;
556 previous.numWithMediaSession = numWithMediaSession;
557 previous.numWithTitle = numWithTitle;
558 previous.numWithText = numWithText;
559 previous.numWithSubText = numWithSubText;
560 previous.numWithInfoText = numWithInfoText;
Chris Wrenc8673a82016-05-17 17:11:29 -0400561 previous.numRateViolations = numRateViolations;
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400562 previous.numAlertViolations = numAlertViolations;
Chris Wrenc8673a82016-05-17 17:11:29 -0400563 previous.numQuotaViolations = numQuotaViolations;
Chris Wren7c4c87b2016-03-15 12:47:26 -0400564 noisyImportance.update(previous.noisyImportance);
565 quietImportance.update(previous.quietImportance);
566 finalImportance.update(previous.finalImportance);
Chris Wren5eab2b72015-06-16 13:56:22 -0400567 }
568
569 void maybeCount(String name, int value) {
570 if (value > 0) {
571 MetricsLogger.count(mContext, name, value);
Chris Wren78403d72014-07-28 10:23:24 +0100572 }
Christoph Studer546bec82014-03-14 12:17:12 +0100573 }
574
575 public void dump(PrintWriter pw, String indent) {
576 pw.println(toStringWithIndent(indent));
577 }
578
579 @Override
580 public String toString() {
581 return toStringWithIndent("");
582 }
583
Chris Wrenc8673a82016-05-17 17:11:29 -0400584 /** @return the enqueue rate if there were a new enqueue event right now. */
585 public float getEnqueueRate() {
586 return getEnqueueRate(SystemClock.elapsedRealtime());
587 }
588
589 public float getEnqueueRate(long now) {
590 return enqueueRate.getRate(now);
591 }
592
593 public void updateInterarrivalEstimate(long now) {
594 enqueueRate.update(now);
595 }
596
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400597 public boolean isAlertRateLimited() {
598 boolean limited = alertRate.shouldRateLimitAlert(SystemClock.elapsedRealtime());
599 if (limited) {
600 numAlertViolations++;
601 }
602 return limited;
603 }
604
Christoph Studer546bec82014-03-14 12:17:12 +0100605 private String toStringWithIndent(String indent) {
Chris Wrencdee8cd2016-01-25 17:10:30 -0500606 StringBuilder output = new StringBuilder();
607 output.append(indent).append("AggregatedStats{\n");
608 String indentPlusTwo = indent + " ";
609 output.append(indentPlusTwo);
610 output.append("key='").append(key).append("',\n");
611 output.append(indentPlusTwo);
Chris Wren888b7a82016-06-17 15:47:19 -0400612 output.append("numEnqueuedByApp=").append(numEnqueuedByApp).append(",\n");
613 output.append(indentPlusTwo);
Chris Wrencdee8cd2016-01-25 17:10:30 -0500614 output.append("numPostedByApp=").append(numPostedByApp).append(",\n");
615 output.append(indentPlusTwo);
616 output.append("numUpdatedByApp=").append(numUpdatedByApp).append(",\n");
617 output.append(indentPlusTwo);
618 output.append("numRemovedByApp=").append(numRemovedByApp).append(",\n");
619 output.append(indentPlusTwo);
620 output.append("numPeopleCacheHit=").append(numPeopleCacheHit).append(",\n");
621 output.append(indentPlusTwo);
622 output.append("numWithStaredPeople=").append(numWithStaredPeople).append(",\n");
623 output.append(indentPlusTwo);
624 output.append("numWithValidPeople=").append(numWithValidPeople).append(",\n");
625 output.append(indentPlusTwo);
626 output.append("numPeopleCacheMiss=").append(numPeopleCacheMiss).append(",\n");
627 output.append(indentPlusTwo);
628 output.append("numBlocked=").append(numBlocked).append(",\n");
629 output.append(indentPlusTwo);
Andrei Stingaceanu0122f6512016-01-22 15:33:03 +0000630 output.append("numSuspendedByAdmin=").append(numSuspendedByAdmin).append(",\n");
631 output.append(indentPlusTwo);
Chris Wrencdee8cd2016-01-25 17:10:30 -0500632 output.append("numWithActions=").append(numWithActions).append(",\n");
633 output.append(indentPlusTwo);
634 output.append("numPrivate=").append(numPrivate).append(",\n");
635 output.append(indentPlusTwo);
636 output.append("numSecret=").append(numSecret).append(",\n");
637 output.append(indentPlusTwo);
638 output.append("numInterrupt=").append(numInterrupt).append(",\n");
639 output.append(indentPlusTwo);
640 output.append("numWithBigText=").append(numWithBigText).append(",\n");
641 output.append(indentPlusTwo);
642 output.append("numWithBigPicture=").append(numWithBigPicture).append("\n");
643 output.append(indentPlusTwo);
644 output.append("numForegroundService=").append(numForegroundService).append("\n");
645 output.append(indentPlusTwo);
646 output.append("numOngoing=").append(numOngoing).append("\n");
647 output.append(indentPlusTwo);
648 output.append("numAutoCancel=").append(numAutoCancel).append("\n");
649 output.append(indentPlusTwo);
650 output.append("numWithLargeIcon=").append(numWithLargeIcon).append("\n");
651 output.append(indentPlusTwo);
652 output.append("numWithInbox=").append(numWithInbox).append("\n");
653 output.append(indentPlusTwo);
654 output.append("numWithMediaSession=").append(numWithMediaSession).append("\n");
655 output.append(indentPlusTwo);
656 output.append("numWithTitle=").append(numWithTitle).append("\n");
657 output.append(indentPlusTwo);
658 output.append("numWithText=").append(numWithText).append("\n");
659 output.append(indentPlusTwo);
660 output.append("numWithSubText=").append(numWithSubText).append("\n");
661 output.append(indentPlusTwo);
662 output.append("numWithInfoText=").append(numWithInfoText).append("\n");
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400663 output.append(indentPlusTwo);
Chris Wrenc8673a82016-05-17 17:11:29 -0400664 output.append("numRateViolations=").append(numRateViolations).append("\n");
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400665 output.append(indentPlusTwo);
666 output.append("numAlertViolations=").append(numAlertViolations).append("\n");
667 output.append(indentPlusTwo);
Chris Wrenc8673a82016-05-17 17:11:29 -0400668 output.append("numQuotaViolations=").append(numQuotaViolations).append("\n");
Chris Wrencdee8cd2016-01-25 17:10:30 -0500669 output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n");
670 output.append(indentPlusTwo).append(quietImportance.toString()).append("\n");
671 output.append(indentPlusTwo).append(finalImportance.toString()).append("\n");
672 output.append(indent).append("}");
673 return output.toString();
Christoph Studer546bec82014-03-14 12:17:12 +0100674 }
Chris Wrene4b38802015-07-07 15:54:19 -0400675
676 public JSONObject dumpJson() throws JSONException {
Chris Wren7c4c87b2016-03-15 12:47:26 -0400677 AggregatedStats previous = getPrevious();
Chris Wrene4b38802015-07-07 15:54:19 -0400678 JSONObject dump = new JSONObject();
679 dump.put("key", key);
680 dump.put("duration", SystemClock.elapsedRealtime() - mCreated);
Chris Wren888b7a82016-06-17 15:47:19 -0400681 maybePut(dump, "numEnqueuedByApp", numEnqueuedByApp);
Chris Wrene4b38802015-07-07 15:54:19 -0400682 maybePut(dump, "numPostedByApp", numPostedByApp);
683 maybePut(dump, "numUpdatedByApp", numUpdatedByApp);
684 maybePut(dump, "numRemovedByApp", numRemovedByApp);
685 maybePut(dump, "numPeopleCacheHit", numPeopleCacheHit);
686 maybePut(dump, "numPeopleCacheMiss", numPeopleCacheMiss);
687 maybePut(dump, "numWithStaredPeople", numWithStaredPeople);
688 maybePut(dump, "numWithValidPeople", numWithValidPeople);
689 maybePut(dump, "numBlocked", numBlocked);
Andrei Stingaceanu0122f6512016-01-22 15:33:03 +0000690 maybePut(dump, "numSuspendedByAdmin", numSuspendedByAdmin);
Chris Wrene4b38802015-07-07 15:54:19 -0400691 maybePut(dump, "numWithActions", numWithActions);
692 maybePut(dump, "numPrivate", numPrivate);
693 maybePut(dump, "numSecret", numSecret);
Chris Wrene4b38802015-07-07 15:54:19 -0400694 maybePut(dump, "numInterrupt", numInterrupt);
695 maybePut(dump, "numWithBigText", numWithBigText);
696 maybePut(dump, "numWithBigPicture", numWithBigPicture);
697 maybePut(dump, "numForegroundService", numForegroundService);
698 maybePut(dump, "numOngoing", numOngoing);
699 maybePut(dump, "numAutoCancel", numAutoCancel);
700 maybePut(dump, "numWithLargeIcon", numWithLargeIcon);
701 maybePut(dump, "numWithInbox", numWithInbox);
702 maybePut(dump, "numWithMediaSession", numWithMediaSession);
703 maybePut(dump, "numWithTitle", numWithTitle);
704 maybePut(dump, "numWithText", numWithText);
705 maybePut(dump, "numWithSubText", numWithSubText);
706 maybePut(dump, "numWithInfoText", numWithInfoText);
Chris Wrenc8673a82016-05-17 17:11:29 -0400707 maybePut(dump, "numRateViolations", numRateViolations);
708 maybePut(dump, "numQuotaLViolations", numQuotaViolations);
709 maybePut(dump, "notificationEnqueueRate", getEnqueueRate());
Julia Reynolds5f8e0b882017-06-19 08:16:04 -0400710 maybePut(dump, "numAlertViolations", numAlertViolations);
Chris Wren7c4c87b2016-03-15 12:47:26 -0400711 noisyImportance.maybePut(dump, previous.noisyImportance);
712 quietImportance.maybePut(dump, previous.quietImportance);
713 finalImportance.maybePut(dump, previous.finalImportance);
Chris Wrencdee8cd2016-01-25 17:10:30 -0500714
Chris Wrene4b38802015-07-07 15:54:19 -0400715 return dump;
716 }
717
718 private void maybePut(JSONObject dump, String name, int value) throws JSONException {
719 if (value > 0) {
720 dump.put(name, value);
721 }
722 }
Chris Wrenc8673a82016-05-17 17:11:29 -0400723
724 private void maybePut(JSONObject dump, String name, float value) throws JSONException {
725 if (value > 0.0) {
726 dump.put(name, value);
727 }
728 }
Christoph Studer546bec82014-03-14 12:17:12 +0100729 }
730
Chris Wrencdee8cd2016-01-25 17:10:30 -0500731 private static class ImportanceHistogram {
732 // TODO define these somewhere else
Julia Reynoldsf0f629f2016-02-25 09:34:04 -0500733 private static final int NUM_IMPORTANCES = 6;
734 private static final String[] IMPORTANCE_NAMES =
735 {"none", "min", "low", "default", "high", "max"};
Chris Wrencdee8cd2016-01-25 17:10:30 -0500736 private final Context mContext;
737 private final String[] mCounterNames;
738 private final String mPrefix;
739 private int[] mCount;
740
741 ImportanceHistogram(Context context, String prefix) {
742 mContext = context;
743 mCount = new int[NUM_IMPORTANCES];
744 mCounterNames = new String[NUM_IMPORTANCES];
745 mPrefix = prefix;
746 for (int i = 0; i < NUM_IMPORTANCES; i++) {
747 mCounterNames[i] = mPrefix + IMPORTANCE_NAMES[i];
748 }
749 }
750
751 void increment(int imp) {
Kouji Shiotaniea50f802017-03-16 15:20:49 +0900752 imp = Math.max(0, Math.min(imp, mCount.length - 1));
753 mCount[imp]++;
Chris Wrencdee8cd2016-01-25 17:10:30 -0500754 }
755
756 void maybeCount(ImportanceHistogram prev) {
757 for (int i = 0; i < NUM_IMPORTANCES; i++) {
758 final int value = mCount[i] - prev.mCount[i];
759 if (value > 0) {
760 MetricsLogger.count(mContext, mCounterNames[i], value);
761 }
762 }
763 }
764
765 void update(ImportanceHistogram that) {
766 for (int i = 0; i < NUM_IMPORTANCES; i++) {
767 mCount[i] = that.mCount[i];
768 }
769 }
770
771 public void maybePut(JSONObject dump, ImportanceHistogram prev)
772 throws JSONException {
773 dump.put(mPrefix, new JSONArray(mCount));
774 }
775
776 @Override
777 public String toString() {
778 StringBuilder output = new StringBuilder();
779 output.append(mPrefix).append(": [");
780 for (int i = 0; i < NUM_IMPORTANCES; i++) {
781 output.append(mCount[i]);
782 if (i < (NUM_IMPORTANCES-1)) {
783 output.append(", ");
784 }
785 }
786 output.append("]");
787 return output.toString();
788 }
789 }
790
Christoph Studer546bec82014-03-14 12:17:12 +0100791 /**
792 * Tracks usage of an individual notification that is currently active.
793 */
794 public static class SingleNotificationStats {
Chris Wren78403d72014-07-28 10:23:24 +0100795 private boolean isVisible = false;
796 private boolean isExpanded = false;
Christoph Studer546bec82014-03-14 12:17:12 +0100797 /** SystemClock.elapsedRealtime() when the notification was posted. */
798 public long posttimeElapsedMs = -1;
799 /** Elapsed time since the notification was posted until it was first clicked, or -1. */
800 public long posttimeToFirstClickMs = -1;
801 /** Elpased time since the notification was posted until it was dismissed by the user. */
802 public long posttimeToDismissMs = -1;
Christoph Studerffeb0c32014-05-07 22:23:56 +0200803 /** Number of times the notification has been made visible. */
804 public long airtimeCount = 0;
805 /** Time in ms between the notification was posted and first shown; -1 if never shown. */
806 public long posttimeToFirstAirtimeMs = -1;
807 /**
808 * If currently visible, SystemClock.elapsedRealtime() when the notification was made
809 * visible; -1 otherwise.
810 */
811 public long currentAirtimeStartElapsedMs = -1;
812 /** Accumulated visible time. */
813 public long airtimeMs = 0;
Chris Wren78403d72014-07-28 10:23:24 +0100814 /**
815 * Time in ms between the notification being posted and when it first
816 * became visible and expanded; -1 if it was never visibly expanded.
817 */
818 public long posttimeToFirstVisibleExpansionMs = -1;
819 /**
820 * If currently visible, SystemClock.elapsedRealtime() when the notification was made
821 * visible; -1 otherwise.
822 */
823 public long currentAirtimeExpandedStartElapsedMs = -1;
824 /** Accumulated visible expanded time. */
825 public long airtimeExpandedMs = 0;
826 /** Number of times the notification has been expanded by the user. */
827 public long userExpansionCount = 0;
Chris Wrencdee8cd2016-01-25 17:10:30 -0500828 /** Importance directly requested by the app. */
829 public int requestedImportance;
830 /** Did the app include sound or vibration on the notificaiton. */
831 public boolean isNoisy;
832 /** Importance after initial filtering for noise and other features */
833 public int naturalImportance;
Christoph Studerffeb0c32014-05-07 22:23:56 +0200834
835 public long getCurrentPosttimeMs() {
836 if (posttimeElapsedMs < 0) {
837 return 0;
838 }
839 return SystemClock.elapsedRealtime() - posttimeElapsedMs;
840 }
841
842 public long getCurrentAirtimeMs() {
843 long result = airtimeMs;
844 // Add incomplete airtime if currently shown.
845 if (currentAirtimeStartElapsedMs >= 0) {
Chris Wren78403d72014-07-28 10:23:24 +0100846 result += (SystemClock.elapsedRealtime() - currentAirtimeStartElapsedMs);
847 }
848 return result;
849 }
850
851 public long getCurrentAirtimeExpandedMs() {
852 long result = airtimeExpandedMs;
853 // Add incomplete expanded airtime if currently shown.
854 if (currentAirtimeExpandedStartElapsedMs >= 0) {
855 result += (SystemClock.elapsedRealtime() - currentAirtimeExpandedStartElapsedMs);
Christoph Studerffeb0c32014-05-07 22:23:56 +0200856 }
857 return result;
858 }
Christoph Studer546bec82014-03-14 12:17:12 +0100859
860 /**
861 * Called when the user clicked the notification.
862 */
863 public void onClick() {
864 if (posttimeToFirstClickMs < 0) {
865 posttimeToFirstClickMs = SystemClock.elapsedRealtime() - posttimeElapsedMs;
866 }
867 }
868
869 /**
870 * Called when the user removed the notification.
871 */
872 public void onDismiss() {
873 if (posttimeToDismissMs < 0) {
874 posttimeToDismissMs = SystemClock.elapsedRealtime() - posttimeElapsedMs;
875 }
Christoph Studerffeb0c32014-05-07 22:23:56 +0200876 finish();
877 }
878
879 public void onCancel() {
880 finish();
881 }
882
883 public void onRemoved() {
884 finish();
885 }
886
887 public void onVisibilityChanged(boolean visible) {
888 long elapsedNowMs = SystemClock.elapsedRealtime();
Chris Wren78403d72014-07-28 10:23:24 +0100889 final boolean wasVisible = isVisible;
890 isVisible = visible;
Christoph Studerffeb0c32014-05-07 22:23:56 +0200891 if (visible) {
892 if (currentAirtimeStartElapsedMs < 0) {
893 airtimeCount++;
894 currentAirtimeStartElapsedMs = elapsedNowMs;
895 }
896 if (posttimeToFirstAirtimeMs < 0) {
897 posttimeToFirstAirtimeMs = elapsedNowMs - posttimeElapsedMs;
898 }
899 } else {
900 if (currentAirtimeStartElapsedMs >= 0) {
901 airtimeMs += (elapsedNowMs - currentAirtimeStartElapsedMs);
902 currentAirtimeStartElapsedMs = -1;
903 }
904 }
Chris Wren78403d72014-07-28 10:23:24 +0100905
906 if (wasVisible != isVisible) {
907 updateVisiblyExpandedStats();
908 }
909 }
910
911 public void onExpansionChanged(boolean userAction, boolean expanded) {
912 isExpanded = expanded;
913 if (isExpanded && userAction) {
914 userExpansionCount++;
915 }
916 updateVisiblyExpandedStats();
917 }
918
919 private void updateVisiblyExpandedStats() {
920 long elapsedNowMs = SystemClock.elapsedRealtime();
921 if (isExpanded && isVisible) {
922 // expanded and visible
923 if (currentAirtimeExpandedStartElapsedMs < 0) {
924 currentAirtimeExpandedStartElapsedMs = elapsedNowMs;
925 }
926 if (posttimeToFirstVisibleExpansionMs < 0) {
927 posttimeToFirstVisibleExpansionMs = elapsedNowMs - posttimeElapsedMs;
928 }
929 } else {
930 // not-expanded or not-visible
931 if (currentAirtimeExpandedStartElapsedMs >= 0) {
932 airtimeExpandedMs += (elapsedNowMs - currentAirtimeExpandedStartElapsedMs);
933 currentAirtimeExpandedStartElapsedMs = -1;
934 }
935 }
Christoph Studerffeb0c32014-05-07 22:23:56 +0200936 }
937
938 /** The notification is leaving the system. Finalize. */
939 public void finish() {
940 onVisibilityChanged(false);
Christoph Studer546bec82014-03-14 12:17:12 +0100941 }
942
943 @Override
944 public String toString() {
Chris Wrencdee8cd2016-01-25 17:10:30 -0500945 StringBuilder output = new StringBuilder();
946 output.append("SingleNotificationStats{");
947
948 output.append("posttimeElapsedMs=").append(posttimeElapsedMs).append(", ");
949 output.append("posttimeToFirstClickMs=").append(posttimeToFirstClickMs).append(", ");
950 output.append("posttimeToDismissMs=").append(posttimeToDismissMs).append(", ");
951 output.append("airtimeCount=").append(airtimeCount).append(", ");
952 output.append("airtimeMs=").append(airtimeMs).append(", ");
953 output.append("currentAirtimeStartElapsedMs=").append(currentAirtimeStartElapsedMs)
954 .append(", ");
955 output.append("airtimeExpandedMs=").append(airtimeExpandedMs).append(", ");
956 output.append("posttimeToFirstVisibleExpansionMs=")
957 .append(posttimeToFirstVisibleExpansionMs).append(", ");
958 output.append("currentAirtimeExpandedStartElapsedMs=")
959 .append(currentAirtimeExpandedStartElapsedMs).append(", ");
960 output.append("requestedImportance=").append(requestedImportance).append(", ");
961 output.append("naturalImportance=").append(naturalImportance).append(", ");
962 output.append("isNoisy=").append(isNoisy);
963 output.append('}');
964 return output.toString();
965 }
966
967 /** Copy useful information out of the stats from the pre-update notifications. */
968 public void updateFrom(SingleNotificationStats old) {
969 posttimeElapsedMs = old.posttimeElapsedMs;
970 posttimeToFirstClickMs = old.posttimeToFirstClickMs;
971 airtimeCount = old.airtimeCount;
972 posttimeToFirstAirtimeMs = old.posttimeToFirstAirtimeMs;
973 currentAirtimeStartElapsedMs = old.currentAirtimeStartElapsedMs;
974 airtimeMs = old.airtimeMs;
975 posttimeToFirstVisibleExpansionMs = old.posttimeToFirstVisibleExpansionMs;
976 currentAirtimeExpandedStartElapsedMs = old.currentAirtimeExpandedStartElapsedMs;
977 airtimeExpandedMs = old.airtimeExpandedMs;
978 userExpansionCount = old.userExpansionCount;
Christoph Studer546bec82014-03-14 12:17:12 +0100979 }
980 }
981
982 /**
983 * Aggregates long samples to sum and averages.
984 */
985 public static class Aggregate {
986 long numSamples;
Chris Wren51103972014-04-01 13:57:34 -0400987 double avg;
988 double sum2;
989 double var;
Christoph Studer546bec82014-03-14 12:17:12 +0100990
991 public void addSample(long sample) {
Chris Wren51103972014-04-01 13:57:34 -0400992 // Welford's "Method for Calculating Corrected Sums of Squares"
993 // http://www.jstor.org/stable/1266577?seq=2
Christoph Studer546bec82014-03-14 12:17:12 +0100994 numSamples++;
Chris Wren51103972014-04-01 13:57:34 -0400995 final double n = numSamples;
996 final double delta = sample - avg;
997 avg += (1.0 / n) * delta;
998 sum2 += ((n - 1) / n) * delta * delta;
999 final double divisor = numSamples == 1 ? 1.0 : n - 1.0;
1000 var = sum2 / divisor;
Christoph Studer546bec82014-03-14 12:17:12 +01001001 }
1002
1003 @Override
1004 public String toString() {
1005 return "Aggregate{" +
1006 "numSamples=" + numSamples +
Christoph Studer546bec82014-03-14 12:17:12 +01001007 ", avg=" + avg +
Chris Wren51103972014-04-01 13:57:34 -04001008 ", var=" + var +
Christoph Studer546bec82014-03-14 12:17:12 +01001009 '}';
1010 }
1011 }
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001012
1013 private static class SQLiteLog {
1014 private static final String TAG = "NotificationSQLiteLog";
1015
1016 // Message types passed to the background handler.
1017 private static final int MSG_POST = 1;
1018 private static final int MSG_CLICK = 2;
1019 private static final int MSG_REMOVE = 3;
1020 private static final int MSG_DISMISS = 4;
1021
1022 private static final String DB_NAME = "notification_log.db";
Chris Wrencdee8cd2016-01-25 17:10:30 -05001023 private static final int DB_VERSION = 5;
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001024
1025 /** Age in ms after which events are pruned from the DB. */
1026 private static final long HORIZON_MS = 7 * 24 * 60 * 60 * 1000L; // 1 week
1027 /** Delay between pruning the DB. Used to throttle pruning. */
1028 private static final long PRUNE_MIN_DELAY_MS = 6 * 60 * 60 * 1000L; // 6 hours
1029 /** Mininum number of writes between pruning the DB. Used to throttle pruning. */
1030 private static final long PRUNE_MIN_WRITES = 1024;
1031
1032 // Table 'log'
1033 private static final String TAB_LOG = "log";
1034 private static final String COL_EVENT_USER_ID = "event_user_id";
1035 private static final String COL_EVENT_TYPE = "event_type";
1036 private static final String COL_EVENT_TIME = "event_time_ms";
1037 private static final String COL_KEY = "key";
1038 private static final String COL_PKG = "pkg";
1039 private static final String COL_NOTIFICATION_ID = "nid";
1040 private static final String COL_TAG = "tag";
1041 private static final String COL_WHEN_MS = "when_ms";
1042 private static final String COL_DEFAULTS = "defaults";
1043 private static final String COL_FLAGS = "flags";
Chris Wrencdee8cd2016-01-25 17:10:30 -05001044 private static final String COL_IMPORTANCE_REQ = "importance_request";
1045 private static final String COL_IMPORTANCE_FINAL = "importance_final";
1046 private static final String COL_NOISY = "noisy";
1047 private static final String COL_MUTED = "muted";
1048 private static final String COL_DEMOTED = "demoted";
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001049 private static final String COL_CATEGORY = "category";
1050 private static final String COL_ACTION_COUNT = "action_count";
Christoph Studerffeb0c32014-05-07 22:23:56 +02001051 private static final String COL_POSTTIME_MS = "posttime_ms";
1052 private static final String COL_AIRTIME_MS = "airtime_ms";
Chris Wren78403d72014-07-28 10:23:24 +01001053 private static final String COL_FIRST_EXPANSIONTIME_MS = "first_expansion_time_ms";
1054 private static final String COL_AIRTIME_EXPANDED_MS = "expansion_airtime_ms";
1055 private static final String COL_EXPAND_COUNT = "expansion_count";
1056
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001057
1058 private static final int EVENT_TYPE_POST = 1;
1059 private static final int EVENT_TYPE_CLICK = 2;
1060 private static final int EVENT_TYPE_REMOVE = 3;
1061 private static final int EVENT_TYPE_DISMISS = 4;
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001062 private static long sLastPruneMs;
Chris Wrencdee8cd2016-01-25 17:10:30 -05001063
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001064 private static long sNumWrites;
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001065 private final SQLiteOpenHelper mHelper;
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001066
Chris Wrencdee8cd2016-01-25 17:10:30 -05001067 private final Handler mWriteHandler;
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001068 private static final long DAY_MS = 24 * 60 * 60 * 1000;
Chris Wrencdee8cd2016-01-25 17:10:30 -05001069 private static final String STATS_QUERY = "SELECT " +
1070 COL_EVENT_USER_ID + ", " +
1071 COL_PKG + ", " +
1072 // Bucket by day by looking at 'floor((midnight - eventTimeMs) / dayMs)'
1073 "CAST(((%d - " + COL_EVENT_TIME + ") / " + DAY_MS + ") AS int) " +
1074 "AS day, " +
1075 "COUNT(*) AS cnt, " +
1076 "SUM(" + COL_MUTED + ") as muted, " +
1077 "SUM(" + COL_NOISY + ") as noisy, " +
1078 "SUM(" + COL_DEMOTED + ") as demoted " +
1079 "FROM " + TAB_LOG + " " +
1080 "WHERE " +
1081 COL_EVENT_TYPE + "=" + EVENT_TYPE_POST +
1082 " AND " + COL_EVENT_TIME + " > %d " +
1083 " GROUP BY " + COL_EVENT_USER_ID + ", day, " + COL_PKG;
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001084
1085 public SQLiteLog(Context context) {
1086 HandlerThread backgroundThread = new HandlerThread("notification-sqlite-log",
1087 android.os.Process.THREAD_PRIORITY_BACKGROUND);
1088 backgroundThread.start();
1089 mWriteHandler = new Handler(backgroundThread.getLooper()) {
1090 @Override
1091 public void handleMessage(Message msg) {
1092 NotificationRecord r = (NotificationRecord) msg.obj;
1093 long nowMs = System.currentTimeMillis();
1094 switch (msg.what) {
1095 case MSG_POST:
Christoph Studerffeb0c32014-05-07 22:23:56 +02001096 writeEvent(r.sbn.getPostTime(), EVENT_TYPE_POST, r);
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001097 break;
1098 case MSG_CLICK:
Christoph Studerffeb0c32014-05-07 22:23:56 +02001099 writeEvent(nowMs, EVENT_TYPE_CLICK, r);
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001100 break;
1101 case MSG_REMOVE:
Christoph Studerffeb0c32014-05-07 22:23:56 +02001102 writeEvent(nowMs, EVENT_TYPE_REMOVE, r);
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001103 break;
1104 case MSG_DISMISS:
Christoph Studerffeb0c32014-05-07 22:23:56 +02001105 writeEvent(nowMs, EVENT_TYPE_DISMISS, r);
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001106 break;
1107 default:
1108 Log.wtf(TAG, "Unknown message type: " + msg.what);
1109 break;
1110 }
1111 }
1112 };
1113 mHelper = new SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
1114 @Override
1115 public void onCreate(SQLiteDatabase db) {
1116 db.execSQL("CREATE TABLE " + TAB_LOG + " (" +
1117 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
1118 COL_EVENT_USER_ID + " INT," +
1119 COL_EVENT_TYPE + " INT," +
1120 COL_EVENT_TIME + " INT," +
1121 COL_KEY + " TEXT," +
1122 COL_PKG + " TEXT," +
1123 COL_NOTIFICATION_ID + " INT," +
1124 COL_TAG + " TEXT," +
1125 COL_WHEN_MS + " INT," +
1126 COL_DEFAULTS + " INT," +
1127 COL_FLAGS + " INT," +
Chris Wrencdee8cd2016-01-25 17:10:30 -05001128 COL_IMPORTANCE_REQ + " INT," +
1129 COL_IMPORTANCE_FINAL + " INT," +
1130 COL_NOISY + " INT," +
1131 COL_MUTED + " INT," +
1132 COL_DEMOTED + " INT," +
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001133 COL_CATEGORY + " TEXT," +
Christoph Studerffeb0c32014-05-07 22:23:56 +02001134 COL_ACTION_COUNT + " INT," +
1135 COL_POSTTIME_MS + " INT," +
Christoph Studer1ad856e2014-08-04 14:31:25 +02001136 COL_AIRTIME_MS + " INT," +
1137 COL_FIRST_EXPANSIONTIME_MS + " INT," +
1138 COL_AIRTIME_EXPANDED_MS + " INT," +
Chris Wren78403d72014-07-28 10:23:24 +01001139 COL_EXPAND_COUNT + " INT" +
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001140 ")");
1141 }
1142
1143 @Override
1144 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Chris Wrencdee8cd2016-01-25 17:10:30 -05001145 if (oldVersion != newVersion) {
Christoph Studer1ad856e2014-08-04 14:31:25 +02001146 db.execSQL("DROP TABLE IF EXISTS " + TAB_LOG);
1147 onCreate(db);
Christoph Studerffeb0c32014-05-07 22:23:56 +02001148 }
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001149 }
1150 };
1151 }
1152
1153 public void logPosted(NotificationRecord notification) {
1154 mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_POST, notification));
1155 }
1156
1157 public void logClicked(NotificationRecord notification) {
1158 mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_CLICK, notification));
1159 }
1160
1161 public void logRemoved(NotificationRecord notification) {
1162 mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_REMOVE, notification));
1163 }
1164
1165 public void logDismissed(NotificationRecord notification) {
1166 mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_DISMISS, notification));
1167 }
1168
Chris Wrencdee8cd2016-01-25 17:10:30 -05001169 private JSONArray jsonPostFrequencies(DumpFilter filter) throws JSONException {
Chris Wrene4b38802015-07-07 15:54:19 -04001170 JSONArray frequencies = new JSONArray();
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001171 SQLiteDatabase db = mHelper.getReadableDatabase();
Chris Wrene4b38802015-07-07 15:54:19 -04001172 long midnight = getMidnightMs();
Chris Wrencdee8cd2016-01-25 17:10:30 -05001173 String q = String.format(STATS_QUERY, midnight, filter.since);
Chris Wrene4b38802015-07-07 15:54:19 -04001174 Cursor cursor = db.rawQuery(q, null);
1175 try {
1176 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
1177 int userId = cursor.getInt(0);
1178 String pkg = cursor.getString(1);
1179 if (filter != null && !filter.matches(pkg)) continue;
1180 int day = cursor.getInt(2);
1181 int count = cursor.getInt(3);
Chris Wrencdee8cd2016-01-25 17:10:30 -05001182 int muted = cursor.getInt(4);
1183 int noisy = cursor.getInt(5);
1184 int demoted = cursor.getInt(6);
Chris Wrene4b38802015-07-07 15:54:19 -04001185 JSONObject row = new JSONObject();
1186 row.put("user_id", userId);
1187 row.put("package", pkg);
1188 row.put("day", day);
1189 row.put("count", count);
Chris Wrencdee8cd2016-01-25 17:10:30 -05001190 row.put("noisy", noisy);
1191 row.put("muted", muted);
1192 row.put("demoted", demoted);
Chris Wrene4b38802015-07-07 15:54:19 -04001193 frequencies.put(row);
1194 }
1195 } finally {
1196 cursor.close();
1197 }
1198 return frequencies;
1199 }
1200
1201 public void printPostFrequencies(PrintWriter pw, String indent, DumpFilter filter) {
1202 SQLiteDatabase db = mHelper.getReadableDatabase();
1203 long midnight = getMidnightMs();
Chris Wrencdee8cd2016-01-25 17:10:30 -05001204 String q = String.format(STATS_QUERY, midnight, filter.since);
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001205 Cursor cursor = db.rawQuery(q, null);
1206 try {
1207 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
1208 int userId = cursor.getInt(0);
1209 String pkg = cursor.getString(1);
John Spurlock25e2d242014-06-27 13:58:23 -04001210 if (filter != null && !filter.matches(pkg)) continue;
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001211 int day = cursor.getInt(2);
1212 int count = cursor.getInt(3);
Chris Wrencdee8cd2016-01-25 17:10:30 -05001213 int muted = cursor.getInt(4);
1214 int noisy = cursor.getInt(5);
1215 int demoted = cursor.getInt(6);
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001216 pw.println(indent + "post_frequency{user_id=" + userId + ",pkg=" + pkg +
Chris Wrencdee8cd2016-01-25 17:10:30 -05001217 ",day=" + day + ",count=" + count + ",muted=" + muted + "/" + noisy +
1218 ",demoted=" + demoted + "}");
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001219 }
1220 } finally {
1221 cursor.close();
1222 }
1223 }
1224
Chris Wrene4b38802015-07-07 15:54:19 -04001225 private long getMidnightMs() {
1226 GregorianCalendar midnight = new GregorianCalendar();
1227 midnight.set(midnight.get(Calendar.YEAR), midnight.get(Calendar.MONTH),
1228 midnight.get(Calendar.DATE), 23, 59, 59);
1229 return midnight.getTimeInMillis();
1230 }
1231
Christoph Studerffeb0c32014-05-07 22:23:56 +02001232 private void writeEvent(long eventTimeMs, int eventType, NotificationRecord r) {
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001233 ContentValues cv = new ContentValues();
1234 cv.put(COL_EVENT_USER_ID, r.sbn.getUser().getIdentifier());
1235 cv.put(COL_EVENT_TIME, eventTimeMs);
1236 cv.put(COL_EVENT_TYPE, eventType);
1237 putNotificationIdentifiers(r, cv);
Christoph Studerffeb0c32014-05-07 22:23:56 +02001238 if (eventType == EVENT_TYPE_POST) {
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001239 putNotificationDetails(r, cv);
Christoph Studerffeb0c32014-05-07 22:23:56 +02001240 } else {
Chris Wren78403d72014-07-28 10:23:24 +01001241 putPosttimeVisibility(r, cv);
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001242 }
1243 SQLiteDatabase db = mHelper.getWritableDatabase();
1244 if (db.insert(TAB_LOG, null, cv) < 0) {
1245 Log.wtf(TAG, "Error while trying to insert values: " + cv);
1246 }
1247 sNumWrites++;
1248 pruneIfNecessary(db);
1249 }
1250
1251 private void pruneIfNecessary(SQLiteDatabase db) {
1252 // Prune if we haven't in a while.
1253 long nowMs = System.currentTimeMillis();
1254 if (sNumWrites > PRUNE_MIN_WRITES ||
1255 nowMs - sLastPruneMs > PRUNE_MIN_DELAY_MS) {
1256 sNumWrites = 0;
1257 sLastPruneMs = nowMs;
1258 long horizonStartMs = nowMs - HORIZON_MS;
1259 int deletedRows = db.delete(TAB_LOG, COL_EVENT_TIME + " < ?",
1260 new String[] { String.valueOf(horizonStartMs) });
1261 Log.d(TAG, "Pruned event entries: " + deletedRows);
1262 }
1263 }
1264
1265 private static void putNotificationIdentifiers(NotificationRecord r, ContentValues outCv) {
1266 outCv.put(COL_KEY, r.sbn.getKey());
1267 outCv.put(COL_PKG, r.sbn.getPackageName());
1268 }
1269
1270 private static void putNotificationDetails(NotificationRecord r, ContentValues outCv) {
1271 outCv.put(COL_NOTIFICATION_ID, r.sbn.getId());
1272 if (r.sbn.getTag() != null) {
1273 outCv.put(COL_TAG, r.sbn.getTag());
1274 }
1275 outCv.put(COL_WHEN_MS, r.sbn.getPostTime());
1276 outCv.put(COL_FLAGS, r.getNotification().flags);
Chris Wrencdee8cd2016-01-25 17:10:30 -05001277 final int before = r.stats.requestedImportance;
1278 final int after = r.getImportance();
1279 final boolean noisy = r.stats.isNoisy;
1280 outCv.put(COL_IMPORTANCE_REQ, before);
1281 outCv.put(COL_IMPORTANCE_FINAL, after);
1282 outCv.put(COL_DEMOTED, after < before ? 1 : 0);
1283 outCv.put(COL_NOISY, noisy);
1284 if (noisy && after < IMPORTANCE_HIGH) {
1285 outCv.put(COL_MUTED, 1);
1286 } else {
1287 outCv.put(COL_MUTED, 0);
1288 }
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001289 if (r.getNotification().category != null) {
1290 outCv.put(COL_CATEGORY, r.getNotification().category);
1291 }
1292 outCv.put(COL_ACTION_COUNT, r.getNotification().actions != null ?
1293 r.getNotification().actions.length : 0);
1294 }
1295
Chris Wren78403d72014-07-28 10:23:24 +01001296 private static void putPosttimeVisibility(NotificationRecord r, ContentValues outCv) {
Christoph Studerffeb0c32014-05-07 22:23:56 +02001297 outCv.put(COL_POSTTIME_MS, r.stats.getCurrentPosttimeMs());
1298 outCv.put(COL_AIRTIME_MS, r.stats.getCurrentAirtimeMs());
Chris Wren78403d72014-07-28 10:23:24 +01001299 outCv.put(COL_EXPAND_COUNT, r.stats.userExpansionCount);
1300 outCv.put(COL_AIRTIME_EXPANDED_MS, r.stats.getCurrentAirtimeExpandedMs());
1301 outCv.put(COL_FIRST_EXPANSIONTIME_MS, r.stats.posttimeToFirstVisibleExpansionMs);
Christoph Studerffeb0c32014-05-07 22:23:56 +02001302 }
1303
John Spurlock25e2d242014-06-27 13:58:23 -04001304 public void dump(PrintWriter pw, String indent, DumpFilter filter) {
1305 printPostFrequencies(pw, indent, filter);
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001306 }
Chris Wrene4b38802015-07-07 15:54:19 -04001307
1308 public JSONObject dumpJson(DumpFilter filter) {
1309 JSONObject dump = new JSONObject();
1310 try {
Chris Wrencdee8cd2016-01-25 17:10:30 -05001311 dump.put("post_frequency", jsonPostFrequencies(filter));
1312 dump.put("since", filter.since);
1313 dump.put("now", System.currentTimeMillis());
Chris Wrene4b38802015-07-07 15:54:19 -04001314 } catch (JSONException e) {
1315 // pass
1316 }
1317 return dump;
1318 }
Christoph Studer1c3f81f2014-04-16 15:05:56 +02001319 }
Christoph Studer546bec82014-03-14 12:17:12 +01001320}