How to use UsageStatsManager?
Asked Answered
R

7

63

Background

Google has deprecated the function "getRecentTasks" of "ActivityManager" class. Now all it does is to get the list of apps that the current app has opened.

I've even written a post about it here on StackOverflow, but I noticed it's impossible.

The problem

I've made a post about it (here, and another, similar one created by someone else, here) and requested to re-consider it, and Google decided to make a new class, that seem to provide a similar functionality (more like statistics, but might also be useful), but I can't find out how to use it.

The class is called "UsageStatsManager", and my guess is that the function "queryUsageStats" does the job.

Also, it seems it has a new permission ("android.permission.PACKAGE_USAGE_STATS"), which is a system permission, but it's written that:

declaring the permission implies intention to use the API and the user of the device can grant permission through the Settings application.

Here's another link about this new functionality.

What I've found

I've looked at the code of Android, and noticed that "Context" has USAGE_STATS_SERVICE , which in the JavaDocs say the next thing:

/**
 * Use with {@link #getSystemService} to retrieve a {@link
 * android.app.UsageStatsManager} for interacting with the status bar.
 *
 * @see #getSystemService
 * @see android.app.UsageStatsManager
 * @hide
 */
public static final String USAGE_STATS_SERVICE = "usagestats";

The weird thing is that not only it says "status bar", but also the packageName doesn't match (should be "android.app.usage.UsageStatsManager" instead) .

I've also added the correct permission:

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

and here's the code I use:

  final UsageStatsManager usageStatsManager=(UsageStatsManager)context.getSystemService("usagestats");// Context.USAGE_STATS_SERVICE);
  final int currentYear=Calendar.getInstance().get(Calendar.YEAR);
  final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY,currentYear-2,currentYear);

In the emulator itself, I went to "Settings"->"security"->"apps with usage access" , and enabled my app.

However, when running the code, all I get is an empty list...

The question

How do you use UsageStatsManager ?

Also, how do you let the user to grant the permission in the easiest way possible? Or is it automatically done, as soon as the app tries to get the needed information?

What happens when trying to use this class yet the user hasn't confirmed it yet?

How can I make the code return me a real list of apps?

Reflexive answered 17/10, 2014 at 19:7 Comment(11)
"how do you let the user to grant the permission in the easiest way possible?" -- I see "Apps with usage access" in the Settings > Security screen. Presumably, apps requesting this permission show up there. IMHO, they should have used <meta-data>.Mohler
@Mohler Yes, I've noticed that, but I ask this as a developer. What should the developer do with it? I don't even know how to initialize the whole thing. It's also a bit weird that it's a system permission and yet it's possible to use it... Is there such a thing on a different case? Maybe it would be a similar handling...Reflexive
"What should the developer do with it?" -- tell the user to go visit that screen, I would imagine. "Is there such a thing on a different case?" -- not that I am aware of.Mohler
@Mohler I mean, how does it work? How can I even initialize or reach this class? It doesn't have a CTOR, and it's not available via Context.getSystemService (not in the docs and in the SDK, at least) ... Also, what happens when I try to call this function? will it go to the needed settings screen, or should I do it myself? I need to wish how to work with this new class...Reflexive
My guess is that it is supposed to be via getSystemService() and that there is a documentation bug. As for the rest, I have no idea. The source code for it does not seem to have been released yet.Mohler
@Mohler Maybe I should post about it back to Google... BTW, here's another clue: developer.android.com/about/versions/android-5.0.html#SystemReflexive
ok, I've posted about it here: code.google.com/p/android/issues/detail?id=77648 .Reflexive
@Mohler I've found something interesting. I've looked at the code of Android, and noticed that "Context" has USAGE_STATS_SERVICE , which in the JavaDocs say "Use with getSystemService to retrieve a android.app.UsageStatsManager for interacting with the status bar." . The weird thing is that not only it says "status bar", but also the packageName doesn't match (should be "android.app.usage.UsageStatsManager" instead) .Reflexive
@Mohler Updated the question again, as I've found some more clues.Reflexive
The data is located here /data/system/usagestats/{user_id_}, where {user_id} is id of user account on the device.Schwejda
found this repo doing exactly what you have asked.Dyna
A
36

I think the documentation was just short hand for the Calendar stuff. I don't think it actually works with just 2014; however I can be wrong.

In order to access the actually list of UsageStats, you would need to create a Calendar object with the correct month,day, and year. Exactly how MRK said in the other answer. I copied and corrected the errors in MRK's code so anyone who sees it in the future can see it.

Calendar beginCal = Calendar.getInstance();
beginCal.set(Calendar.DATE, 1);
beginCal.set(Calendar.MONTH, 0);
beginCal.set(Calendar.YEAR, 2012);

Calendar endCal = Calendar.getInstance();
endCal.set(Calendar.DATE, 1);
endCal.set(Calendar.MONTH, 0);
endCal.set(Calendar.YEAR, 2014);

final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY, beginCal.getTimeInMillis(), endCal.getTimeInMillis());

-Credit MRK; corrected by me (he accidentally just put cal instead of beginCal and endCal)

The code for the usage access settings is below. :)

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);
Aldas answered 29/10, 2014 at 4:1 Comment(6)
How did I miss that? Maybe they've added it after I looked at it. Anyway, is there a way to reach to exactly the app to enable/disable? so that the user will just have to confirm?Reflexive
I don't think its possible. If you look at the Input under ACTION_USAGE_ACCESS_SETTINGS , you'll notice it says nothing. Therefore, I believe there is no way to specify a certain app.Aldas
Too bad. Now I don't know what to do with the answer to my question, as both you and MRK answered different parts of it. Only thing that's missing for me is an explanation of how to work with it, and what's the "interval" exactly. I also wonder if it will work on normal devices and not just the emulator, as it was seem as if it's a system-permission.Reflexive
It'll work on devices. You can just check the amount of time a package has been active to decide if an activity is being used or i think you can use queryEvents() to see if an app was moved to the foreground or background. Honestly, its really going to discourage users to use your app. Going and giving special permission makes users feel scared/weird if its the first time they are using your app.Aldas
I fixed MRK's code and put it into one to make it easier for future reference. He had forgotten to use beginCal and endCal instead of just cal.Aldas
I don't intend on forcing users to give this permission, but I will give a warning dialog about it. The app's main purpose is far from it, so it should be fine: play.google.com/store/apps/details?id=com.lb.app_manager . Anyway, thank you.Reflexive
S
29

I've created a sample of how to use UsageStats on my Github. Hopefully it can be of help to someone

https://github.com/ColeMurray/UsageStatsSample

Selfconfessed answered 2/3, 2015 at 21:27 Comment(3)
Thank you . you get +1 for the effort. :)Reflexive
the code is a bit wrong about assuming when you get the required permission, as it assumes that if the result is empty, you don't have permission. I've tried using those solutions: https://mcmap.net/q/89426/-how-permission-can-be-checked-at-runtime-without-throwing-securityexception/878126 , but all of them returned the same result (denied), no matter what happens. I've written a post about it here: https://mcmap.net/q/88983/-how-to-check-if-quot-android-permission-package_usage_stats-quot-permission-is-given/878126Reflexive
Could this be due to it being a hidden permission?^Selfconfessed
S
9

Answering your last question "How can I make the code return me a real list of apps?". queryUsageStats takes begin time and end time in milliseconds, not the value of the year in int.

Calendar beginCal = Calendar.getInstance();
beginCal.set(Calendar.DATE, 1);
beginCal.set(Calendar.MONTH, 0);
beginCal.set(Calendar.YEAR, 2012);

Calendar endCal = Calendar.getInstance();
endCal.set(Calendar.DATE, 1);
endCal.set(Calendar.MONTH, 0);
endCal.set(Calendar.YEAR, 2014);

final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY, beginCal.getTimeInMillis(), endCal.getTimeInMillis());

This should return a list of UsageStats for the years 2012 and 2013 (keep in mind the end time is exclusive of the end result time range).

Smoking answered 28/10, 2014 at 8:5 Comment(3)
That's weird, as the documentation says : beginTime = 2013 endTime = 2015 . Seems to work now. Why do they need the interval? I give them the range anyway... Also, do you know perhaps how to start the intent of asking the user for this permission ? It seems it's a permission that's manually enabled by the user.Reflexive
Yes, the documentation is not clear at all. It's not very clear why they need an interval along with the start and end time, but from what I can deduce the UsageStatsManger stores data in daily, weekly, monthly and yearly buckets which have a start and end time of their own. Eg, the daily bucket starts at 5:30 AM. So if you provide a daily interval along with the start time of 10/30/2014 00:00 and end time of 10/30/2014 23:59, queryUsageStats will return data for 10/29 and 10/30. I may be wrong but this is what I understood testing on an emulator.Smoking
I didn't understand your understanding . :(Reflexive
L
9

There is actually an example app included in AOSP sample code: developers/samples/android/system/AppUsageStatistics/

It includes all the bits necessary to use UsageStats in an app:

  1. Declaring the permission in AndroidManifest.xml

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

  2. Show settings to grant permission to access UsageStatsManager

    startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));

  3. Query UsageStatsManager for statistics.

    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.YEAR, -1);
    List<UsageStats> queryUsageStats = mUsageStatsManager
            .queryUsageStats(intervalType, cal.getTimeInMillis(),
                    System.currentTimeMillis());
    
  4. Creating a list of apps based on UsageStats

To summarize and apply to your example:

  • You seem to correctly ask for and grant permissions to access usage stats.
  • You correctly get the UsageStats system service.
  • However, the time period you query for is way too short: Arguments beginTime and endTime are measured in milliseconds since the epoch. Calendar instances can give you this value with getTimeinMillis(). What you erroneously do is to only give the year numbers (2015 and2017 if you would run the program today). These values are interpreted as milliseconds since the epoch and thus the interval is only 2 milliseconds long and is some time in 1970.

Instead of the following code snippet that you posted, you should copy the example I posted below:

Wrong:

final int currentYear=Calendar.getInstance().get(Calendar.YEAR);
final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY,currentYear-2,currentYear);

Correct:

final long currentTime = System.currentTimeMillis(); // Get current time in milliseconds

final Calendar cal = Calendar.getInstance();
cal.add(Calendar.YEAR, -2); // Set year to beginning of desired period.
final long beginTime = cal.getTimeInMillis(); // Get begin time in milliseconds

final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY, beginTime, currentTime);
Landaulet answered 2/3, 2017 at 16:12 Comment(22)
What is exactly the "intervalType" , if you've already set the time to be a year before the current one?Reflexive
@android-developer: intervalType is the granularity statistics get aggregated for. Official documentation has more details. There is also a convenience method queryAndAggregateUsageStats(long, long) that tries to automatically find the best interval type.Landaulet
I don't understand. What is there to aggregate here? Doesn't it just show usage statistics? I've looked in UsageStats docs (here: developer.android.com/reference/android/app/usage/… ) and it seems quite simple to me... What would it mean, for example, if I chose a month to aggregate? Would "getLastTimeUsed" return a time only in the range of the last month, and for the second one of the month before it?Reflexive
@android-developer, better look atqueryUsageStats() in UsageStatsManager which you need to query for stats. There you see that UsageStats are returned for each package in each of the intervals. Say you query for a year and want monthly intervals. That means the list could contain 12 UsageStats elements for the very same package (if the app is used in each of the 12 months in the interval.Landaulet
So it is as I wrote?Reflexive
@android-developer, no, it isn't. It depends on the values that you pass for beginTime and endTime as well as the instance of UsageStats that you look at. Please post a new question if you need more clarification of the documentation of UsageStatsManager.queryUsageStats(). These comments are the wrong place to do that. I'd be happy to elaborate in the larger space of proper answer.Landaulet
But that's the question I've posted "How to use UsageStatsManager" .Reflexive
@android-developer, I have added some more information regarding your code example. It seems you pass the wrong values for beginTime and endTime. Hopefully, with the new information you can solve your problem. Let me know if anything stays unclear.Landaulet
One other thing: As mentioned already: using queryAndAggregateUsageStats(long, long) might actually be the better solution for you because it gives you results for the entire two years for each package.Landaulet
I need the stats of, say, the whole recent year. I want to sort the apps by recent-time-of-launch. For example, if I launched app "A" 3 hours ago, and then app "B" 2 hours ago, and then again "A" 1 hour ago, it means "A will be most recently launched app, and then "B".Reflexive
That is exactly what the sample app that I linked does. Does this screenshot look promising to you? Please have a look at the source of the example app.Landaulet
I don't understand how to import this sample app. The screenshot shows something that I don't need (time span - just one day?) and something that I do (recently launched time). In any case, will the code you've provided here suffice for this? This is the code I use today: postimg.org/image/fu3bcw1lbReflexive
@android-developer, the screenshot shows "daily" in a dropdown menu which lists different time intervals. This is apparent from the example code I linked in my answer. Please have a look at the example, it should be useful even if you cannot get it to compile. I do not believe that comments are the right place for a code review. That said, the code example you linked looks like it should work. If you cannot get it to work, please open a new question with your (new) specific problems. Happy coding!Landaulet
I don't know your exact specifications and it is hard to judge in isolation, but the snippet seems to do what you described above.Landaulet
It's just what I wrote recently. I want to get apps by recently launched time, over a specific durationReflexive
As mentioned before: If anything is still unclear, please post a new question or modify your question with the code you currently use.Landaulet
I didn't understand well?Reflexive
My point is that obviously the comments are not the right place to continue this conversation, since we have failed to solve your issue for many comments. Creating a new question is probably best because the question you have posed initially seems to be solved and you seem to be stuck at a new problem.Landaulet
But it's the same problem. I want to know get the stats of apps (mainly the recently launched time of apps), and I'm not sure what's the purpose of the aggregation .Reflexive
Aggregation gets rid of duplicate list entries for packages by combining information from all entries with the same package name into one entry of the Map. How do you not understand how to get recently launched time? You do exactly that in the code snippet you posted a screenshot of. Here is an example how to get return value of getLastTimeUsed() and format it in a human readable way.Landaulet
Can't I just aggregate to the whole range I provide?Reflexive
That's exactly what queryAndAggregateUsageStats() does ... and you use it already. I will stop responding to comments in this thread unless something new comes up. I believe that your question will be resolved once you read and really tried to understand the example and documentation of UsageStatsManager.Landaulet
P
7

Use UsageStats will get the wrong information when user opens the notification drawer or on a locked screen. You have to use UsageStatsManager.queryEvents() and look for the latest event with MOVE_TO_FOREGROUND event type.

Promotion answered 29/4, 2016 at 14:49 Comment(2)
Why is it wrong? And can you please show code of how it should be, to demonstrate how it's better?Reflexive
I just tested out the problem on a Samsung Galaxy S5 6.0.1 and Motorola Moto G5 Plus 8.1.0. When the screen was locked, the latest UsageStats had a package of android rather than the last-used app. Is that the problem you're referring to?Forfeiture
M
1

If you want to see usage statistics of a specific time period, you have to first calculate the length of time in milliseconds of the start and end of your time period since the epoch. (epoch is the number of seconds that have elapsed since 00:00:00 UTC, Thursday 1, 1970.) Or, as I will show in the following sample code, an easy way is to calculate backwards from the current time in milliseconds.

For example, if you want a usage statistic of the past 4 days, you can use the following code:

UsageStatsManager mUsageStatsManager = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE);    
List<UsageStats> queryUsageStats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
                    (System.currentTimeMillis() - 345600000), System.currentTimeMillis());

The number 345600000 is the number of milliseconds in 4 days.

And for the INTERVAL_TYPE this article explains it well.

The system collects and aggregates the data over 4 different intervals and they are: INTERVAL_DAILY, INTERVAL_WEEKLY, INTERVAL_MONTHLY and INTERVAL_YEARLY. The system records are limited in time, so you’ll be able to retrieve app usage data for up to 7 days for interval daily, up to 4 weeks for interval weekly, up to 6 months for monthly and finally up to 2 years for yearly.

There’s a fifth option to mention: INTERVAL_BEST will choose the best fitting interval between the four above based on the timespan you’ve chosen.

Milurd answered 19/8, 2018 at 20:9 Comment(5)
But you used INTERVAL_TYPEReflexive
So the max we can use is of 6 months? I don't get what's the point of the interval type, if we already give it an interval in exact time...Reflexive
The INTERVAL_TYPE is to help you see stats for your interval of selection. For the above snippet that I used, the stat will show me daily app usage of packages in the last 4 days. If you just want to see usage stats of the interval you specified (the begin and end time) please look into queryAndAggregateUsageStats(long, long). Otherwise, the INTERVAL_TYPE will produce usage stat in one of the 4 interval types (daily, weekly, monthly or yearly).Milurd
Again, I don't get why they had to add to add the extra parameter. why couldn't they just have a single parameter for time, which tells in which period of time to fetch the data from, and that's it. And if there are some restrictions, tell about them...Reflexive
Might be unrelated, but could you please tell why you invoked #getSystemService on this instead of directly calling it?Banian
M
0

you can do like this

//noinspection ResourceType
final UsageStatsManager usageStatsManager=(UsageStatsManager)context.getSystemService("usagestats");// Context.USAGE_STATS_SERVICE);
final int currentYear=Calendar.getInstance().get(Calendar.YEAR);
final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY,currentYear-2,currentYear);
Mannos answered 17/5, 2016 at 13:40 Comment(7)
What does the UsageStatsManager.INTERVAL_YEARLY mean?Reflexive
Provides access to usage history and statistics. Usage data is aggregated into time intervals: over year developer.android.com/reference/android/app/usage/…Mannos
I don't understand. If you want data of the recent 2 years, why is there an interval of one year? How different would the result be if you chose a different interval?Reflexive
if you select INTERVAL_DAILY will show todays stats if you select INTERVAL_MONTHLY will show months stats and so on hope that helpsMannos
But what if I want to show stats of recent 2 years?Reflexive
@androiddeveloper try using INTERVAL_BEST it might be the one you are looking forMannos
Let us continue this discussion in chat.Mannos

© 2022 - 2024 — McMap. All rights reserved.