1 /**
2  * Copyright (C) 2014 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
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 
17 package android.app.usage.cts;
18 
19 import android.app.Activity;
20 import android.app.AppOpsManager;
21 import android.app.Instrumentation;
22 import android.app.usage.UsageEvents;
23 import android.app.usage.UsageStats;
24 import android.app.usage.UsageStatsManager;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.os.Parcel;
28 import android.os.ParcelFileDescriptor;
29 import android.os.SystemClock;
30 import android.test.InstrumentationTestCase;
31 
32 import java.io.FileInputStream;
33 import java.text.MessageFormat;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Map;
37 
38 import android.util.SparseLongArray;
39 import junit.framework.AssertionFailedError;
40 import libcore.io.IoUtils;
41 import libcore.io.Streams;
42 import org.junit.Ignore;
43 
44 /**
45  * Test the UsageStats API. It is difficult to test the entire surface area
46  * of the API, as a lot of the testing depends on what data is already present
47  * on the device and for how long that data has been aggregating.
48  *
49  * These tests perform simple checks that each interval is of the correct duration,
50  * and that events do appear in the event log.
51  *
52  * Tests to add that are difficult to add now:
53  * - Invoking a device configuration change and then watching for it in the event log.
54  * - Changing the system time and verifying that all data has been correctly shifted
55  *   along with the new time.
56  * - Proper eviction of old data.
57  */
58 public class UsageStatsTest extends InstrumentationTestCase {
59     private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} " +
60             AppOpsManager.OPSTR_GET_USAGE_STATS + " {1}";
61 
62     private static final long MINUTE = 1000 * 60;
63     private static final long DAY = MINUTE * 60 * 24;
64     private static final long WEEK = 7 * DAY;
65     private static final long MONTH = 30 * DAY;
66     private static final long YEAR = 365 * DAY;
67     private static final long TIME_DIFF_THRESHOLD = 200;
68 
69     private UsageStatsManager mUsageStatsManager;
70     private String mTargetPackage;
71     private ArrayList<Activity> mStartedActivities = new ArrayList<>();
72 
73     @Override
setUp()74     protected void setUp() throws Exception {
75         mUsageStatsManager = (UsageStatsManager) getInstrumentation().getContext()
76                 .getSystemService(Context.USAGE_STATS_SERVICE);
77         mTargetPackage = getInstrumentation().getContext().getPackageName();
78 
79         setAppOpsMode("allow");
80     }
81 
82     @Override
tearDown()83     protected void tearDown() throws Exception {
84         for (Activity activity : mStartedActivities) {
85             activity.finish();
86         }
87     }
88 
assertLessThan(long left, long right)89     private static void assertLessThan(long left, long right) {
90         if (left >= right) {
91             throw new AssertionFailedError("Expected " + left + " to be less than " + right);
92         }
93     }
94 
assertLessThanOrEqual(long left, long right)95     private static void assertLessThanOrEqual(long left, long right) {
96         if (left > right) {
97             throw new AssertionFailedError("Expected " + left + " to be less than or equal to " + right);
98         }
99     }
100 
setAppOpsMode(String mode)101     private void setAppOpsMode(String mode) throws Exception {
102         final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND,
103                 getInstrumentation().getContext().getPackageName(), mode);
104         ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
105                 .executeShellCommand(command);
106         try {
107             Streams.readFully(new FileInputStream(pfd.getFileDescriptor()));
108         } finally {
109             IoUtils.closeQuietly(pfd.getFileDescriptor());
110         }
111     }
112 
launchSubActivity(Class<? extends Activity> clazz)113     private void launchSubActivity(Class<? extends Activity> clazz) {
114         final Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(0, new Intent());
115         final Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
116         getInstrumentation().addMonitor(monitor);
117         launchActivity(mTargetPackage, clazz, null);
118         mStartedActivities.add(monitor.waitForActivity());
119     }
120 
launchSubActivities(Class<? extends Activity>[] activityClasses)121     private void launchSubActivities(Class<? extends Activity>[] activityClasses) {
122         for (Class<? extends Activity> clazz : activityClasses) {
123             launchSubActivity(clazz);
124         }
125     }
126 
testOrderedActivityLaunchSequenceInEventLog()127     public void testOrderedActivityLaunchSequenceInEventLog() throws Exception {
128         @SuppressWarnings("unchecked")
129         Class<? extends Activity>[] activitySequence = new Class[] {
130                 Activities.ActivityOne.class,
131                 Activities.ActivityTwo.class,
132                 Activities.ActivityThree.class,
133         };
134 
135         final long startTime = System.currentTimeMillis() - MINUTE;
136 
137         // Launch the series of Activities.
138         launchSubActivities(activitySequence);
139 
140         final long endTime = System.currentTimeMillis();
141         UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
142 
143         // Consume all the events.
144         ArrayList<UsageEvents.Event> eventList = new ArrayList<>();
145         while (events.hasNextEvent()) {
146             UsageEvents.Event event = new UsageEvents.Event();
147             assertTrue(events.getNextEvent(event));
148             eventList.add(event);
149         }
150 
151         // Find the last Activity's MOVE_TO_FOREGROUND event.
152         int end = eventList.size();
153         while (end > 0) {
154             UsageEvents.Event event = eventList.get(end - 1);
155             if (event.getClassName().equals(activitySequence[activitySequence.length - 1].getName())
156                     && event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
157                 break;
158             }
159             end--;
160         }
161 
162         // We expect 2 events per Activity launched (foreground + background)
163         // except for the last Activity, which was in the foreground when
164         // we queried the event log.
165         final int start = end - ((activitySequence.length * 2) - 1);
166         assertTrue("Not enough events", start >= 0);
167 
168         final int activityCount = activitySequence.length;
169         for (int i = 0; i < activityCount; i++) {
170             final int index = start + (i * 2);
171 
172             // Check for foreground event.
173             UsageEvents.Event event = eventList.get(index);
174             assertEquals(mTargetPackage, event.getPackageName());
175             assertEquals(activitySequence[i].getName(), event.getClassName());
176             assertEquals(UsageEvents.Event.MOVE_TO_FOREGROUND, event.getEventType());
177 
178             // Only check for the background event if this is not the
179             // last activity.
180             if (i < activityCount - 1) {
181                 event = eventList.get(index + 1);
182                 assertEquals(mTargetPackage, event.getPackageName());
183                 assertEquals(activitySequence[i].getName(), event.getClassName());
184                 assertEquals(UsageEvents.Event.MOVE_TO_BACKGROUND, event.getEventType());
185             }
186         }
187     }
188 
189     /**
190      * We can't run this test because we are unable to change the system time.
191      * It would be nice to add a shell command or other to allow the shell user
192      * to set the time, thereby allowing this test to set the time using the UIAutomator.
193      */
194     @Ignore
ignore_testStatsAreShiftedInTimeWhenSystemTimeChanges()195     public void ignore_testStatsAreShiftedInTimeWhenSystemTimeChanges() throws Exception {
196         launchSubActivity(Activities.ActivityOne.class);
197         launchSubActivity(Activities.ActivityThree.class);
198 
199         long endTime = System.currentTimeMillis();
200         long startTime = endTime - MINUTE;
201         Map<String, UsageStats> statsMap = mUsageStatsManager.queryAndAggregateUsageStats(startTime, endTime);
202         assertFalse(statsMap.isEmpty());
203         assertTrue(statsMap.containsKey(mTargetPackage));
204         final UsageStats before = statsMap.get(mTargetPackage);
205 
206         SystemClock.setCurrentTimeMillis(System.currentTimeMillis() - (DAY / 2));
207         try {
208             endTime = System.currentTimeMillis();
209             startTime = endTime - MINUTE;
210             statsMap = mUsageStatsManager.queryAndAggregateUsageStats(startTime, endTime);
211             assertFalse(statsMap.isEmpty());
212             assertTrue(statsMap.containsKey(mTargetPackage));
213             final UsageStats after = statsMap.get(mTargetPackage);
214             assertEquals(before.getPackageName(), after.getPackageName());
215 
216             long diff = before.getFirstTimeStamp() - after.getFirstTimeStamp();
217             assertLessThan(Math.abs(diff - (DAY / 2)), TIME_DIFF_THRESHOLD);
218 
219             assertEquals(before.getLastTimeStamp() - before.getFirstTimeStamp(),
220                     after.getLastTimeStamp() - after.getFirstTimeStamp());
221             assertEquals(before.getLastTimeUsed() - before.getFirstTimeStamp(),
222                     after.getLastTimeUsed() - after.getFirstTimeStamp());
223             assertEquals(before.getTotalTimeInForeground(), after.getTotalTimeInForeground());
224         } finally {
225             SystemClock.setCurrentTimeMillis(System.currentTimeMillis() + (DAY / 2));
226         }
227     }
228 
testUsageEventsParceling()229     public void testUsageEventsParceling() throws Exception {
230         final long startTime = System.currentTimeMillis() - MINUTE;
231 
232         // Ensure some data is in the UsageStats log.
233         @SuppressWarnings("unchecked")
234         Class<? extends Activity>[] activityClasses = new Class[] {
235                 Activities.ActivityTwo.class,
236                 Activities.ActivityOne.class,
237                 Activities.ActivityThree.class,
238         };
239         launchSubActivities(activityClasses);
240 
241         final long endTime = System.currentTimeMillis();
242         UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
243         assertTrue(events.getNextEvent(new UsageEvents.Event()));
244 
245         Parcel p = Parcel.obtain();
246         p.setDataPosition(0);
247         events.writeToParcel(p, 0);
248         p.setDataPosition(0);
249 
250         UsageEvents reparceledEvents = UsageEvents.CREATOR.createFromParcel(p);
251 
252         UsageEvents.Event e1 = new UsageEvents.Event();
253         UsageEvents.Event e2 = new UsageEvents.Event();
254         while (events.hasNextEvent() && reparceledEvents.hasNextEvent()) {
255             events.getNextEvent(e1);
256             reparceledEvents.getNextEvent(e2);
257             assertEquals(e1.getPackageName(), e2.getPackageName());
258             assertEquals(e1.getClassName(), e2.getClassName());
259             assertEquals(e1.getConfiguration(), e2.getConfiguration());
260             assertEquals(e1.getEventType(), e2.getEventType());
261             assertEquals(e1.getTimeStamp(), e2.getTimeStamp());
262         }
263 
264         assertEquals(events.hasNextEvent(), reparceledEvents.hasNextEvent());
265     }
266 
testPackageUsageStatsIntervals()267     public void testPackageUsageStatsIntervals() throws Exception {
268         final long beforeTime = System.currentTimeMillis();
269 
270         // Launch an Activity.
271         launchSubActivity(Activities.ActivityFour.class);
272         launchSubActivity(Activities.ActivityThree.class);
273 
274         final long endTime = System.currentTimeMillis();
275 
276         final SparseLongArray intervalLengths = new SparseLongArray();
277         intervalLengths.put(UsageStatsManager.INTERVAL_DAILY, DAY);
278         intervalLengths.put(UsageStatsManager.INTERVAL_WEEKLY, WEEK);
279         intervalLengths.put(UsageStatsManager.INTERVAL_MONTHLY, MONTH);
280         intervalLengths.put(UsageStatsManager.INTERVAL_YEARLY, YEAR);
281 
282         final int intervalCount = intervalLengths.size();
283         for (int i = 0; i < intervalCount; i++) {
284             final int intervalType = intervalLengths.keyAt(i);
285             final long intervalDuration = intervalLengths.valueAt(i);
286             final long startTime = endTime - (2 * intervalDuration);
287             final List<UsageStats> statsList = mUsageStatsManager.queryUsageStats(intervalType, startTime, endTime);
288             assertFalse(statsList.isEmpty());
289 
290             boolean foundPackage = false;
291             for (UsageStats stats : statsList) {
292                 // Verify that each period is a day long.
293                 assertLessThanOrEqual(stats.getLastTimeStamp() - stats.getFirstTimeStamp(), intervalDuration);
294                 if (stats.getPackageName().equals(mTargetPackage) &&
295                         stats.getLastTimeUsed() >= beforeTime - TIME_DIFF_THRESHOLD) {
296                     foundPackage = true;
297                 }
298             }
299 
300             assertTrue("Did not find package " + mTargetPackage + " in interval " + intervalType, foundPackage);
301         }
302     }
303 
testNoAccessSilentlyFails()304     public void testNoAccessSilentlyFails() throws Exception {
305         final long startTime = System.currentTimeMillis() - MINUTE;
306 
307         launchSubActivity(Activities.ActivityOne.class);
308         launchSubActivity(Activities.ActivityThree.class);
309 
310         final long endTime = System.currentTimeMillis();
311         List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
312                 startTime, endTime);
313         assertFalse(stats.isEmpty());
314 
315         setAppOpsMode("default");
316 
317         stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
318                 startTime, endTime);
319         assertTrue(stats.isEmpty());
320     }
321 }
322