Android: get UsageStats per hour

2020-07-24 01:41发布

问题:

I use UsageStats feature of Android, but the smallest interval is DAILY INTERVAL.

long time = System.currentTimeMillis();
List<UsageStats> appList = manager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - DAY_IN_MILLI_SECONDS, time);

How can I get UsageStats in an hourly interval?

回答1:

All credit goes to this answer. I have learned from that one.

How can we collect app usage data for customized time range (e.g. for per 1 hour)?

We have to call queryEvents(long begin_time, long end_time) method as it will provide us all data starting from begin_time to end_time. It give us each app data through foreground and background events instead of total spent time like queryUsageStats() method. So, using foreground and background events time stamp, we can count the number of times an app has been launched and also can find out the usage duration for each app.

Implementation to Collect Last 1 Hour App Usage Data

At first, add the following line in the AndroidManifest.xml file and also request user to get permission of usage access.

<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />

Add the following lines inside any method

    long hour_in_mil = 1000*60*60; // In Milliseconds
    long end_time = System.currentTimeMillis();
    long start_time = end_time - hour_in_mil;

Then, call the method getUsageStatistics()

    getUsageStatistics(start_time, end_time);

getUsageStatistics methiod

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
void getUsageStatistics(long start_time, long end_time) {

    UsageEvents.Event currentEvent;
  //  List<UsageEvents.Event> allEvents = new ArrayList<>();
    HashMap<String, AppUsageInfo> map = new HashMap<>();
    HashMap<String, List<UsageEvents.Event>> sameEvents = new HashMap<>();

    UsageStatsManager mUsageStatsManager = (UsageStatsManager)
            context.getSystemService(Context.USAGE_STATS_SERVICE);

    if (mUsageStatsManager != null) {
        // Get all apps data from starting time to end time
        UsageEvents usageEvents = mUsageStatsManager.queryEvents(start_time, end_time);

        // Put these data into the map
        while (usageEvents.hasNextEvent()) {
            currentEvent = new UsageEvents.Event();
            usageEvents.getNextEvent(currentEvent);
            if (currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED ||
                    currentEvent.getEventType() == UsageEvents.Event.ACTIVITY_PAUSED) {
              //  allEvents.add(currentEvent);
                String key = currentEvent.getPackageName();
                if (map.get(key) == null) {
                    map.put(key, new AppUsageInfo(key));
                    sameEvents.put(key,new ArrayList<UsageEvents.Event>());
                }
                sameEvents.get(key).add(currentEvent);
            }
        }

        // Traverse through each app data which is grouped together and count launch, calculate duration
        for (Map.Entry<String,List<UsageEvents.Event>> entry : sameEvents.entrySet()) {
            int totalEvents = entry.getValue().size();
            if (totalEvents > 1) {
                for (int i = 0; i < totalEvents - 1; i++) {
                    UsageEvents.Event E0 = entry.getValue().get(i);
                    UsageEvents.Event E1 = entry.getValue().get(i + 1);

                    if (E1.getEventType() == 1 || E0.getEventType() == 1) {
                        map.get(E1.getPackageName()).launchCount++;
                    }

                    if (E0.getEventType() == 1 && E1.getEventType() == 2) {
                        long diff = E1.getTimeStamp() - E0.getTimeStamp();
                        map.get(E0.getPackageName()).timeInForeground += diff;
                    }
                }
            }

    // If First eventtype is ACTIVITY_PAUSED then added the difference of start_time and Event occuring time because the application is already running.
            if (entry.getValue().get(0).getEventType() == 2) {
                long diff = entry.getValue().get(0).getTimeStamp() - start_time;
                map.get(entry.getValue().get(0).getPackageName()).timeInForeground += diff;
            }
            
    // If Last eventtype is ACTIVITY_RESUMED then added the difference of end_time and Event occuring time because the application is still running .
            if (entry.getValue().get(totalEvents - 1).getEventType() == 1) {
                long diff = end_time - entry.getValue().get(totalEvents - 1).getTimeStamp();
                map.get(entry.getValue().get(totalEvents - 1).getPackageName()).timeInForeground += diff;
            }
        }
    
    smallInfoList = new ArrayList<>(map.values());

    // Concatenating data to show in a text view. You may do according to your requirement
    for (AppUsageInfo appUsageInfo : smallInfoList)
    {
        // Do according to your requirement
        strMsg = strMsg.concat(appUsageInfo.packageName + " : " + appUsageInfo.launchCount + "\n\n");
    }

    TextView tvMsg = findViewById(R.id.MA_TvMsg);
    tvMsg.setText(strMsg);
       
    } else {
        Toast.makeText(context, "Sorry...", Toast.LENGTH_SHORT).show();
    }

}

AppUsageInfo.class

import android.graphics.drawable.Drawable;

class AppUsageInfo {
    Drawable appIcon; // You may add get this usage data also, if you wish.
    String appName, packageName;
    long timeInForeground;
    int launchCount;

    AppUsageInfo(String pName) {
        this.packageName=pName;
    }
}

How can I customize these codes to collect per 1 hour data?

As you want to get per hour data, please change the end_time and start_time value for every hour data. For instance: If I would try to collect past per hour data (for past 2 hour data). I would do the following thing.

    long end_time = System.currentTimeMillis();
    long start_time = end_time - (1000*60*60);

    getUsageStatistics(start_time, end_time);

    end_time =  start_time;
    start_time = start_time - hour_in_mil;

    getUsageStatistics(start_time, end_time);

However, you may use a Handler to skip repeatedly writing start_time and end_time to change value of these variables. Each time data will be collected for one hour, a task will be completed and after automatically changing the values of the variables, you will again call the getUsageStatistics method.

Note: Maybe, you will not be able to retrieve data for more than past 7.5 days as events are only kept by the system for a few days.



回答2:

    Calendar cal = (Calendar) Calendar.getInstance().clone();
    //I used this and it worked, only for 7 days and a half ago 
    if (daysAgo == 0) {
        //Today - I only count from 00h00m00s today to present

        end = cal.getTimeInMillis();
        start = LocalDate.now().toDateTimeAtStartOfDay().toInstant().getMillis();
    } else {
        long todayStartOfDayTimeStamp = LocalDate.now().toDateTimeAtStartOfDay().toInstant().getMillis();
        if (mDaysAgo == -6) {
            //6 days ago, only get events in time -7 days to -7.5 days

            cal.setTimeInMillis(System.currentTimeMillis());
            cal.add(Calendar.DATE, daysAgo + 1);
            end = cal .getTimeInMillis();
            start = end - 43200000;
        } else {
            //get events from 00h00m00s to  23h59m59s
            //Current calendar point to 0h0m today
            cal.setTimeInMillis(todayStartOfDayTimeStamp);
            cal.add(Calendar.DATE, daysAgo + 1);
            end = calendar.getTimeInMillis();
            cal.add(Calendar.DATE, -1);
            start = calendar.getTimeInMillis();
        }
    }


回答3:

I don't think it's possible, even if you ask for data in the middle of an interval, it looks like the data is stored in buckets and the minimum bucket is a day. In UsageStatsManager documentation, it says:

A request for data in the middle of a time interval will include that interval.

Also, INTERVAL_BEST is not a real interval, it just selects one of the available intervals for the given time range. In UsageStatsManager.java source code, it says:

/**
 * The number of available intervals. Does not include {@link #INTERVAL_BEST}, since it
 * is a pseudo interval (it actually selects a real interval).
 * {@hide}
 */
public static final int INTERVAL_COUNT = 4;


回答4:

Yes, Android is providing minimum INTERVAL_DAILY. But for the best result, you can use INTERVAL_BEST. Android is giving the best interval timer for the given time range in queryUsageStats(int, long, long).

Happy coding...