1 /* 2 * Copyright (C) 2019 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 package com.android.server.usage; 17 18 import android.app.usage.ConfigurationStats; 19 import android.app.usage.UsageEvents; 20 import android.app.usage.UsageStats; 21 import android.content.res.Configuration; 22 import android.util.Pair; 23 import android.util.Slog; 24 import android.util.SparseArray; 25 import android.util.SparseIntArray; 26 import android.util.proto.ProtoInputStream; 27 import android.util.proto.ProtoOutputStream; 28 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.util.ArrayList; 33 import java.util.LinkedList; 34 import java.util.Map; 35 import java.util.concurrent.TimeUnit; 36 37 /** 38 * UsageStats reader/writer V2 for Protocol Buffer format. 39 */ 40 final class UsageStatsProtoV2 { 41 private static final String TAG = "UsageStatsProtoV2"; 42 43 private static final long ONE_HOUR_MS = TimeUnit.HOURS.toMillis(1); 44 45 // Static-only utility class. UsageStatsProtoV2()46 private UsageStatsProtoV2() {} 47 parseUsageStats(ProtoInputStream proto, final long beginTime)48 private static UsageStats parseUsageStats(ProtoInputStream proto, final long beginTime) 49 throws IOException { 50 UsageStats stats = new UsageStats(); 51 // Time attributes stored is an offset of the beginTime. 52 while (true) { 53 switch (proto.nextField()) { 54 case (int) UsageStatsObfuscatedProto.PACKAGE_TOKEN: 55 stats.mPackageToken = proto.readInt( 56 UsageStatsObfuscatedProto.PACKAGE_TOKEN) - 1; 57 break; 58 case (int) UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS: 59 stats.mLastTimeUsed = beginTime + proto.readLong( 60 UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS); 61 break; 62 case (int) UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS: 63 stats.mTotalTimeInForeground = proto.readLong( 64 UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS); 65 break; 66 case (int) UsageStatsObfuscatedProto.APP_LAUNCH_COUNT: 67 stats.mAppLaunchCount = proto.readInt( 68 UsageStatsObfuscatedProto.APP_LAUNCH_COUNT); 69 break; 70 case (int) UsageStatsObfuscatedProto.CHOOSER_ACTIONS: 71 try { 72 final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS); 73 loadChooserCounts(proto, stats); 74 proto.end(token); 75 } catch (IOException e) { 76 Slog.e(TAG, "Unable to read chooser counts for " + stats.mPackageToken); 77 } 78 break; 79 case (int) UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS: 80 stats.mLastTimeForegroundServiceUsed = beginTime + proto.readLong( 81 UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS); 82 break; 83 case (int) UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS: 84 stats.mTotalTimeForegroundServiceUsed = proto.readLong( 85 UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS); 86 break; 87 case (int) UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS: 88 stats.mLastTimeVisible = beginTime + proto.readLong( 89 UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS); 90 break; 91 case (int) UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS: 92 stats.mTotalTimeVisible = proto.readLong( 93 UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS); 94 break; 95 case (int) UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS: 96 stats.mLastTimeComponentUsed = beginTime + proto.readLong( 97 UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS); 98 break; 99 case ProtoInputStream.NO_MORE_FIELDS: 100 return stats; 101 } 102 } 103 } 104 loadCountAndTime(ProtoInputStream proto, long fieldId, IntervalStats.EventTracker tracker)105 private static void loadCountAndTime(ProtoInputStream proto, long fieldId, 106 IntervalStats.EventTracker tracker) { 107 try { 108 final long token = proto.start(fieldId); 109 while (true) { 110 switch (proto.nextField()) { 111 case (int) IntervalStatsObfuscatedProto.CountAndTime.COUNT: 112 tracker.count = proto.readInt( 113 IntervalStatsObfuscatedProto.CountAndTime.COUNT); 114 break; 115 case (int) IntervalStatsObfuscatedProto.CountAndTime.TIME_MS: 116 tracker.duration = proto.readLong( 117 IntervalStatsObfuscatedProto.CountAndTime.TIME_MS); 118 break; 119 case ProtoInputStream.NO_MORE_FIELDS: 120 proto.end(token); 121 return; 122 } 123 } 124 } catch (IOException e) { 125 Slog.e(TAG, "Unable to read event tracker " + fieldId, e); 126 } 127 } 128 loadChooserCounts(ProtoInputStream proto, UsageStats usageStats)129 private static void loadChooserCounts(ProtoInputStream proto, UsageStats usageStats) 130 throws IOException { 131 int actionToken; 132 SparseIntArray counts; 133 if (proto.nextField(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN)) { 134 // Fast path; this should work for most cases since the action token is written first 135 actionToken = proto.readInt(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1; 136 counts = usageStats.mChooserCountsObfuscated.get(actionToken); 137 if (counts == null) { 138 counts = new SparseIntArray(); 139 usageStats.mChooserCountsObfuscated.put(actionToken, counts); 140 } 141 } else { 142 counts = new SparseIntArray(); 143 } 144 145 while (true) { 146 switch (proto.nextField()) { 147 case (int) UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN: 148 // Fast path failed for some reason, add the SparseIntArray object to usageStats 149 actionToken = proto.readInt( 150 UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1; 151 usageStats.mChooserCountsObfuscated.put(actionToken, counts); 152 break; 153 case (int) UsageStatsObfuscatedProto.ChooserAction.COUNTS: 154 final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS); 155 loadCountsForAction(proto, counts); 156 proto.end(token); 157 break; 158 case ProtoInputStream.NO_MORE_FIELDS: 159 return; // if the action was never read, the loaded counts will be ignored. 160 } 161 } 162 } 163 loadCountsForAction(ProtoInputStream proto, SparseIntArray counts)164 private static void loadCountsForAction(ProtoInputStream proto, SparseIntArray counts) 165 throws IOException { 166 int categoryToken = PackagesTokenData.UNASSIGNED_TOKEN; 167 int count = 0; 168 while (true) { 169 switch (proto.nextField()) { 170 case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN: 171 categoryToken = proto.readInt( 172 UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN) 173 - 1; 174 break; 175 case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT: 176 count = proto.readInt( 177 UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT); 178 break; 179 case ProtoInputStream.NO_MORE_FIELDS: 180 if (categoryToken != PackagesTokenData.UNASSIGNED_TOKEN) { 181 counts.put(categoryToken, count); 182 } 183 return; 184 } 185 } 186 } 187 loadConfigStats(ProtoInputStream proto, IntervalStats stats)188 private static void loadConfigStats(ProtoInputStream proto, IntervalStats stats) 189 throws IOException { 190 boolean configActive = false; 191 final Configuration config = new Configuration(); 192 ConfigurationStats configStats = new ConfigurationStats(); 193 if (proto.nextField(IntervalStatsObfuscatedProto.Configuration.CONFIG)) { 194 // Fast path; this should work since the configuration is written first 195 config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG); 196 configStats = stats.getOrCreateConfigurationStats(config); 197 } 198 199 while (true) { 200 switch (proto.nextField()) { 201 case (int) IntervalStatsObfuscatedProto.Configuration.CONFIG: 202 // Fast path failed from some reason, add ConfigStats object to statsOut now 203 config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG); 204 final ConfigurationStats temp = stats.getOrCreateConfigurationStats(config); 205 temp.mLastTimeActive = configStats.mLastTimeActive; 206 temp.mTotalTimeActive = configStats.mTotalTimeActive; 207 temp.mActivationCount = configStats.mActivationCount; 208 configStats = temp; 209 break; 210 case (int) IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS: 211 configStats.mLastTimeActive = stats.beginTime + proto.readLong( 212 IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS); 213 break; 214 case (int) IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS: 215 configStats.mTotalTimeActive = proto.readLong( 216 IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS); 217 break; 218 case (int) IntervalStatsObfuscatedProto.Configuration.COUNT: 219 configStats.mActivationCount = proto.readInt( 220 IntervalStatsObfuscatedProto.Configuration.COUNT); 221 break; 222 case (int) IntervalStatsObfuscatedProto.Configuration.ACTIVE: 223 configActive = proto.readBoolean( 224 IntervalStatsObfuscatedProto.Configuration.ACTIVE); 225 break; 226 case ProtoInputStream.NO_MORE_FIELDS: 227 if (configActive) { 228 stats.activeConfiguration = configStats.mConfiguration; 229 } 230 return; 231 } 232 } 233 } 234 parseEvent(ProtoInputStream proto, long beginTime)235 private static UsageEvents.Event parseEvent(ProtoInputStream proto, long beginTime) 236 throws IOException { 237 final UsageEvents.Event event = new UsageEvents.Event(); 238 while (true) { 239 switch (proto.nextField()) { 240 case (int) EventObfuscatedProto.PACKAGE_TOKEN: 241 event.mPackageToken = proto.readInt(EventObfuscatedProto.PACKAGE_TOKEN) - 1; 242 break; 243 case (int) EventObfuscatedProto.CLASS_TOKEN: 244 event.mClassToken = proto.readInt(EventObfuscatedProto.CLASS_TOKEN) - 1; 245 break; 246 case (int) EventObfuscatedProto.TIME_MS: 247 event.mTimeStamp = beginTime + proto.readLong(EventObfuscatedProto.TIME_MS); 248 break; 249 case (int) EventObfuscatedProto.FLAGS: 250 event.mFlags = proto.readInt(EventObfuscatedProto.FLAGS); 251 break; 252 case (int) EventObfuscatedProto.TYPE: 253 event.mEventType = proto.readInt(EventObfuscatedProto.TYPE); 254 break; 255 case (int) EventObfuscatedProto.CONFIG: 256 event.mConfiguration = new Configuration(); 257 event.mConfiguration.readFromProto(proto, EventObfuscatedProto.CONFIG); 258 break; 259 case (int) EventObfuscatedProto.SHORTCUT_ID_TOKEN: 260 event.mShortcutIdToken = proto.readInt( 261 EventObfuscatedProto.SHORTCUT_ID_TOKEN) - 1; 262 break; 263 case (int) EventObfuscatedProto.STANDBY_BUCKET: 264 event.mBucketAndReason = proto.readInt(EventObfuscatedProto.STANDBY_BUCKET); 265 break; 266 case (int) EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN: 267 event.mNotificationChannelIdToken = proto.readInt( 268 EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN) - 1; 269 break; 270 case (int) EventObfuscatedProto.INSTANCE_ID: 271 event.mInstanceId = proto.readInt(EventObfuscatedProto.INSTANCE_ID); 272 break; 273 case (int) EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN: 274 event.mTaskRootPackageToken = proto.readInt( 275 EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN) - 1; 276 break; 277 case (int) EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN: 278 event.mTaskRootClassToken = proto.readInt( 279 EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN) - 1; 280 break; 281 case (int) EventObfuscatedProto.LOCUS_ID_TOKEN: 282 event.mLocusIdToken = proto.readInt( 283 EventObfuscatedProto.LOCUS_ID_TOKEN) - 1; 284 break; 285 case ProtoInputStream.NO_MORE_FIELDS: 286 return event.mPackageToken == PackagesTokenData.UNASSIGNED_TOKEN ? null : event; 287 } 288 } 289 } 290 writeOffsetTimestamp(ProtoOutputStream proto, long fieldId, long timestamp, long beginTime)291 static void writeOffsetTimestamp(ProtoOutputStream proto, long fieldId, 292 long timestamp, long beginTime) { 293 // timestamps will only be written if they're after the begin time 294 // a grace period of one hour before the begin time is allowed because of rollover logic 295 final long rolloverGracePeriod = beginTime - ONE_HOUR_MS; 296 if (timestamp > rolloverGracePeriod) { 297 // time attributes are stored as an offset of the begin time (given offset) 298 proto.write(fieldId, getOffsetTimestamp(timestamp, beginTime)); 299 } 300 } 301 getOffsetTimestamp(long timestamp, long offset)302 static long getOffsetTimestamp(long timestamp, long offset) { 303 final long offsetTimestamp = timestamp - offset; 304 // add one ms to timestamp if 0 to ensure it's written to proto (default values are ignored) 305 return offsetTimestamp == 0 ? offsetTimestamp + 1 : offsetTimestamp; 306 } 307 writeUsageStats(ProtoOutputStream proto, final long beginTime, final UsageStats stats)308 private static void writeUsageStats(ProtoOutputStream proto, final long beginTime, 309 final UsageStats stats) throws IllegalArgumentException { 310 proto.write(UsageStatsObfuscatedProto.PACKAGE_TOKEN, stats.mPackageToken + 1); 311 writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS, 312 stats.mLastTimeUsed, beginTime); 313 proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS, stats.mTotalTimeInForeground); 314 writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS, 315 stats.mLastTimeForegroundServiceUsed, beginTime); 316 proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS, 317 stats.mTotalTimeForegroundServiceUsed); 318 writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS, 319 stats.mLastTimeVisible, beginTime); 320 proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS, stats.mTotalTimeVisible); 321 writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS, 322 stats.mLastTimeComponentUsed, beginTime); 323 proto.write(UsageStatsObfuscatedProto.APP_LAUNCH_COUNT, stats.mAppLaunchCount); 324 try { 325 writeChooserCounts(proto, stats); 326 } catch (IllegalArgumentException e) { 327 Slog.e(TAG, "Unable to write chooser counts for " + stats.mPackageName, e); 328 } 329 } 330 writeCountAndTime(ProtoOutputStream proto, long fieldId, int count, long time)331 private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count, 332 long time) throws IllegalArgumentException { 333 final long token = proto.start(fieldId); 334 proto.write(IntervalStatsObfuscatedProto.CountAndTime.COUNT, count); 335 proto.write(IntervalStatsObfuscatedProto.CountAndTime.TIME_MS, time); 336 proto.end(token); 337 } 338 writeChooserCounts(ProtoOutputStream proto, final UsageStats stats)339 private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats stats) 340 throws IllegalArgumentException { 341 if (stats == null || stats.mChooserCountsObfuscated.size() == 0) { 342 return; 343 } 344 final int chooserCountSize = stats.mChooserCountsObfuscated.size(); 345 for (int i = 0; i < chooserCountSize; i++) { 346 final int action = stats.mChooserCountsObfuscated.keyAt(i); 347 final SparseIntArray counts = stats.mChooserCountsObfuscated.valueAt(i); 348 if (counts == null || counts.size() == 0) { 349 continue; 350 } 351 final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS); 352 proto.write(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN, action + 1); 353 writeCountsForAction(proto, counts); 354 proto.end(token); 355 } 356 } 357 writeCountsForAction(ProtoOutputStream proto, SparseIntArray counts)358 private static void writeCountsForAction(ProtoOutputStream proto, SparseIntArray counts) 359 throws IllegalArgumentException { 360 final int countsSize = counts.size(); 361 for (int i = 0; i < countsSize; i++) { 362 final int category = counts.keyAt(i); 363 final int count = counts.valueAt(i); 364 if (count <= 0) { 365 continue; 366 } 367 final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS); 368 proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN, 369 category + 1); 370 proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT, count); 371 proto.end(token); 372 } 373 } 374 writeConfigStats(ProtoOutputStream proto, final long statsBeginTime, final ConfigurationStats configStats, boolean isActive)375 private static void writeConfigStats(ProtoOutputStream proto, final long statsBeginTime, 376 final ConfigurationStats configStats, boolean isActive) 377 throws IllegalArgumentException { 378 configStats.mConfiguration.dumpDebug(proto, 379 IntervalStatsObfuscatedProto.Configuration.CONFIG); 380 writeOffsetTimestamp(proto, IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS, 381 configStats.mLastTimeActive, statsBeginTime); 382 proto.write(IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS, 383 configStats.mTotalTimeActive); 384 proto.write(IntervalStatsObfuscatedProto.Configuration.COUNT, configStats.mActivationCount); 385 proto.write(IntervalStatsObfuscatedProto.Configuration.ACTIVE, isActive); 386 } 387 writeEvent(ProtoOutputStream proto, final long statsBeginTime, final UsageEvents.Event event)388 private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime, 389 final UsageEvents.Event event) throws IllegalArgumentException { 390 proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1); 391 if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) { 392 proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1); 393 } 394 writeOffsetTimestamp(proto, EventObfuscatedProto.TIME_MS, event.mTimeStamp, statsBeginTime); 395 proto.write(EventObfuscatedProto.FLAGS, event.mFlags); 396 proto.write(EventObfuscatedProto.TYPE, event.mEventType); 397 proto.write(EventObfuscatedProto.INSTANCE_ID, event.mInstanceId); 398 if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) { 399 proto.write(EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN, 400 event.mTaskRootPackageToken + 1); 401 } 402 if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) { 403 proto.write(EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN, event.mTaskRootClassToken + 1); 404 } 405 switch (event.mEventType) { 406 case UsageEvents.Event.CONFIGURATION_CHANGE: 407 if (event.mConfiguration != null) { 408 event.mConfiguration.dumpDebug(proto, EventObfuscatedProto.CONFIG); 409 } 410 break; 411 case UsageEvents.Event.STANDBY_BUCKET_CHANGED: 412 if (event.mBucketAndReason != 0) { 413 proto.write(EventObfuscatedProto.STANDBY_BUCKET, event.mBucketAndReason); 414 } 415 break; 416 case UsageEvents.Event.SHORTCUT_INVOCATION: 417 if (event.mShortcutIdToken != PackagesTokenData.UNASSIGNED_TOKEN) { 418 proto.write(EventObfuscatedProto.SHORTCUT_ID_TOKEN, event.mShortcutIdToken + 1); 419 } 420 break; 421 case UsageEvents.Event.LOCUS_ID_SET: 422 if (event.mLocusIdToken != PackagesTokenData.UNASSIGNED_TOKEN) { 423 proto.write(EventObfuscatedProto.LOCUS_ID_TOKEN, event.mLocusIdToken + 1); 424 } 425 break; 426 case UsageEvents.Event.NOTIFICATION_INTERRUPTION: 427 if (event.mNotificationChannelIdToken != PackagesTokenData.UNASSIGNED_TOKEN) { 428 proto.write(EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN, 429 event.mNotificationChannelIdToken + 1); 430 } 431 break; 432 } 433 } 434 435 /** 436 * Populates a tokenized version of interval stats from the input stream given. 437 * 438 * @param in the input stream from which to read events. 439 * @param stats the interval stats object which will be populated. 440 */ read(InputStream in, IntervalStats stats)441 public static void read(InputStream in, IntervalStats stats) throws IOException { 442 final ProtoInputStream proto = new ProtoInputStream(in); 443 while (true) { 444 switch (proto.nextField()) { 445 case (int) IntervalStatsObfuscatedProto.END_TIME_MS: 446 stats.endTime = stats.beginTime + proto.readLong( 447 IntervalStatsObfuscatedProto.END_TIME_MS); 448 break; 449 case (int) IntervalStatsObfuscatedProto.MAJOR_VERSION: 450 stats.majorVersion = proto.readInt(IntervalStatsObfuscatedProto.MAJOR_VERSION); 451 break; 452 case (int) IntervalStatsObfuscatedProto.MINOR_VERSION: 453 stats.minorVersion = proto.readInt(IntervalStatsObfuscatedProto.MINOR_VERSION); 454 break; 455 case (int) IntervalStatsObfuscatedProto.INTERACTIVE: 456 loadCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE, 457 stats.interactiveTracker); 458 break; 459 case (int) IntervalStatsObfuscatedProto.NON_INTERACTIVE: 460 loadCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE, 461 stats.nonInteractiveTracker); 462 break; 463 case (int) IntervalStatsObfuscatedProto.KEYGUARD_SHOWN: 464 loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN, 465 stats.keyguardShownTracker); 466 break; 467 case (int) IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN: 468 loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN, 469 stats.keyguardHiddenTracker); 470 break; 471 case (int) IntervalStatsObfuscatedProto.PACKAGES: 472 try { 473 final long packagesToken = proto.start( 474 IntervalStatsObfuscatedProto.PACKAGES); 475 UsageStats usageStats = parseUsageStats(proto, stats.beginTime); 476 proto.end(packagesToken); 477 if (usageStats.mPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) { 478 stats.packageStatsObfuscated.put(usageStats.mPackageToken, usageStats); 479 } 480 } catch (IOException e) { 481 Slog.e(TAG, "Unable to read some usage stats from proto.", e); 482 } 483 break; 484 case (int) IntervalStatsObfuscatedProto.CONFIGURATIONS: 485 try { 486 final long configsToken = proto.start( 487 IntervalStatsObfuscatedProto.CONFIGURATIONS); 488 loadConfigStats(proto, stats); 489 proto.end(configsToken); 490 } catch (IOException e) { 491 Slog.e(TAG, "Unable to read some configuration stats from proto.", e); 492 } 493 break; 494 case (int) IntervalStatsObfuscatedProto.EVENT_LOG: 495 try { 496 final long eventsToken = proto.start( 497 IntervalStatsObfuscatedProto.EVENT_LOG); 498 UsageEvents.Event event = parseEvent(proto, stats.beginTime); 499 proto.end(eventsToken); 500 if (event != null) { 501 stats.events.insert(event); 502 } 503 } catch (IOException e) { 504 Slog.e(TAG, "Unable to read some events from proto.", e); 505 } 506 break; 507 case ProtoInputStream.NO_MORE_FIELDS: 508 // update the begin and end time stamps for all usage stats 509 final int usageStatsSize = stats.packageStatsObfuscated.size(); 510 for (int i = 0; i < usageStatsSize; i++) { 511 final UsageStats usageStats = stats.packageStatsObfuscated.valueAt(i); 512 usageStats.mBeginTimeStamp = stats.beginTime; 513 usageStats.mEndTimeStamp = stats.endTime; 514 } 515 return; 516 } 517 } 518 } 519 520 /** 521 * Writes the tokenized interval stats object to a ProtoBuf file. 522 * 523 * @param out the output stream to which to write the interval stats data. 524 * @param stats the interval stats object to write to the proto file. 525 */ write(OutputStream out, IntervalStats stats)526 public static void write(OutputStream out, IntervalStats stats) 527 throws IOException, IllegalArgumentException { 528 final ProtoOutputStream proto = new ProtoOutputStream(out); 529 proto.write(IntervalStatsObfuscatedProto.END_TIME_MS, 530 getOffsetTimestamp(stats.endTime, stats.beginTime)); 531 proto.write(IntervalStatsObfuscatedProto.MAJOR_VERSION, stats.majorVersion); 532 proto.write(IntervalStatsObfuscatedProto.MINOR_VERSION, stats.minorVersion); 533 534 try { 535 writeCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE, 536 stats.interactiveTracker.count, stats.interactiveTracker.duration); 537 writeCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE, 538 stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration); 539 writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN, 540 stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration); 541 writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN, 542 stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration); 543 } catch (IllegalArgumentException e) { 544 Slog.e(TAG, "Unable to write some interval stats trackers to proto.", e); 545 } 546 547 final int statsCount = stats.packageStatsObfuscated.size(); 548 for (int i = 0; i < statsCount; i++) { 549 try { 550 final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGES); 551 writeUsageStats(proto, stats.beginTime, stats.packageStatsObfuscated.valueAt(i)); 552 proto.end(token); 553 } catch (IllegalArgumentException e) { 554 Slog.e(TAG, "Unable to write some usage stats to proto.", e); 555 } 556 } 557 final int configCount = stats.configurations.size(); 558 for (int i = 0; i < configCount; i++) { 559 boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i)); 560 try { 561 final long token = proto.start(IntervalStatsObfuscatedProto.CONFIGURATIONS); 562 writeConfigStats(proto, stats.beginTime, stats.configurations.valueAt(i), active); 563 proto.end(token); 564 } catch (IllegalArgumentException e) { 565 Slog.e(TAG, "Unable to write some configuration stats to proto.", e); 566 } 567 } 568 final int eventCount = stats.events.size(); 569 for (int i = 0; i < eventCount; i++) { 570 try { 571 final long token = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG); 572 writeEvent(proto, stats.beginTime, stats.events.get(i)); 573 proto.end(token); 574 } catch (IllegalArgumentException e) { 575 Slog.e(TAG, "Unable to write some events to proto.", e); 576 } 577 } 578 579 proto.flush(); 580 } 581 582 /***** Read/Write obfuscated packages data logic. *****/ 583 loadPackagesMap(ProtoInputStream proto, SparseArray<ArrayList<String>> tokensToPackagesMap)584 private static void loadPackagesMap(ProtoInputStream proto, 585 SparseArray<ArrayList<String>> tokensToPackagesMap) throws IOException { 586 int key = PackagesTokenData.UNASSIGNED_TOKEN; 587 final ArrayList<String> strings = new ArrayList<>(); 588 while (true) { 589 switch (proto.nextField()) { 590 case (int) ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN: 591 key = proto.readInt(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN) - 1; 592 break; 593 case (int) ObfuscatedPackagesProto.PackagesMap.STRINGS: 594 strings.add(proto.readString(ObfuscatedPackagesProto.PackagesMap.STRINGS)); 595 break; 596 case ProtoInputStream.NO_MORE_FIELDS: 597 if (key != PackagesTokenData.UNASSIGNED_TOKEN) { 598 tokensToPackagesMap.put(key, strings); 599 } 600 return; 601 } 602 } 603 } 604 605 /** 606 * Populates the package mappings from the input stream given. 607 * 608 * @param in the input stream from which to read the mappings. 609 * @param packagesTokenData the packages data object to which the data will be read to. 610 */ readObfuscatedData(InputStream in, PackagesTokenData packagesTokenData)611 static void readObfuscatedData(InputStream in, PackagesTokenData packagesTokenData) 612 throws IOException { 613 final ProtoInputStream proto = new ProtoInputStream(in); 614 while (true) { 615 switch (proto.nextField()) { 616 case (int) ObfuscatedPackagesProto.COUNTER: 617 packagesTokenData.counter = proto.readInt(ObfuscatedPackagesProto.COUNTER); 618 break; 619 case (int) ObfuscatedPackagesProto.PACKAGES_MAP: 620 final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP); 621 loadPackagesMap(proto, packagesTokenData.tokensToPackagesMap); 622 proto.end(token); 623 break; 624 case ProtoInputStream.NO_MORE_FIELDS: 625 return; 626 } 627 } 628 } 629 630 /** 631 * Writes the packages mapping data to a ProtoBuf file. 632 * 633 * @param out the output stream to which to write the mappings. 634 * @param packagesTokenData the packages data object holding the data to write. 635 */ writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData)636 static void writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData) 637 throws IOException, IllegalArgumentException { 638 final ProtoOutputStream proto = new ProtoOutputStream(out); 639 proto.write(ObfuscatedPackagesProto.COUNTER, packagesTokenData.counter); 640 641 final int mapSize = packagesTokenData.tokensToPackagesMap.size(); 642 for (int i = 0; i < mapSize; i++) { 643 final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP); 644 int packageToken = packagesTokenData.tokensToPackagesMap.keyAt(i); 645 proto.write(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN, packageToken + 1); 646 647 final ArrayList<String> strings = packagesTokenData.tokensToPackagesMap.valueAt(i); 648 final int listSize = strings.size(); 649 for (int j = 0; j < listSize; j++) { 650 proto.write(ObfuscatedPackagesProto.PackagesMap.STRINGS, strings.get(j)); 651 } 652 proto.end(token); 653 } 654 655 proto.flush(); 656 } 657 658 /***** Read/Write pending events logic. *****/ 659 parsePendingEvent(ProtoInputStream proto)660 private static UsageEvents.Event parsePendingEvent(ProtoInputStream proto) throws IOException { 661 final UsageEvents.Event event = new UsageEvents.Event(); 662 while (true) { 663 switch (proto.nextField()) { 664 case (int) PendingEventProto.PACKAGE_NAME: 665 event.mPackage = proto.readString(PendingEventProto.PACKAGE_NAME); 666 break; 667 case (int) PendingEventProto.CLASS_NAME: 668 event.mClass = proto.readString(PendingEventProto.CLASS_NAME); 669 break; 670 case (int) PendingEventProto.TIME_MS: 671 event.mTimeStamp = proto.readLong(PendingEventProto.TIME_MS); 672 break; 673 case (int) PendingEventProto.FLAGS: 674 event.mFlags = proto.readInt(PendingEventProto.FLAGS); 675 break; 676 case (int) PendingEventProto.TYPE: 677 event.mEventType = proto.readInt(PendingEventProto.TYPE); 678 break; 679 case (int) PendingEventProto.CONFIG: 680 event.mConfiguration = new Configuration(); 681 event.mConfiguration.readFromProto(proto, PendingEventProto.CONFIG); 682 break; 683 case (int) PendingEventProto.SHORTCUT_ID: 684 event.mShortcutId = proto.readString(PendingEventProto.SHORTCUT_ID); 685 break; 686 case (int) PendingEventProto.STANDBY_BUCKET: 687 event.mBucketAndReason = proto.readInt(PendingEventProto.STANDBY_BUCKET); 688 break; 689 case (int) PendingEventProto.NOTIFICATION_CHANNEL_ID: 690 event.mNotificationChannelId = proto.readString( 691 PendingEventProto.NOTIFICATION_CHANNEL_ID); 692 break; 693 case (int) PendingEventProto.INSTANCE_ID: 694 event.mInstanceId = proto.readInt(PendingEventProto.INSTANCE_ID); 695 break; 696 case (int) PendingEventProto.TASK_ROOT_PACKAGE: 697 event.mTaskRootPackage = proto.readString(PendingEventProto.TASK_ROOT_PACKAGE); 698 break; 699 case (int) PendingEventProto.TASK_ROOT_CLASS: 700 event.mTaskRootClass = proto.readString(PendingEventProto.TASK_ROOT_CLASS); 701 break; 702 case ProtoInputStream.NO_MORE_FIELDS: 703 // Handle default values for certain events types 704 switch (event.mEventType) { 705 case UsageEvents.Event.CONFIGURATION_CHANGE: 706 if (event.mConfiguration == null) { 707 event.mConfiguration = new Configuration(); 708 } 709 break; 710 case UsageEvents.Event.SHORTCUT_INVOCATION: 711 if (event.mShortcutId == null) { 712 event.mShortcutId = ""; 713 } 714 break; 715 case UsageEvents.Event.NOTIFICATION_INTERRUPTION: 716 if (event.mNotificationChannelId == null) { 717 event.mNotificationChannelId = ""; 718 } 719 break; 720 } 721 return event.mPackage == null ? null : event; 722 } 723 } 724 } 725 726 /** 727 * Populates the list of pending events from the input stream given. 728 * 729 * @param in the input stream from which to read the pending events. 730 * @param events the list of pending events to populate. 731 */ readPendingEvents(InputStream in, LinkedList<UsageEvents.Event> events)732 static void readPendingEvents(InputStream in, LinkedList<UsageEvents.Event> events) 733 throws IOException { 734 final ProtoInputStream proto = new ProtoInputStream(in); 735 while (true) { 736 switch (proto.nextField()) { 737 case (int) IntervalStatsObfuscatedProto.PENDING_EVENTS: 738 try { 739 final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS); 740 UsageEvents.Event event = parsePendingEvent(proto); 741 proto.end(token); 742 if (event != null) { 743 events.add(event); 744 } 745 } catch (IOException e) { 746 Slog.e(TAG, "Unable to parse some pending events from proto.", e); 747 } 748 break; 749 case ProtoInputStream.NO_MORE_FIELDS: 750 return; 751 } 752 } 753 } 754 writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event)755 private static void writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event) 756 throws IllegalArgumentException { 757 proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage); 758 if (event.mClass != null) { 759 proto.write(PendingEventProto.CLASS_NAME, event.mClass); 760 } 761 proto.write(PendingEventProto.TIME_MS, event.mTimeStamp); 762 proto.write(PendingEventProto.FLAGS, event.mFlags); 763 proto.write(PendingEventProto.TYPE, event.mEventType); 764 proto.write(PendingEventProto.INSTANCE_ID, event.mInstanceId); 765 if (event.mTaskRootPackage != null) { 766 proto.write(PendingEventProto.TASK_ROOT_PACKAGE, event.mTaskRootPackage); 767 } 768 if (event.mTaskRootClass != null) { 769 proto.write(PendingEventProto.TASK_ROOT_CLASS, event.mTaskRootClass); 770 } 771 switch (event.mEventType) { 772 case UsageEvents.Event.CONFIGURATION_CHANGE: 773 if (event.mConfiguration != null) { 774 event.mConfiguration.dumpDebug(proto, PendingEventProto.CONFIG); 775 } 776 break; 777 case UsageEvents.Event.STANDBY_BUCKET_CHANGED: 778 if (event.mBucketAndReason != 0) { 779 proto.write(PendingEventProto.STANDBY_BUCKET, event.mBucketAndReason); 780 } 781 break; 782 case UsageEvents.Event.SHORTCUT_INVOCATION: 783 if (event.mShortcutId != null) { 784 proto.write(PendingEventProto.SHORTCUT_ID, event.mShortcutId); 785 } 786 break; 787 case UsageEvents.Event.NOTIFICATION_INTERRUPTION: 788 if (event.mNotificationChannelId != null) { 789 proto.write(PendingEventProto.NOTIFICATION_CHANNEL_ID, 790 event.mNotificationChannelId); 791 } 792 break; 793 } 794 } 795 796 /** 797 * Writes the pending events to a ProtoBuf file. 798 * 799 * @param out the output stream to which to write the pending events. 800 * @param events the list of pending events. 801 */ writePendingEvents(OutputStream out, LinkedList<UsageEvents.Event> events)802 static void writePendingEvents(OutputStream out, LinkedList<UsageEvents.Event> events) 803 throws IOException, IllegalArgumentException { 804 final ProtoOutputStream proto = new ProtoOutputStream(out); 805 final int eventCount = events.size(); 806 for (int i = 0; i < eventCount; i++) { 807 try { 808 final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS); 809 writePendingEvent(proto, events.get(i)); 810 proto.end(token); 811 } catch (IllegalArgumentException e) { 812 Slog.e(TAG, "Unable to write some pending events to proto.", e); 813 } 814 } 815 proto.flush(); 816 } 817 parseGlobalComponentUsage(ProtoInputStream proto)818 private static Pair<String, Long> parseGlobalComponentUsage(ProtoInputStream proto) 819 throws IOException { 820 String packageName = ""; 821 long time = 0; 822 while (true) { 823 switch (proto.nextField()) { 824 case (int) IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME: 825 packageName = proto.readString( 826 IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME); 827 break; 828 case (int) IntervalStatsObfuscatedProto.PackageUsage.TIME_MS: 829 time = proto.readLong(IntervalStatsObfuscatedProto.PackageUsage.TIME_MS); 830 break; 831 case ProtoInputStream.NO_MORE_FIELDS: 832 return new Pair<>(packageName, time); 833 } 834 } 835 } 836 837 /** 838 * Populates the map of latest package usage from the input stream given. 839 * 840 * @param in the input stream from which to read the package usage. 841 * @param lastTimeComponentUsedGlobal the map of package's global component usage to populate. 842 */ readGlobalComponentUsage(InputStream in, Map<String, Long> lastTimeComponentUsedGlobal)843 static void readGlobalComponentUsage(InputStream in, 844 Map<String, Long> lastTimeComponentUsedGlobal) throws IOException { 845 final ProtoInputStream proto = new ProtoInputStream(in); 846 while (true) { 847 switch (proto.nextField()) { 848 case (int) IntervalStatsObfuscatedProto.PACKAGE_USAGE: 849 try { 850 final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGE_USAGE); 851 final Pair<String, Long> usage = parseGlobalComponentUsage(proto); 852 proto.end(token); 853 if (!usage.first.isEmpty() && usage.second > 0) { 854 lastTimeComponentUsedGlobal.put(usage.first, usage.second); 855 } 856 } catch (IOException e) { 857 Slog.e(TAG, "Unable to parse some package usage from proto.", e); 858 } 859 break; 860 case ProtoInputStream.NO_MORE_FIELDS: 861 return; 862 } 863 } 864 } 865 866 /** 867 * Writes the user-agnostic last time package usage to a ProtoBuf file. 868 * 869 * @param out the output stream to which to write the package usage 870 * @param lastTimeComponentUsedGlobal the map storing the global component usage of packages 871 */ writeGlobalComponentUsage(OutputStream out, Map<String, Long> lastTimeComponentUsedGlobal)872 static void writeGlobalComponentUsage(OutputStream out, 873 Map<String, Long> lastTimeComponentUsedGlobal) { 874 final ProtoOutputStream proto = new ProtoOutputStream(out); 875 final Map.Entry<String, Long>[] entries = 876 (Map.Entry<String, Long>[]) lastTimeComponentUsedGlobal.entrySet().toArray(); 877 final int size = entries.length; 878 for (int i = 0; i < size; ++i) { 879 if (entries[i].getValue() <= 0) continue; 880 final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGE_USAGE); 881 proto.write(IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME, 882 entries[i].getKey()); 883 proto.write(IntervalStatsObfuscatedProto.PackageUsage.TIME_MS, entries[i].getValue()); 884 proto.end(token); 885 } 886 } 887 } 888