1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.usage;
17 
18 import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE;
19 
20 import static junit.framework.Assert.assertEquals;
21 import static junit.framework.Assert.assertTrue;
22 import static junit.framework.Assert.fail;
23 
24 import android.app.usage.UsageEvents;
25 import android.content.res.Configuration;
26 import android.test.suitebuilder.annotation.SmallTest;
27 
28 import androidx.test.runner.AndroidJUnit4;
29 
30 import com.android.internal.util.ArrayUtils;
31 
32 import org.junit.Test;
33 import org.junit.runner.RunWith;
34 
35 import java.lang.reflect.Field;
36 import java.util.Locale;
37 
38 @RunWith(AndroidJUnit4.class)
39 @SmallTest
40 public class IntervalStatsTests {
41     private static final int NUMBER_OF_PACKAGES = 7;
42     private static final int NUMBER_OF_EVENTS_PER_PACKAGE = 200;
43     private static final int NUMBER_OF_EVENTS = NUMBER_OF_PACKAGES * NUMBER_OF_EVENTS_PER_PACKAGE;
44 
45     private long mEndTime = 0;
46 
populateIntervalStats(IntervalStats intervalStats)47     private void populateIntervalStats(IntervalStats intervalStats) {
48         final int timeProgression = 23;
49         long time = System.currentTimeMillis() - (NUMBER_OF_EVENTS * timeProgression);
50 
51         intervalStats.majorVersion = 7;
52         intervalStats.minorVersion = 8;
53         intervalStats.beginTime = time;
54         intervalStats.interactiveTracker.count = 2;
55         intervalStats.interactiveTracker.duration = 111111;
56         intervalStats.nonInteractiveTracker.count = 3;
57         intervalStats.nonInteractiveTracker.duration = 222222;
58         intervalStats.keyguardShownTracker.count = 4;
59         intervalStats.keyguardShownTracker.duration = 333333;
60         intervalStats.keyguardHiddenTracker.count = 5;
61         intervalStats.keyguardHiddenTracker.duration = 4444444;
62 
63         for (int i = 0; i < NUMBER_OF_EVENTS; i++) {
64             UsageEvents.Event event = new UsageEvents.Event();
65             final int packageInt = ((i / 3) % NUMBER_OF_PACKAGES); // clusters of 3 events
66             event.mPackage = "fake.package.name" + packageInt;
67             if (packageInt == 3) {
68                 // Third app is an instant app
69                 event.mFlags |= UsageEvents.Event.FLAG_IS_PACKAGE_INSTANT_APP;
70             }
71 
72             final int instanceId = i % 11;
73             event.mClass = ".fake.class.name" + instanceId;
74             event.mTimeStamp = time;
75             event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
76             event.mInstanceId = instanceId;
77 
78 
79             final int rootPackageInt = (i % 5); // 5 "apps" start each task
80             event.mTaskRootPackage = "fake.package.name" + rootPackageInt;
81 
82             final int rootClassInt = i % 6;
83             event.mTaskRootClass = ".fake.class.name" + rootClassInt;
84 
85             switch (event.mEventType) {
86                 case UsageEvents.Event.CONFIGURATION_CHANGE:
87                     event.mConfiguration = new Configuration(); //empty config
88                     break;
89                 case UsageEvents.Event.SHORTCUT_INVOCATION:
90                     event.mShortcutId = "shortcut" + (i % 8); //"random" shortcut
91                     break;
92                 case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
93                     //"random" bucket and reason
94                     event.mBucketAndReason = (((i % 5 + 1) * 10) << 16) & (i % 5 + 1) << 8;
95                     break;
96                 case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
97                     event.mNotificationChannelId = "channel" + (i % 5); //"random" channel
98                     break;
99                 case UsageEvents.Event.LOCUS_ID_SET:
100                     event.mLocusId = "locus" + (i % 7); //"random" locus
101                     break;
102             }
103 
104             intervalStats.addEvent(event);
105             intervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
106                     event.mInstanceId);
107 
108             time += timeProgression; // Arbitrary progression of time
109         }
110         mEndTime = time;
111 
112         final Configuration config1 = new Configuration();
113         config1.fontScale = 3.3f;
114         config1.mcc = 4;
115         intervalStats.getOrCreateConfigurationStats(config1);
116 
117         final Configuration config2 = new Configuration();
118         config2.mnc = 5;
119         config2.setLocale(new Locale("en", "US"));
120         intervalStats.getOrCreateConfigurationStats(config2);
121 
122         intervalStats.activeConfiguration = config2;
123     }
124 
125     @Test
testObfuscation()126     public void testObfuscation() {
127         final IntervalStats intervalStats = new IntervalStats();
128         populateIntervalStats(intervalStats);
129 
130         final PackagesTokenData packagesTokenData = new PackagesTokenData();
131         intervalStats.obfuscateData(packagesTokenData);
132 
133         // data is populated with 7 different "apps"
134         assertEquals(packagesTokenData.tokensToPackagesMap.size(), NUMBER_OF_PACKAGES);
135         assertEquals(packagesTokenData.packagesToTokensMap.size(), NUMBER_OF_PACKAGES);
136         assertEquals(packagesTokenData.counter, NUMBER_OF_PACKAGES + 1);
137 
138         assertEquals(intervalStats.events.size(), NUMBER_OF_EVENTS);
139         assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
140     }
141 
142     @Test
testDeobfuscation()143     public void testDeobfuscation() {
144         final IntervalStats intervalStats = new IntervalStats();
145         populateIntervalStats(intervalStats);
146 
147         final PackagesTokenData packagesTokenData = new PackagesTokenData();
148         intervalStats.obfuscateData(packagesTokenData);
149         intervalStats.deobfuscateData(packagesTokenData);
150 
151         // ensure deobfuscation doesn't update any of the mappings data
152         assertEquals(packagesTokenData.tokensToPackagesMap.size(), NUMBER_OF_PACKAGES);
153         assertEquals(packagesTokenData.packagesToTokensMap.size(), NUMBER_OF_PACKAGES);
154         assertEquals(packagesTokenData.counter, NUMBER_OF_PACKAGES + 1);
155 
156         // ensure deobfuscation didn't remove any events or usage stats
157         assertEquals(intervalStats.events.size(), NUMBER_OF_EVENTS);
158         assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
159     }
160 
161     @Test
testBadDataOnDeobfuscation()162     public void testBadDataOnDeobfuscation() {
163         final IntervalStats intervalStats = new IntervalStats();
164         populateIntervalStats(intervalStats);
165 
166         final PackagesTokenData packagesTokenData = new PackagesTokenData();
167         intervalStats.obfuscateData(packagesTokenData);
168         intervalStats.packageStats.clear();
169 
170         // remove the mapping for token 2
171         packagesTokenData.tokensToPackagesMap.remove(2);
172 
173         intervalStats.deobfuscateData(packagesTokenData);
174         // deobfuscation should have removed all events mapped to package token 2
175         assertEquals(intervalStats.events.size(),
176                 NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE - 1);
177         assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES - 1);
178     }
179 
180     @Test
testBadPackageDataOnDeobfuscation()181     public void testBadPackageDataOnDeobfuscation() {
182         final IntervalStats intervalStats = new IntervalStats();
183         populateIntervalStats(intervalStats);
184 
185         final PackagesTokenData packagesTokenData = new PackagesTokenData();
186         intervalStats.obfuscateData(packagesTokenData);
187         intervalStats.packageStats.clear();
188 
189         // remove mapping number 2 within package 3 (random)
190         packagesTokenData.tokensToPackagesMap.valueAt(3).remove(2);
191 
192         intervalStats.deobfuscateData(packagesTokenData);
193         // deobfuscation should not have removed all events for a package - however, it's possible
194         // that some events were removed because of how shortcut and notification events are handled
195         assertTrue(intervalStats.events.size() > NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE);
196         assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
197     }
198 
199     // All fields in this list are defined in IntervalStats and persisted - please ensure they're
200     // defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto
201     private static final String[] INTERVALSTATS_PERSISTED_FIELDS = {"beginTime", "endTime",
202             "mStringCache", "majorVersion", "minorVersion", "interactiveTracker",
203             "nonInteractiveTracker", "keyguardShownTracker", "keyguardHiddenTracker",
204             "packageStats", "configurations", "activeConfiguration", "events"};
205     // All fields in this list are defined in IntervalStats but not persisted
206     private static final String[] INTERVALSTATS_IGNORED_FIELDS = {"lastTimeSaved",
207             "packageStatsObfuscated", "CURRENT_MAJOR_VERSION", "CURRENT_MINOR_VERSION", "TAG"};
208 
209     @Test
testIntervalStatsFieldsAreKnown()210     public void testIntervalStatsFieldsAreKnown() {
211         final IntervalStats stats = new IntervalStats();
212         final Field[] fields = stats.getClass().getDeclaredFields();
213         for (Field field : fields) {
214             if (!(ArrayUtils.contains(INTERVALSTATS_PERSISTED_FIELDS, field.getName())
215                     || ArrayUtils.contains(INTERVALSTATS_IGNORED_FIELDS, field.getName()))) {
216                 fail("Found an unknown field: " + field.getName() + ". Please correctly update "
217                         + "either INTERVALSTATS_PERSISTED_FIELDS or INTERVALSTATS_IGNORED_FIELDS.");
218             }
219         }
220     }
221 }
222