I wrote a cleaner and safer queryEvents function inspired by @Vishal Sharma, @jguerinet, and @Floarian answers.
Starting with Android Q, Foreground Service events can also be recorded. Especially if you want to calculate the time spent in the background, you can now do it.
Create an AppUsageStats class:
public class AppUsageStats {
private final long lastTimeUsedMillis;
private final long totalTimeInForegroundMillis;
private final long lastTimeForegroundServiceUsedMillis;
private final long totalTimeForegroundServiceUsedMillis;
public AppUsageStats(
long lastTimeUsedMillis,
long totalTimeInForegroundMillis,
long lastTimeForegroundServiceUsedMillis,
long totalTimeForegroundServiceUsedMillis
) {
this.lastTimeUsedMillis = lastTimeUsedMillis;
this.totalTimeInForegroundMillis = totalTimeInForegroundMillis;
this.lastTimeForegroundServiceUsedMillis = lastTimeForegroundServiceUsedMillis;
this.totalTimeForegroundServiceUsedMillis = totalTimeForegroundServiceUsedMillis;
}
public long getLastTimeUsedMillis() {
return lastTimeUsedMillis;
}
public long getTotalTimeInForegroundMillis() {
return totalTimeInForegroundMillis;
}
@RequiresApi(Build.VERSION_CODES.Q)
public long getLastTimeForegroundServiceUsedMillis() {
return lastTimeForegroundServiceUsedMillis;
}
@RequiresApi(Build.VERSION_CODES.Q)
public long getTotalTimeForegroundServiceUsedMillis() {
return totalTimeForegroundServiceUsedMillis;
}
}
Create an AppUsageStatsBucket class to store Foreground Service data:
public class AppUsageStatsBucket {
private long startMillis;
private long endMillis;
private long totalTime;
public AppUsageStatsBucket() {
this.startMillis = 0L;
this.endMillis = 0L;
this.totalTime = 0L;
}
public long getStartMillis() {
return startMillis;
}
public void setStartMillis(long startMillis) {
this.startMillis = startMillis;
}
public long getEndMillis() {
return endMillis;
}
public void setEndMillis(long endMillis) {
this.endMillis = endMillis;
}
public long getTotalTime() {
return totalTime;
}
public void addTotalTime() {
this.totalTime += endMillis - startMillis;
}
public void setTotalTime(long totalTime) {
this.totalTime = totalTime;
}
}
Create a UsageStatsSelection Enum class:
public enum UsageStatsSelection {
HOURLY(
UsageStatsManager.INTERVAL_DAILY,
System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1L),
System.currentTimeMillis()
),
DAILY(
UsageStatsManager.INTERVAL_DAILY,
0L,
System.currentTimeMillis()
),
WEEKLY(
UsageStatsManager.INTERVAL_WEEKLY,
0L,
System.currentTimeMillis()
),
MONTHLY(
UsageStatsManager.INTERVAL_MONTHLY,
0L,
System.currentTimeMillis()
),
YEARLY(
UsageStatsManager.INTERVAL_YEARLY,
0L,
System.currentTimeMillis()
);
private final int usageStatsInterval;
private final long beginTime;
private final long endTime;
UsageStatsSelection(int usageStatsInterval, long beginTime, long endTime) {
this.usageStatsInterval = usageStatsInterval;
this.beginTime = beginTime;
this.endTime = endTime;
}
public int getUsageStatsInterval() {
return usageStatsInterval;
}
public long getBeginTime() {
return beginTime;
}
public long getEndTime() {
return endTime;
}
}
Since it is not possible to get all days for queryEvents (see: queryEvents(), Query for events in the given time range. Events are only kept by the system for a few days.), we will use queryEvents to get only daily events. We will also use the queryUsageStats() function to get the Weekly, Monthly and Yearly usage data.
@NonNull
public static Map<String, AppUsageStats> queryUsageStats(
Context context,
@NonNull UsageStatsSelection statsSelection
) {
final UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
final Map<String, AppUsageStats> appUsageStatsHashMap = new HashMap<>();
switch (statsSelection) {
case HOURLY:
final UsageEvents events = usageStatsManager.queryEvents(
statsSelection.getBeginTime(),
statsSelection.getEndTime()
);
Map<String, List<UsageEvents.Event>> eventsMap = new HashMap<>();
UsageEvents.Event currentEvent;
while (events.hasNextEvent()) {
currentEvent = new UsageEvents.Event();
if (events.getNextEvent(currentEvent)) {
switch (currentEvent.getEventType()) {
case UsageEvents.Event.ACTIVITY_RESUMED:
case UsageEvents.Event.ACTIVITY_PAUSED:
case UsageEvents.Event.ACTIVITY_STOPPED:
case UsageEvents.Event.FOREGROUND_SERVICE_START:
case UsageEvents.Event.FOREGROUND_SERVICE_STOP:
List<UsageEvents.Event> packageEvents = eventsMap.get(currentEvent.getPackageName());
if (packageEvents == null) {
packageEvents = new ArrayList<>(Collections.singletonList(currentEvent));
} else {
packageEvents.add(currentEvent);
}
eventsMap.put(currentEvent.getPackageName(), packageEvents);
break;
}
}
}
for (Map.Entry<String, List<UsageEvents.Event>> entry : eventsMap.entrySet()) {
final AppUsageStatsBucket foregroundBucket = new AppUsageStatsBucket();
final Map<String, AppUsageStatsBucket> backgroundBucketMap = new HashMap<>();
for (int pos = 0; pos < entry.getValue().size(); pos++) {
final UsageEvents.Event event = entry.getValue().get(pos);
AppUsageStatsBucket backgroundBucket = backgroundBucketMap.get(event.getClassName());
if (backgroundBucket == null) {
backgroundBucket = new AppUsageStatsBucket();
backgroundBucketMap.put(event.getClassName(), backgroundBucket);
}
switch (event.getEventType()) {
case UsageEvents.Event.ACTIVITY_RESUMED:
foregroundBucket.setStartMillis(event.getTimeStamp());
break;
case UsageEvents.Event.ACTIVITY_PAUSED:
case UsageEvents.Event.ACTIVITY_STOPPED:
if (foregroundBucket.getStartMillis() >= foregroundBucket.getEndMillis()) {
if (foregroundBucket.getStartMillis() == 0L) {
foregroundBucket.setStartMillis(statsSelection.getBeginTime());
}
foregroundBucket.setEndMillis(event.getTimeStamp());
foregroundBucket.addTotalTime();
}
break;
case UsageEvents.Event.FOREGROUND_SERVICE_START:
backgroundBucket.setStartMillis(event.getTimeStamp());
break;
case UsageEvents.Event.FOREGROUND_SERVICE_STOP:
if (backgroundBucket.getStartMillis() >= backgroundBucket.getEndMillis()) {
if (backgroundBucket.getStartMillis() == 0L) {
backgroundBucket.setStartMillis(statsSelection.getBeginTime());
}
backgroundBucket.setEndMillis(event.getTimeStamp());
backgroundBucket.addTotalTime();
}
break;
}
if (pos == entry.getValue().size() - 1) {
if (foregroundBucket.getStartMillis() > foregroundBucket.getEndMillis()) {
foregroundBucket.setEndMillis(statsSelection.getEndTime());
foregroundBucket.addTotalTime();
}
if (backgroundBucket.getStartMillis() > backgroundBucket.getEndMillis()) {
backgroundBucket.setEndMillis(statsSelection.getEndTime());
backgroundBucket.addTotalTime();
}
}
}
final long foregroundEnd = foregroundBucket.getEndMillis();
final long totalTimeForeground = foregroundBucket.getTotalTime();
final long backgroundEnd = backgroundBucketMap.values()
.stream()
.mapToLong(AppUsageStatsBucket::getEndMillis)
.max()
.orElse(0L);
final long totalTimeBackground = backgroundBucketMap.values()
.stream()
.mapToLong(AppUsageStatsBucket::getTotalTime)
.sum();
appUsageStatsHashMap.put(entry.getKey(), new AppUsageStats(
Math.max(foregroundEnd, backgroundEnd),
totalTimeForeground,
backgroundEnd,
totalTimeBackground
));
}
break;
default:
final List<UsageStats> usageStats = usageStatsManager
.queryUsageStats(
statsSelection.getUsageStatsInterval(),
statsSelection.getBeginTime(),
statsSelection.getEndTime()
);
appUsageStatsHashMap.putAll(usageStats.parallelStream()
.collect(Collectors.toMap(
UsageStats::getPackageName,
stats -> new AppUsageStats(
stats.getLastTimeUsed(),
stats.getTotalTimeInForeground(),
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
? stats.getLastTimeForegroundServiceUsed()
: 0,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
? stats.getTotalTimeForegroundServiceUsed()
: 0
),
(oldValue, newValue) -> newValue
)));
break;
}
return appUsageStatsHashMap;
}
The result will be returned as Package Name and AppUsageStats list.