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