| /** |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy |
| * of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package android.app.usage.cts; |
| |
| import android.app.Activity; |
| import android.app.AppOpsManager; |
| import android.app.Instrumentation; |
| import android.app.usage.UsageEvents; |
| import android.app.usage.UsageStats; |
| import android.app.usage.UsageStatsManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Parcel; |
| import android.os.ParcelFileDescriptor; |
| import android.os.SystemClock; |
| import android.test.InstrumentationTestCase; |
| |
| import java.io.FileInputStream; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import android.util.SparseLongArray; |
| import junit.framework.AssertionFailedError; |
| import libcore.io.IoUtils; |
| import libcore.io.Streams; |
| import org.junit.Ignore; |
| |
| /** |
| * Test the UsageStats API. It is difficult to test the entire surface area |
| * of the API, as a lot of the testing depends on what data is already present |
| * on the device and for how long that data has been aggregating. |
| * |
| * These tests perform simple checks that each interval is of the correct duration, |
| * and that events do appear in the event log. |
| * |
| * Tests to add that are difficult to add now: |
| * - Invoking a device configuration change and then watching for it in the event log. |
| * - Changing the system time and verifying that all data has been correctly shifted |
| * along with the new time. |
| * - Proper eviction of old data. |
| */ |
| public class UsageStatsTest extends InstrumentationTestCase { |
| private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} " + |
| AppOpsManager.OPSTR_GET_USAGE_STATS + " {1}"; |
| |
| private static final long MINUTE = 1000 * 60; |
| private static final long DAY = MINUTE * 60 * 24; |
| private static final long WEEK = 7 * DAY; |
| private static final long MONTH = 30 * DAY; |
| private static final long YEAR = 365 * DAY; |
| private static final long TIME_DIFF_THRESHOLD = 200; |
| |
| private UsageStatsManager mUsageStatsManager; |
| private String mTargetPackage; |
| private ArrayList<Activity> mStartedActivities = new ArrayList<>(); |
| |
| @Override |
| protected void setUp() throws Exception { |
| mUsageStatsManager = (UsageStatsManager) getInstrumentation().getContext() |
| .getSystemService(Context.USAGE_STATS_SERVICE); |
| mTargetPackage = getInstrumentation().getContext().getPackageName(); |
| |
| setAppOpsMode("allow"); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| for (Activity activity : mStartedActivities) { |
| activity.finish(); |
| } |
| } |
| |
| private static void assertLessThan(long left, long right) { |
| if (left >= right) { |
| throw new AssertionFailedError("Expected " + left + " to be less than " + right); |
| } |
| } |
| |
| private static void assertLessThanOrEqual(long left, long right) { |
| if (left > right) { |
| throw new AssertionFailedError("Expected " + left + " to be less than or equal to " + right); |
| } |
| } |
| |
| private void setAppOpsMode(String mode) throws Exception { |
| final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, |
| getInstrumentation().getContext().getPackageName(), mode); |
| ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation() |
| .executeShellCommand(command); |
| try { |
| Streams.readFully(new FileInputStream(pfd.getFileDescriptor())); |
| } finally { |
| IoUtils.closeQuietly(pfd.getFileDescriptor()); |
| } |
| } |
| |
| private void launchSubActivity(Class<? extends Activity> clazz) { |
| final Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(0, new Intent()); |
| final Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(clazz.getName(), result, false); |
| getInstrumentation().addMonitor(monitor); |
| launchActivity(mTargetPackage, clazz, null); |
| mStartedActivities.add(monitor.waitForActivity()); |
| } |
| |
| private void launchSubActivities(Class<? extends Activity>[] activityClasses) { |
| for (Class<? extends Activity> clazz : activityClasses) { |
| launchSubActivity(clazz); |
| } |
| } |
| |
| public void testOrderedActivityLaunchSequenceInEventLog() throws Exception { |
| @SuppressWarnings("unchecked") |
| Class<? extends Activity>[] activitySequence = new Class[] { |
| Activities.ActivityOne.class, |
| Activities.ActivityTwo.class, |
| Activities.ActivityThree.class, |
| }; |
| |
| // Launch the series of Activities. |
| launchSubActivities(activitySequence); |
| |
| final long endTime = System.currentTimeMillis(); |
| final long startTime = endTime - DAY; |
| UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime); |
| |
| // Consume all the events. |
| ArrayList<UsageEvents.Event> eventList = new ArrayList<>(); |
| while (events.hasNextEvent()) { |
| UsageEvents.Event event = new UsageEvents.Event(); |
| assertTrue(events.getNextEvent(event)); |
| eventList.add(event); |
| } |
| |
| // We expect 2 events per Activity launched (foreground + background) |
| // except for the last Activity, which was in the foreground when |
| // we queried the event log. |
| assertTrue(eventList.size() >= (activitySequence.length * 2) - 1); |
| final int offset = eventList.size() - ((activitySequence.length * 2) - 1); |
| |
| final int activityCount = activitySequence.length; |
| for (int i = 0; i < activityCount; i++) { |
| int index = offset + (i * 2); |
| |
| // Check for foreground event. |
| UsageEvents.Event event = eventList.get(index); |
| assertEquals(mTargetPackage, event.getPackageName()); |
| assertEquals(activitySequence[i].getName(), event.getClassName()); |
| assertEquals(UsageEvents.Event.MOVE_TO_FOREGROUND, event.getEventType()); |
| |
| index += 1; |
| if (i < activityCount - 1) { |
| // Check for background event. |
| event = eventList.get(index); |
| assertEquals(mTargetPackage, event.getPackageName()); |
| assertEquals(activitySequence[i].getName(), event.getClassName()); |
| assertEquals(UsageEvents.Event.MOVE_TO_BACKGROUND, event.getEventType()); |
| } |
| } |
| } |
| |
| /** |
| * We can't run this test because we are unable to change the system time. |
| * It would be nice to add a shell command or other to allow the shell user |
| * to set the time, thereby allowing this test to set the time using the UIAutomator. |
| */ |
| @Ignore |
| public void ignore_testStatsAreShiftedInTimeWhenSystemTimeChanges() throws Exception { |
| launchSubActivity(Activities.ActivityOne.class); |
| |
| long endTime = System.currentTimeMillis(); |
| long startTime = endTime - MINUTE; |
| Map<String, UsageStats> statsMap = mUsageStatsManager.queryAndAggregateUsageStats(startTime, endTime); |
| assertFalse(statsMap.isEmpty()); |
| assertTrue(statsMap.containsKey(mTargetPackage)); |
| final UsageStats before = statsMap.get(mTargetPackage); |
| |
| SystemClock.setCurrentTimeMillis(System.currentTimeMillis() - (DAY / 2)); |
| try { |
| endTime = System.currentTimeMillis(); |
| startTime = endTime - MINUTE; |
| statsMap = mUsageStatsManager.queryAndAggregateUsageStats(startTime, endTime); |
| assertFalse(statsMap.isEmpty()); |
| assertTrue(statsMap.containsKey(mTargetPackage)); |
| final UsageStats after = statsMap.get(mTargetPackage); |
| assertEquals(before.getPackageName(), after.getPackageName()); |
| |
| long diff = before.getFirstTimeStamp() - after.getFirstTimeStamp(); |
| assertLessThan(Math.abs(diff - (DAY / 2)), TIME_DIFF_THRESHOLD); |
| |
| assertEquals(before.getLastTimeStamp() - before.getFirstTimeStamp(), |
| after.getLastTimeStamp() - after.getFirstTimeStamp()); |
| assertEquals(before.getLastTimeUsed() - before.getFirstTimeStamp(), |
| after.getLastTimeUsed() - after.getFirstTimeStamp()); |
| assertEquals(before.getTotalTimeInForeground(), after.getTotalTimeInForeground()); |
| } finally { |
| SystemClock.setCurrentTimeMillis(System.currentTimeMillis() + (DAY / 2)); |
| } |
| } |
| |
| public void testUsageEventsParceling() throws Exception { |
| final long startTime = System.currentTimeMillis(); |
| |
| @SuppressWarnings("unchecked") |
| Class<? extends Activity>[] activityClasses = new Class[] { |
| Activities.ActivityOne.class, |
| Activities.ActivityThree.class, |
| Activities.ActivityTwo.class, |
| }; |
| launchSubActivities(activityClasses); |
| |
| final long endTime = System.currentTimeMillis(); |
| UsageEvents events = mUsageStatsManager.queryEvents(startTime - TIME_DIFF_THRESHOLD, endTime); |
| assertTrue(events.getNextEvent(new UsageEvents.Event())); |
| |
| Parcel p = Parcel.obtain(); |
| p.setDataPosition(0); |
| events.writeToParcel(p, 0); |
| p.setDataPosition(0); |
| |
| UsageEvents reparceledEvents = UsageEvents.CREATOR.createFromParcel(p); |
| |
| UsageEvents.Event e1 = new UsageEvents.Event(); |
| UsageEvents.Event e2 = new UsageEvents.Event(); |
| while (events.hasNextEvent() && reparceledEvents.hasNextEvent()) { |
| events.getNextEvent(e1); |
| reparceledEvents.getNextEvent(e2); |
| assertEquals(e1.getPackageName(), e2.getPackageName()); |
| assertEquals(e1.getClassName(), e2.getClassName()); |
| assertEquals(e1.getConfiguration(), e2.getConfiguration()); |
| assertEquals(e1.getEventType(), e2.getEventType()); |
| assertEquals(e1.getTimeStamp(), e2.getTimeStamp()); |
| } |
| |
| assertEquals(events.hasNextEvent(), reparceledEvents.hasNextEvent()); |
| } |
| |
| public void testPackageUsageStatsIntervals() throws Exception { |
| final long beforeTime = System.currentTimeMillis(); |
| |
| // Launch an Activity. |
| launchSubActivity(Activities.ActivityFour.class); |
| |
| final long endTime = System.currentTimeMillis(); |
| |
| final SparseLongArray intervalLengths = new SparseLongArray(); |
| intervalLengths.put(UsageStatsManager.INTERVAL_DAILY, DAY); |
| intervalLengths.put(UsageStatsManager.INTERVAL_WEEKLY, WEEK); |
| intervalLengths.put(UsageStatsManager.INTERVAL_MONTHLY, MONTH); |
| intervalLengths.put(UsageStatsManager.INTERVAL_YEARLY, YEAR); |
| |
| final int intervalCount = intervalLengths.size(); |
| for (int i = 0; i < intervalCount; i++) { |
| final int intervalType = intervalLengths.keyAt(i); |
| final long intervalDuration = intervalLengths.valueAt(i); |
| final long startTime = endTime - (2 * intervalDuration); |
| final List<UsageStats> statsList = mUsageStatsManager.queryUsageStats(intervalType, startTime, endTime); |
| assertFalse(statsList.isEmpty()); |
| |
| boolean foundPackage = false; |
| for (UsageStats stats : statsList) { |
| // Verify that each period is a day long. |
| assertLessThanOrEqual(stats.getLastTimeStamp() - stats.getFirstTimeStamp(), intervalDuration); |
| if (stats.getPackageName().equals(mTargetPackage) && |
| stats.getLastTimeUsed() >= beforeTime - TIME_DIFF_THRESHOLD) { |
| foundPackage = true; |
| } |
| } |
| |
| assertTrue("Did not find package " + mTargetPackage + " in interval " + intervalType, foundPackage); |
| } |
| } |
| |
| public void testNoAccessSilentlyFails() throws Exception { |
| launchSubActivity(Activities.ActivityOne.class); |
| |
| final long endTime = System.currentTimeMillis(); |
| final long startTime = endTime - MINUTE; |
| List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, |
| startTime, endTime); |
| assertFalse(stats.isEmpty()); |
| |
| setAppOpsMode("default"); |
| |
| stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, |
| startTime, endTime); |
| assertTrue(stats.isEmpty()); |
| } |
| } |