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