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