• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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