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