How to count app usage time while app is on foreground?
Asked Answered
P

2

12

I'm working on an android app for tracking daily app usage. The idea is that a user can set daily time limit for any app and a notification will appear within at most 2 minutes after the limit is exceeded. (The reason for delay: I've created an alarm system using AlarmManager class that will go off every minute to run a JobIntentService which will check whether limit for any app is exceeded)

I've used queryEvents method of UsageStatsManager class to count app usage time.

Here's my code for counting app usage time (I've followed How to use queryEvents):

HashMap<String, Integer> getTimeSpent(Context context, String packageName, long beginTime, long endTime) {
    UsageEvents.Event currentEvent;
    List<UsageEvents.Event> allEvents = new ArrayList<>();
    HashMap<String, Integer> appUsageMap = new HashMap<>();

    UsageStatsManager usageStatsManager = (UsageStatsManager)context.getSystemService(Context.USAGE_STATS_SERVICE);
    UsageEvents usageEvents = usageStatsManager.queryEvents(beginTime, endTime);

    while (usageEvents.hasNextEvent()) {
        currentEvent = new UsageEvents.Event();
        usageEvents.getNextEvent(currentEvent);
        if(currentEvent.getPackageName().equals(packageName) || packageName == null) {
            if (currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED
                    || currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED) {
                allEvents.add(currentEvent);
                String key = currentEvent.getPackageName();
                if (appUsageMap.get(key) == null)
                    appUsageMap.put(key, 0);
            }
        }
    }

    for (int i = 0; i < allEvents.size() - 1; i++) {
        UsageEvents.Event E0 = allEvents.get(i);
        UsageEvents.Event E1 = allEvents.get(i + 1);

        if (E0.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED
                && E1.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED
                && E0.getClassName().equals(E1.getClassName())) {
            int diff = (int)(E1.getTimeStamp() - E0.getTimeStamp());
            diff /= 1000;
            Integer prev = appUsageMap.get(E0.getPackageName());
            if(prev == null) prev = 0;
            appUsageMap.put(E0.getPackageName(), prev + diff);
        }
    }
    return appUsageMap;
}

In short the above code counts the time difference of the timestamp when an app goes foreground (UsageEvents.Event.ACTIVITY_RESUMED) and the timestamp when it goes background (UsageEvents.Event.ACTIVITY_PAUSED). Then it adds this difference to the total usage time of the app.

The problem is that the amount of time spent on foreground can't be counted unless the app goes background. So, if usage limit is exceeded, notification won't appear until the app goes background.

Is it actually possible to get foreground time while app is on foreground?

N.B. I've tried queryUsageStats along with UsageStats.getTotalTimeInForeground() but couldn't succeed since queryUsageStats had some other issues not related to this question.

Prescience answered 8/5, 2020 at 10:54 Comment(0)
P
11

I've solved the issue.

Adding difference of current time and timestamp of current running app going foreground does the trick.

I just added the following code before the return statement:

UsageEvents.Event lastEvent = allEvents.get(allEvents.size() - 1);
if(lastEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
    int diff = (int)System.currentTimeMillis() - (int)lastEvent.getTimeStamp();
    diff /= 1000;
    Integer prev = appUsageMap.get(lastEvent.getPackageName());
    if(prev == null) prev = 0;
    appUsageMap.put(lastEvent.getPackageName(), prev + diff);
}

It is pretty straightforward, I should have thought about it before posting the question.

Prescience answered 11/5, 2020 at 9:46 Comment(0)
E
0

For get App uses states you can use below code it shows close to accurate result

val stateMap = HashMap<String, AppStateModel>()
val usageStatsManager = getSystemService(USAGE_STATS_SERVICE) as UsageStatsManager

val calendar: Calendar = Calendar.getInstance()
calendar.set(Calendar.HOUR_OF_DAY,0)
calendar.set(Calendar.MINUTE,0)
calendar.set(Calendar.SECOND,0)

val calenderEnd = Calendar.getInstance()
calenderEnd.set(Calendar.HOUR_OF_DAY,23)
calenderEnd.set(Calendar.MINUTE,59)
calenderEnd.set(Calendar.SECOND,59)

val eventList = usageStatsManager.queryEvents(calendar.timeInMillis, calenderEnd.timeInMillis)
while(eventList.hasNextEvent()) {
    val event = UsageEvents.Event()
    eventList.getNextEvent(event)
    if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) {
        val packageCheck = stateMap[event.packageName]
        if (packageCheck != null) {
            if (stateMap[event.packageName]!!.calssMap[event.className] != null) {
                stateMap[event.packageName]!!.className = event.className
                stateMap[event.packageName]!!.startTime = event.timeStamp
                stateMap[event.packageName]!!.calssMap[event.className]!!.startTime = event.timeStamp
                stateMap[event.packageName]!!.calssMap[event.className]!!.isResume = true
            } else {
                stateMap[event.packageName]!!.className = event.className
                stateMap[event.packageName]!!.startTime = event.timeStamp
                stateMap[event.packageName]!!.calssMap[event.className] = BoolObj(event.timeStamp, true)
            }
        } else {
            val appStates = AppStateModel(packageName = event.packageName, className = event.className, startTime = event.timeStamp)
            appStates.calssMap[event.className] = BoolObj(event.timeStamp, true)
            stateMap[event.packageName] = appStates
        }
    }else if (event.eventType == UsageEvents.Event.ACTIVITY_STOPPED){
        val packageCheck = stateMap[event.packageName]
        if (packageCheck != null) {
            if (stateMap[event.packageName]!!.calssMap[event.className] != null) {
                stateMap[event.packageName]!!.totalTime += event.timeStamp - stateMap[event.packageName]!!.calssMap[event.className]!!.startTime
                stateMap[event.packageName]!!.calssMap[event.className]!!.isResume = false
            }
        }
    }
}
batteryAppsList.addAll(stateMap.values.toList())

AppStateModel:

data class AppStateModel(
    val packageName: String,
    var startTime: Long = 0,
    var endTime: Long = 0,
    var totalTime : Long = 0,
    var totalCapacity : Double = 0.0,
    var currentCapacity: Double = 0.0,
    var isAlreadyResume : Boolean = false,
    var className : String = "",
    var calssMap : HashMap<String, BoolObj> = HashMap()
)

data class BoolObj(var startTime: Long, var isResume: Boolean)
Enidenigma answered 8/12, 2023 at 5:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.