1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.server.usage; 18 19 import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE; 20 21 import static junit.framework.TestCase.fail; 22 23 import static org.testng.Assert.assertEquals; 24 import static org.testng.Assert.assertFalse; 25 26 import android.app.usage.TimeSparseArray; 27 import android.app.usage.UsageEvents.Event; 28 import android.app.usage.UsageStats; 29 import android.app.usage.UsageStatsManager; 30 import android.content.Context; 31 import android.content.res.Configuration; 32 import android.test.suitebuilder.annotation.SmallTest; 33 import android.util.AtomicFile; 34 35 import androidx.test.InstrumentationRegistry; 36 import androidx.test.runner.AndroidJUnit4; 37 38 import org.junit.Before; 39 import org.junit.Ignore; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 43 import java.io.File; 44 import java.io.FileOutputStream; 45 import java.io.IOException; 46 import java.util.List; 47 import java.util.Locale; 48 import java.util.Set; 49 50 @RunWith(AndroidJUnit4.class) 51 @SmallTest 52 public class UsageStatsDatabaseTest { 53 54 private static final int MAX_TESTED_VERSION = 5; 55 private static final int OLDER_VERSION_MAX_EVENT_TYPE = 29; 56 protected Context mContext; 57 private UsageStatsDatabase mUsageStatsDatabase; 58 private File mTestDir; 59 60 private IntervalStats mIntervalStats = new IntervalStats(); 61 private long mEndTime = 0; 62 63 // Key under which the payload blob is stored 64 // same as UsageStatsBackupHelper.KEY_USAGE_STATS 65 static final String KEY_USAGE_STATS = "usage_stats"; 66 67 private static final UsageStatsDatabase.StatCombiner<IntervalStats> mIntervalStatsVerifier = 68 new UsageStatsDatabase.StatCombiner<IntervalStats>() { 69 @Override 70 public void combine(IntervalStats stats, boolean mutable, 71 List<IntervalStats> accResult) { 72 accResult.add(stats); 73 } 74 }; 75 76 @Before setUp()77 public void setUp() { 78 mContext = InstrumentationRegistry.getTargetContext(); 79 mTestDir = new File(mContext.getFilesDir(), "UsageStatsDatabaseTest"); 80 mUsageStatsDatabase = new UsageStatsDatabase(mTestDir); 81 mUsageStatsDatabase.readMappingsLocked(); 82 mUsageStatsDatabase.init(1); 83 populateIntervalStats(MAX_TESTED_VERSION); 84 clearUsageStatsFiles(); 85 } 86 87 /** 88 * A debugging utility for viewing the files currently in the test directory 89 */ clearUsageStatsFiles()90 private void clearUsageStatsFiles() { 91 File[] intervalDirs = mTestDir.listFiles(); 92 for (File intervalDir : intervalDirs) { 93 if (intervalDir.isDirectory()) { 94 File[] usageFiles = intervalDir.listFiles(); 95 for (File f : usageFiles) { 96 f.delete(); 97 } 98 } else { 99 intervalDir.delete(); 100 } 101 } 102 } 103 104 /** 105 * A debugging utility for viewing the files currently in the test directory 106 */ dumpUsageStatsFiles()107 private String dumpUsageStatsFiles() { 108 StringBuilder sb = new StringBuilder(); 109 File[] intervalDirs = mTestDir.listFiles(); 110 for (File intervalDir : intervalDirs) { 111 if (intervalDir.isDirectory()) { 112 File[] usageFiles = intervalDir.listFiles(); 113 for (File f : usageFiles) { 114 sb.append(f.toString()); 115 } 116 } 117 } 118 return sb.toString(); 119 } 120 populateIntervalStats(int minVersion)121 private void populateIntervalStats(int minVersion) { 122 final int numberOfEvents = 3000; 123 final int timeProgression = 23; 124 long time = System.currentTimeMillis() - (numberOfEvents*timeProgression); 125 mIntervalStats = new IntervalStats(); 126 127 mIntervalStats.majorVersion = 7; 128 mIntervalStats.minorVersion = 8; 129 mIntervalStats.beginTime = time - 1; 130 mIntervalStats.interactiveTracker.count = 2; 131 mIntervalStats.interactiveTracker.duration = 111111; 132 mIntervalStats.nonInteractiveTracker.count = 3; 133 mIntervalStats.nonInteractiveTracker.duration = 222222; 134 mIntervalStats.keyguardShownTracker.count = 4; 135 mIntervalStats.keyguardShownTracker.duration = 333333; 136 mIntervalStats.keyguardHiddenTracker.count = 5; 137 mIntervalStats.keyguardHiddenTracker.duration = 4444444; 138 139 for (int i = 0; i < numberOfEvents; i++) { 140 Event event = new Event(); 141 final int packageInt = ((i / 3) % 7); //clusters of 3 events from 7 "apps" 142 event.mPackage = "fake.package.name" + packageInt; 143 if (packageInt == 3) { 144 // Third app is an instant app 145 event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP; 146 } 147 148 final int instanceId = i % 11; 149 event.mClass = ".fake.class.name" + instanceId; 150 event.mTimeStamp = time; 151 event.mInstanceId = instanceId; 152 153 int maxEventType = (minVersion < 5) ? OLDER_VERSION_MAX_EVENT_TYPE : MAX_EVENT_TYPE; 154 event.mEventType = i % (maxEventType + 1); //"random" event type 155 156 157 158 final int rootPackageInt = (i % 5); // 5 "apps" start each task 159 event.mTaskRootPackage = "fake.package.name" + rootPackageInt; 160 161 final int rootClassInt = i % 6; 162 event.mTaskRootClass = ".fake.class.name" + rootClassInt; 163 164 switch (event.mEventType) { 165 case Event.CONFIGURATION_CHANGE: 166 //empty config, 167 event.mConfiguration = new Configuration(); 168 break; 169 case Event.SHORTCUT_INVOCATION: 170 //"random" shortcut 171 event.mShortcutId = "shortcut" + (i % 8); 172 break; 173 case Event.STANDBY_BUCKET_CHANGED: 174 //"random" bucket and reason 175 event.mBucketAndReason = (((i % 5 + 1) * 10) << 16) & (i % 5 + 1) << 8; 176 break; 177 case Event.NOTIFICATION_INTERRUPTION: 178 //"random" channel 179 event.mNotificationChannelId = "channel" + (i % 5); 180 break; 181 case Event.LOCUS_ID_SET: 182 event.mLocusId = "locus" + (i % 7); //"random" locus 183 break; 184 } 185 186 mIntervalStats.addEvent(event); 187 mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType, 188 event.mInstanceId); 189 190 time += timeProgression; // Arbitrary progression of time 191 } 192 mEndTime = time; 193 194 Configuration config1 = new Configuration(); 195 config1.fontScale = 3.3f; 196 config1.mcc = 4; 197 mIntervalStats.getOrCreateConfigurationStats(config1); 198 199 Configuration config2 = new Configuration(); 200 config2.mnc = 5; 201 config2.setLocale(new Locale("en", "US")); 202 mIntervalStats.getOrCreateConfigurationStats(config2); 203 204 Configuration config3 = new Configuration(); 205 config3.touchscreen = 6; 206 config3.keyboard = 7; 207 mIntervalStats.getOrCreateConfigurationStats(config3); 208 209 Configuration config4 = new Configuration(); 210 config4.keyboardHidden = 8; 211 config4.hardKeyboardHidden = 9; 212 mIntervalStats.getOrCreateConfigurationStats(config4); 213 214 Configuration config5 = new Configuration(); 215 config5.navigation = 10; 216 config5.navigationHidden = 11; 217 mIntervalStats.getOrCreateConfigurationStats(config5); 218 219 Configuration config6 = new Configuration(); 220 config6.orientation = 12; 221 //Ignore screen layout, it's determined by locale 222 mIntervalStats.getOrCreateConfigurationStats(config6); 223 224 Configuration config7 = new Configuration(); 225 config7.colorMode = 14; 226 config7.uiMode = 15; 227 mIntervalStats.getOrCreateConfigurationStats(config7); 228 229 Configuration config8 = new Configuration(); 230 config8.screenWidthDp = 16; 231 config8.screenHeightDp = 17; 232 mIntervalStats.getOrCreateConfigurationStats(config8); 233 234 Configuration config9 = new Configuration(); 235 config9.smallestScreenWidthDp = 18; 236 config9.densityDpi = 19; 237 mIntervalStats.getOrCreateConfigurationStats(config9); 238 239 Configuration config10 = new Configuration(); 240 final Locale locale10 = new Locale.Builder() 241 .setLocale(new Locale("zh", "CN")) 242 .setScript("Hans") 243 .build(); 244 config10.setLocale(locale10); 245 mIntervalStats.getOrCreateConfigurationStats(config10); 246 247 Configuration config11 = new Configuration(); 248 final Locale locale11 = new Locale.Builder() 249 .setLocale(new Locale("zh", "CN")) 250 .setScript("Hant") 251 .build(); 252 config11.setLocale(locale11); 253 mIntervalStats.getOrCreateConfigurationStats(config11); 254 255 mIntervalStats.activeConfiguration = config9; 256 } 257 compareUsageStats(UsageStats us1, UsageStats us2)258 void compareUsageStats(UsageStats us1, UsageStats us2) { 259 assertEquals(us1.mPackageName, us2.mPackageName); 260 // mBeginTimeStamp is based on the enclosing IntervalStats, don't bother checking 261 // mEndTimeStamp is based on the enclosing IntervalStats, don't bother checking 262 assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed); 263 assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible); 264 assertEquals(us1.mLastTimeComponentUsed, us2.mLastTimeComponentUsed); 265 assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground); 266 assertEquals(us1.mTotalTimeVisible, us2.mTotalTimeVisible); 267 assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed); 268 assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed); 269 // mLaunchCount not persisted, so skipped 270 assertEquals(us1.mAppLaunchCount, us2.mAppLaunchCount); 271 assertEquals(us1.mChooserCounts, us2.mChooserCounts); 272 } 273 compareUsageEvent(Event e1, Event e2, int debugId, int minVersion)274 void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) { 275 switch (minVersion) { 276 case 5: // test fields added in version 5 277 assertEquals(e1.mPackageToken, e2.mPackageToken, "Usage event " + debugId); 278 assertEquals(e1.mClassToken, e2.mClassToken, "Usage event " + debugId); 279 assertEquals(e1.mTaskRootPackageToken, e2.mTaskRootPackageToken, 280 "Usage event " + debugId); 281 assertEquals(e1.mTaskRootClassToken, e2.mTaskRootClassToken, 282 "Usage event " + debugId); 283 switch (e1.mEventType) { 284 case Event.SHORTCUT_INVOCATION: 285 assertEquals(e1.mShortcutIdToken, e2.mShortcutIdToken, 286 "Usage event " + debugId); 287 break; 288 case Event.NOTIFICATION_INTERRUPTION: 289 assertEquals(e1.mNotificationChannelIdToken, e2.mNotificationChannelIdToken, 290 "Usage event " + debugId); 291 break; 292 case Event.LOCUS_ID_SET: 293 assertEquals(e1.mLocusIdToken, e2.mLocusIdToken, 294 "Usage event " + debugId); 295 break; 296 } 297 // fallthrough 298 case 4: // test fields added in version 4 299 assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId); 300 assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId); 301 assertEquals(e1.mTaskRootClass, e2.mTaskRootClass, "Usage event " + debugId); 302 // fallthrough 303 default: 304 assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId); 305 assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId); 306 assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId); 307 assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId); 308 switch (e1.mEventType) { 309 case Event.CONFIGURATION_CHANGE: 310 assertEquals(e1.mConfiguration, e2.mConfiguration, 311 "Usage event " + debugId + e2.mConfiguration.toString()); 312 break; 313 case Event.SHORTCUT_INVOCATION: 314 assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId); 315 break; 316 case Event.STANDBY_BUCKET_CHANGED: 317 assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, 318 "Usage event " + debugId); 319 break; 320 case Event.NOTIFICATION_INTERRUPTION: 321 assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId, 322 "Usage event " + debugId); 323 break; 324 } 325 assertEquals(e1.mFlags, e2.mFlags); 326 } 327 } 328 compareIntervalStats(IntervalStats stats1, IntervalStats stats2, int minVersion)329 void compareIntervalStats(IntervalStats stats1, IntervalStats stats2, int minVersion) { 330 assertEquals(stats1.majorVersion, stats2.majorVersion); 331 assertEquals(stats1.minorVersion, stats2.minorVersion); 332 assertEquals(stats1.beginTime, stats2.beginTime); 333 assertEquals(stats1.endTime, stats2.endTime); 334 assertEquals(stats1.interactiveTracker.count, stats2.interactiveTracker.count); 335 assertEquals(stats1.interactiveTracker.duration, stats2.interactiveTracker.duration); 336 assertEquals(stats1.nonInteractiveTracker.count, stats2.nonInteractiveTracker.count); 337 assertEquals(stats1.nonInteractiveTracker.duration, stats2.nonInteractiveTracker.duration); 338 assertEquals(stats1.keyguardShownTracker.count, stats2.keyguardShownTracker.count); 339 assertEquals(stats1.keyguardShownTracker.duration, stats2.keyguardShownTracker.duration); 340 assertEquals(stats1.keyguardHiddenTracker.count, stats2.keyguardHiddenTracker.count); 341 assertEquals(stats1.keyguardHiddenTracker.duration, stats2.keyguardHiddenTracker.duration); 342 343 String[] usageKey1 = stats1.packageStats.keySet().toArray(new String[0]); 344 String[] usageKey2 = stats2.packageStats.keySet().toArray(new String[0]); 345 for (int i = 0; i < usageKey1.length; i++) { 346 UsageStats usageStats1 = stats1.packageStats.get(usageKey1[i]); 347 UsageStats usageStats2 = stats2.packageStats.get(usageKey2[i]); 348 compareUsageStats(usageStats1, usageStats2); 349 } 350 351 assertEquals(stats1.configurations.size(), stats2.configurations.size()); 352 Configuration[] configSet1 = stats1.configurations.keySet().toArray(new Configuration[0]); 353 for (int i = 0; i < configSet1.length; i++) { 354 if (!stats2.configurations.containsKey(configSet1[i])) { 355 Configuration[] configSet2 = stats2.configurations.keySet().toArray( 356 new Configuration[0]); 357 String debugInfo = ""; 358 for (Configuration c : configSet1) { 359 debugInfo += c.toString() + "\n"; 360 } 361 debugInfo += "\n"; 362 for (Configuration c : configSet2) { 363 debugInfo += c.toString() + "\n"; 364 } 365 fail("Config " + configSet1[i].toString() 366 + " not found in deserialized IntervalStat\n" + debugInfo); 367 } 368 } 369 assertEquals(stats1.activeConfiguration, stats2.activeConfiguration); 370 assertEquals(stats1.events.size(), stats2.events.size()); 371 for (int i = 0; i < stats1.events.size(); i++) { 372 compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i, minVersion); 373 } 374 } 375 376 /** 377 * Runs the Write Read test. 378 * Will write the generated IntervalStat to disk, read it from disk and compare the two 379 */ runWriteReadTest(int interval)380 void runWriteReadTest(int interval) throws IOException { 381 mUsageStatsDatabase.putUsageStats(interval, mIntervalStats); 382 List<IntervalStats> stats = mUsageStatsDatabase.queryUsageStats(interval, 0, mEndTime, 383 mIntervalStatsVerifier); 384 385 assertEquals(1, stats.size()); 386 compareIntervalStats(mIntervalStats, stats.get(0), MAX_TESTED_VERSION); 387 } 388 389 /** 390 * Demonstrate that IntervalStats can be serialized and deserialized from disk without loss of 391 * relevant data. 392 */ 393 @Test testWriteRead()394 public void testWriteRead() throws IOException { 395 runWriteReadTest(UsageStatsManager.INTERVAL_DAILY); 396 runWriteReadTest(UsageStatsManager.INTERVAL_WEEKLY); 397 runWriteReadTest(UsageStatsManager.INTERVAL_MONTHLY); 398 runWriteReadTest(UsageStatsManager.INTERVAL_YEARLY); 399 } 400 401 /** 402 * Runs the Version Change tests. 403 * Will write the generated IntervalStat to disk in one version format, "upgrade" to another 404 * version and read the automatically upgraded files on disk in the new file format. 405 */ runVersionChangeTest(int oldVersion, int newVersion, int interval)406 void runVersionChangeTest(int oldVersion, int newVersion, int interval) throws IOException { 407 populateIntervalStats(oldVersion); 408 // Write IntervalStats to disk in old version format 409 UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir, oldVersion); 410 prevDB.readMappingsLocked(); 411 prevDB.init(1); 412 prevDB.putUsageStats(interval, mIntervalStats); 413 if (oldVersion >= 5) { 414 prevDB.writeMappingsLocked(); 415 } 416 417 // Simulate an upgrade to a new version and read from the disk 418 UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir, newVersion); 419 newDB.readMappingsLocked(); 420 newDB.init(mEndTime); 421 List<IntervalStats> stats = newDB.queryUsageStats(interval, 0, mEndTime, 422 mIntervalStatsVerifier); 423 424 assertEquals(1, stats.size()); 425 426 final int minVersion = oldVersion < newVersion ? oldVersion : newVersion; 427 // The written and read IntervalStats should match 428 compareIntervalStats(mIntervalStats, stats.get(0), minVersion); 429 } 430 431 /** 432 * Runs the Backup and Restore tests. 433 * Will write the generated IntervalStat to a database and create a backup in the specified 434 * version's format. The database will then be restored from the blob and the restored 435 * interval stats will be compared to the generated stats. 436 */ 437 void runBackupRestoreTest(int version) throws IOException { 438 UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir); 439 prevDB.readMappingsLocked(); 440 prevDB.init(1); 441 prevDB.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats); 442 // Create a backup with a specific version 443 byte[] blob = prevDB.getBackupPayload(KEY_USAGE_STATS, version); 444 if (version >= 1 && version <= 3) { 445 assertFalse(blob != null && blob.length != 0, 446 "UsageStatsDatabase shouldn't be able to write backups as XML"); 447 return; 448 } 449 450 clearUsageStatsFiles(); 451 452 UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir); 453 newDB.readMappingsLocked(); 454 newDB.init(1); 455 // Attempt to restore the usage stats from the backup 456 newDB.applyRestoredPayload(KEY_USAGE_STATS, blob); 457 List<IntervalStats> stats = newDB.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, 0, mEndTime, 458 mIntervalStatsVerifier); 459 460 if (version > UsageStatsDatabase.BACKUP_VERSION || version < 1) { 461 assertFalse(stats != null && !stats.isEmpty(), 462 "UsageStatsDatabase shouldn't be able to restore from unknown data versions"); 463 return; 464 } 465 466 assertEquals(1, stats.size()); 467 468 // Clear non backed up data from expected IntervalStats 469 mIntervalStats.activeConfiguration = null; 470 mIntervalStats.configurations.clear(); 471 mIntervalStats.events.clear(); 472 473 // The written and read IntervalStats should match 474 compareIntervalStats(mIntervalStats, stats.get(0), version); 475 } 476 477 /** 478 * Test the version upgrade from 3 to 4 479 * 480 * Ignored - version 3 is now deprecated. 481 */ 482 @Ignore 483 @Test ignore_testVersionUpgradeFrom3to4()484 public void ignore_testVersionUpgradeFrom3to4() throws IOException { 485 runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_DAILY); 486 runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_WEEKLY); 487 runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_MONTHLY); 488 runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_YEARLY); 489 } 490 491 /** 492 * Test the version upgrade from 4 to 5 493 */ 494 @Test testVersionUpgradeFrom4to5()495 public void testVersionUpgradeFrom4to5() throws IOException { 496 runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_DAILY); 497 runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_WEEKLY); 498 runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_MONTHLY); 499 runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_YEARLY); 500 } 501 502 /** 503 * Test the version upgrade from 3 to 5 504 * 505 * Ignored - version 3 is now deprecated. 506 */ 507 @Ignore 508 @Test ignore_testVersionUpgradeFrom3to5()509 public void ignore_testVersionUpgradeFrom3to5() throws IOException { 510 runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_DAILY); 511 runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_WEEKLY); 512 runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_MONTHLY); 513 runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_YEARLY); 514 } 515 516 517 /** 518 * Test backup/restore 519 */ 520 @Test testBackupRestore()521 public void testBackupRestore() throws IOException { 522 runBackupRestoreTest(4); 523 524 // test deprecated versions 525 runBackupRestoreTest(1); 526 527 // test invalid backup versions as well 528 runBackupRestoreTest(0); 529 runBackupRestoreTest(99999); 530 } 531 532 /** 533 * Test the pruning in indexFilesLocked() that only allow up to 100 daily files, 50 weekly files 534 * , 12 monthly files, 10 yearly files. 535 */ 536 @Test testMaxFiles()537 public void testMaxFiles() throws IOException { 538 final File[] intervalDirs = new File[]{ 539 new File(mTestDir, "daily"), 540 new File(mTestDir, "weekly"), 541 new File(mTestDir, "monthly"), 542 new File(mTestDir, "yearly"), 543 }; 544 // Create 10 extra files under each interval dir. 545 final int extra = 10; 546 final int length = intervalDirs.length; 547 for (int i = 0; i < length; i++) { 548 final int numFiles = UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i] + extra; 549 for (int f = 0; f < numFiles; f++) { 550 final AtomicFile file = new AtomicFile(new File(intervalDirs[i], Long.toString(f))); 551 FileOutputStream fos = file.startWrite(); 552 fos.write(1); 553 file.finishWrite(fos); 554 } 555 } 556 // indexFilesLocked() list files under each interval dir, if number of files are more than 557 // the max allowed files for each interval type, it deletes the lowest numbered files. 558 mUsageStatsDatabase.forceIndexFiles(); 559 final int len = mUsageStatsDatabase.mSortedStatFiles.length; 560 for (int i = 0; i < len; i++) { 561 final TimeSparseArray<AtomicFile> files = mUsageStatsDatabase.mSortedStatFiles[i]; 562 // The stats file for each interval type equals to max allowed. 563 assertEquals(UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i], 564 files.size()); 565 // The highest numbered file, 566 assertEquals(UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i] + extra - 1, 567 files.keyAt(files.size() - 1)); 568 // The lowest numbered file: 569 assertEquals(extra, files.keyAt(0)); 570 } 571 } 572 compareObfuscatedData(int interval)573 private void compareObfuscatedData(int interval) throws IOException { 574 // Write IntervalStats to disk 575 UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir, 5); 576 prevDB.readMappingsLocked(); 577 prevDB.init(1); 578 prevDB.putUsageStats(interval, mIntervalStats); 579 prevDB.writeMappingsLocked(); 580 581 // Read IntervalStats from disk into a new db 582 UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir, 5); 583 newDB.readMappingsLocked(); 584 newDB.init(mEndTime); 585 List<IntervalStats> stats = newDB.queryUsageStats(interval, 0, mEndTime, 586 mIntervalStatsVerifier); 587 588 assertEquals(1, stats.size()); 589 // The written and read IntervalStats should match 590 compareIntervalStats(mIntervalStats, stats.get(0), 5); 591 } 592 593 @Test testObfuscation()594 public void testObfuscation() throws IOException { 595 compareObfuscatedData(UsageStatsManager.INTERVAL_DAILY); 596 compareObfuscatedData(UsageStatsManager.INTERVAL_WEEKLY); 597 compareObfuscatedData(UsageStatsManager.INTERVAL_MONTHLY); 598 compareObfuscatedData(UsageStatsManager.INTERVAL_YEARLY); 599 } 600 verifyPackageNotRetained(int interval)601 private void verifyPackageNotRetained(int interval) throws IOException { 602 UsageStatsDatabase db = new UsageStatsDatabase(mTestDir, 5); 603 db.readMappingsLocked(); 604 db.init(1); 605 db.putUsageStats(interval, mIntervalStats); 606 db.writeMappingsLocked(); 607 608 final String removedPackage = "fake.package.name0"; 609 // invoke handler call directly from test to remove package 610 db.onPackageRemoved(removedPackage, System.currentTimeMillis()); 611 612 List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, 613 mIntervalStatsVerifier); 614 assertEquals(1, stats.size(), 615 "Only one interval stats object should exist for the given time range."); 616 final IntervalStats stat = stats.get(0); 617 if (stat.packageStats.containsKey(removedPackage)) { 618 fail("Found removed package " + removedPackage + " in package stats."); 619 return; 620 } 621 for (int i = 0; i < stat.events.size(); i++) { 622 final Event event = stat.events.get(i); 623 if (removedPackage.equals(event.mPackage)) { 624 fail("Found an event from removed package " + removedPackage); 625 return; 626 } 627 } 628 } 629 630 @Test testPackageRetention()631 public void testPackageRetention() throws IOException { 632 verifyPackageNotRetained(UsageStatsManager.INTERVAL_DAILY); 633 verifyPackageNotRetained(UsageStatsManager.INTERVAL_WEEKLY); 634 verifyPackageNotRetained(UsageStatsManager.INTERVAL_MONTHLY); 635 verifyPackageNotRetained(UsageStatsManager.INTERVAL_YEARLY); 636 } 637 verifyPackageDataIsRemoved(UsageStatsDatabase db, int interval, String removedPackage)638 private void verifyPackageDataIsRemoved(UsageStatsDatabase db, int interval, 639 String removedPackage) { 640 List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, 641 mIntervalStatsVerifier); 642 assertEquals(1, stats.size(), 643 "Only one interval stats object should exist for the given time range."); 644 final IntervalStats stat = stats.get(0); 645 if (stat.packageStats.containsKey(removedPackage)) { 646 fail("Found removed package " + removedPackage + " in package stats."); 647 return; 648 } 649 for (int i = 0; i < stat.events.size(); i++) { 650 final Event event = stat.events.get(i); 651 if (removedPackage.equals(event.mPackage)) { 652 fail("Found an event from removed package " + removedPackage); 653 return; 654 } 655 } 656 } 657 verifyPackageDataIsNotRemoved(UsageStatsDatabase db, int interval, Set<String> installedPackages)658 private void verifyPackageDataIsNotRemoved(UsageStatsDatabase db, int interval, 659 Set<String> installedPackages) { 660 List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, 661 mIntervalStatsVerifier); 662 assertEquals(1, stats.size(), 663 "Only one interval stats object should exist for the given time range."); 664 final IntervalStats stat = stats.get(0); 665 if (!stat.packageStats.containsAll(installedPackages)) { 666 fail("Could not find some installed packages in package stats."); 667 return; 668 } 669 // attempt to find an event from each installed package 670 for (String installedPackage : installedPackages) { 671 for (int i = 0; i < stat.events.size(); i++) { 672 if (installedPackage.equals(stat.events.get(i).mPackage)) { 673 break; 674 } 675 if (i == stat.events.size() - 1) { 676 fail("Could not find any event for: " + installedPackage); 677 return; 678 } 679 } 680 } 681 } 682 683 @Test testPackageDataIsRemoved()684 public void testPackageDataIsRemoved() throws IOException { 685 UsageStatsDatabase db = new UsageStatsDatabase(mTestDir); 686 db.readMappingsLocked(); 687 db.init(1); 688 689 // write stats to disk for each interval 690 db.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats); 691 db.putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, mIntervalStats); 692 db.putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, mIntervalStats); 693 db.putUsageStats(UsageStatsManager.INTERVAL_YEARLY, mIntervalStats); 694 db.writeMappingsLocked(); 695 696 final Set<String> installedPackages = mIntervalStats.packageStats.keySet(); 697 final String removedPackage = installedPackages.iterator().next(); 698 installedPackages.remove(removedPackage); 699 700 // mimic a package uninstall 701 db.onPackageRemoved(removedPackage, System.currentTimeMillis()); 702 703 // mimic the idle prune job being triggered 704 db.pruneUninstalledPackagesData(); 705 706 // read data from disk into a new db instance 707 UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir); 708 newDB.readMappingsLocked(); 709 newDB.init(mEndTime); 710 711 // query data for each interval and ensure data for package doesn't exist 712 verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_DAILY, removedPackage); 713 verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_WEEKLY, removedPackage); 714 verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_MONTHLY, removedPackage); 715 verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_YEARLY, removedPackage); 716 717 // query data for each interval and ensure some data for installed packages exists 718 verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_DAILY, installedPackages); 719 verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_WEEKLY, installedPackages); 720 verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_MONTHLY, installedPackages); 721 verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_YEARLY, installedPackages); 722 } 723 } 724