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