Merge "Provide app launch count in UsageStats" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
8808edb063
@@ -121,6 +121,7 @@ package android {
|
||||
field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
|
||||
field public static final java.lang.String NOTIFICATION_DURING_SETUP = "android.permission.NOTIFICATION_DURING_SETUP";
|
||||
field public static final java.lang.String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS";
|
||||
field public static final java.lang.String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE";
|
||||
field public static final java.lang.String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
|
||||
field public static final java.lang.String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
|
||||
field public static final java.lang.String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT";
|
||||
@@ -733,6 +734,10 @@ package android.app.usage {
|
||||
field public static final int SYSTEM_INTERACTION = 6; // 0x6
|
||||
}
|
||||
|
||||
public final class UsageStats implements android.os.Parcelable {
|
||||
method public int getAppLaunchCount();
|
||||
}
|
||||
|
||||
public final class UsageStatsManager {
|
||||
method public int getAppStandbyBucket(java.lang.String);
|
||||
method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.app.usage;
|
||||
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@@ -58,6 +59,11 @@ public final class UsageStats implements Parcelable {
|
||||
*/
|
||||
public int mLaunchCount;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
public int mAppLaunchCount;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
@@ -81,6 +87,7 @@ public final class UsageStats implements Parcelable {
|
||||
mLastTimeUsed = stats.mLastTimeUsed;
|
||||
mTotalTimeInForeground = stats.mTotalTimeInForeground;
|
||||
mLaunchCount = stats.mLaunchCount;
|
||||
mAppLaunchCount = stats.mAppLaunchCount;
|
||||
mLastEvent = stats.mLastEvent;
|
||||
mChooserCounts = stats.mChooserCounts;
|
||||
}
|
||||
@@ -136,6 +143,16 @@ public final class UsageStats implements Parcelable {
|
||||
return mTotalTimeInForeground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of times the app was launched as an activity from outside of the app.
|
||||
* Excludes intra-app activity transitions.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public int getAppLaunchCount() {
|
||||
return mAppLaunchCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the statistics from the right {@link UsageStats} to the left. The package name for
|
||||
* both {@link UsageStats} objects must be the same.
|
||||
@@ -161,6 +178,7 @@ public final class UsageStats implements Parcelable {
|
||||
mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
|
||||
mTotalTimeInForeground += right.mTotalTimeInForeground;
|
||||
mLaunchCount += right.mLaunchCount;
|
||||
mAppLaunchCount += right.mAppLaunchCount;
|
||||
if (mChooserCounts == null) {
|
||||
mChooserCounts = right.mChooserCounts;
|
||||
} else if (right.mChooserCounts != null) {
|
||||
@@ -196,6 +214,7 @@ public final class UsageStats implements Parcelable {
|
||||
dest.writeLong(mLastTimeUsed);
|
||||
dest.writeLong(mTotalTimeInForeground);
|
||||
dest.writeInt(mLaunchCount);
|
||||
dest.writeInt(mAppLaunchCount);
|
||||
dest.writeInt(mLastEvent);
|
||||
Bundle allCounts = new Bundle();
|
||||
if (mChooserCounts != null) {
|
||||
@@ -224,6 +243,7 @@ public final class UsageStats implements Parcelable {
|
||||
stats.mLastTimeUsed = in.readLong();
|
||||
stats.mTotalTimeInForeground = in.readLong();
|
||||
stats.mLaunchCount = in.readInt();
|
||||
stats.mAppLaunchCount = in.readInt();
|
||||
stats.mLastEvent = in.readInt();
|
||||
Bundle allCounts = in.readBundle();
|
||||
if (allCounts != null) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package android.app.usage;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
@@ -518,26 +519,28 @@ public final class UsageStatsManager {
|
||||
/**
|
||||
* @hide
|
||||
* Register an app usage limit observer that receives a callback on the provided intent when
|
||||
* the sum of usages of apps in the packages array exceeds the timeLimit specified. The
|
||||
* the sum of usages of apps in the packages array exceeds the {@code timeLimit} specified. The
|
||||
* observer will automatically be unregistered when the time limit is reached and the intent
|
||||
* is delivered.
|
||||
* is delivered. Registering an {@code observerId} that was already registered will override
|
||||
* the previous one.
|
||||
* @param observerId A unique id associated with the group of apps to be monitored. There can
|
||||
* be multiple groups with common packages and different time limits.
|
||||
* @param packages The list of packages to observe for foreground activity time. Must include
|
||||
* at least one package.
|
||||
* @param packages The list of packages to observe for foreground activity time. Cannot be null
|
||||
* and must include at least one package.
|
||||
* @param timeLimit The total time the set of apps can be in the foreground before the
|
||||
* callbackIntent is delivered. Must be greater than 0.
|
||||
* @param timeUnit The unit for time specified in timeLimit.
|
||||
* @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
|
||||
* @param callbackIntent The PendingIntent that will be dispatched when the time limit is
|
||||
* exceeded by the group of apps. The delivered Intent will also contain
|
||||
* the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
|
||||
* {@link #EXTRA_TIME_USED}.
|
||||
* @throws SecurityException if the caller doesn't have the PACKAGE_USAGE_STATS permission.
|
||||
* {@link #EXTRA_TIME_USED}. Cannot be null.
|
||||
* @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or
|
||||
* is not the profile owner of this user.
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
|
||||
public void registerAppUsageObserver(int observerId, String[] packages, long timeLimit,
|
||||
TimeUnit timeUnit, PendingIntent callbackIntent) {
|
||||
@RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
|
||||
public void registerAppUsageObserver(int observerId, @NonNull String[] packages, long timeLimit,
|
||||
@NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
|
||||
try {
|
||||
mService.registerAppUsageObserver(observerId, packages, timeUnit.toMillis(timeLimit),
|
||||
callbackIntent, mContext.getOpPackageName());
|
||||
@@ -547,14 +550,15 @@ public final class UsageStatsManager {
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Unregister the app usage observer specified by the observerId. This will only apply to any
|
||||
* observer registered by this application. Unregistering an observer that was already
|
||||
* Unregister the app usage observer specified by the {@code observerId}. This will only apply
|
||||
* to any observer registered by this application. Unregistering an observer that was already
|
||||
* unregistered or never registered will have no effect.
|
||||
* @param observerId The id of the observer that was previously registered.
|
||||
* @throws SecurityException if the caller doesn't have the PACKAGE_USAGE_STATS permission.
|
||||
* @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or is
|
||||
* not the profile owner of this user.
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
|
||||
@RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
|
||||
public void unregisterAppUsageObserver(int observerId) {
|
||||
try {
|
||||
mService.unregisterAppUsageObserver(observerId, mContext.getOpPackageName());
|
||||
|
||||
@@ -3290,6 +3290,11 @@
|
||||
android:protectionLevel="signature|privileged|development|appop" />
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
|
||||
|
||||
<!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register
|
||||
for callbacks when apps reach a certain usage time limit, etc. -->
|
||||
<permission android:name="android.permission.OBSERVE_APP_USAGE"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- @hide @SystemApi Allows an application to change the app idle state of an app.
|
||||
<p>Not for use by third-party applications. -->
|
||||
<permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
|
||||
|
||||
@@ -71,16 +71,13 @@ public class AppTimeLimitController {
|
||||
/** The time when the current app came to the foreground */
|
||||
private long currentForegroundedTime;
|
||||
|
||||
/** The last app that was in the background */
|
||||
private String lastBackgroundedPackage;
|
||||
|
||||
/** Map from package name for quick lookup */
|
||||
private ArrayMap<String, ArrayList<TimeLimitGroup>> packageMap = new ArrayMap<>();
|
||||
|
||||
/** Map of observerId to details of the time limit group */
|
||||
private SparseArray<TimeLimitGroup> groups = new SparseArray<>();
|
||||
|
||||
UserData(@UserIdInt int userId) {
|
||||
private UserData(@UserIdInt int userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
@@ -114,7 +111,7 @@ public class AppTimeLimitController {
|
||||
int userId;
|
||||
}
|
||||
|
||||
class MyHandler extends Handler {
|
||||
private class MyHandler extends Handler {
|
||||
|
||||
static final int MSG_CHECK_TIMEOUT = 1;
|
||||
static final int MSG_INFORM_LISTENER = 2;
|
||||
@@ -151,7 +148,7 @@ public class AppTimeLimitController {
|
||||
}
|
||||
|
||||
/** Returns an existing UserData object for the given userId, or creates one */
|
||||
UserData getOrCreateUserDataLocked(int userId) {
|
||||
private UserData getOrCreateUserDataLocked(int userId) {
|
||||
UserData userData = mUsers.get(userId);
|
||||
if (userData == null) {
|
||||
userData = new UserData(userId);
|
||||
@@ -258,12 +255,6 @@ public class AppTimeLimitController {
|
||||
user.currentForegroundedPackage = packageName;
|
||||
user.currentForegroundedTime = getUptimeMillis();
|
||||
|
||||
// Check if the last package that was backgrounded is the same as this one
|
||||
if (!TextUtils.equals(packageName, user.lastBackgroundedPackage)) {
|
||||
// TODO: Move this logic up to usage stats to persist there.
|
||||
incTotalLaunchesLocked(user, packageName);
|
||||
}
|
||||
|
||||
// Check if any of the groups need to watch for this package
|
||||
maybeWatchForPackageLocked(user, packageName, user.currentForegroundedTime);
|
||||
}
|
||||
@@ -279,7 +270,6 @@ public class AppTimeLimitController {
|
||||
public void moveToBackground(String packageName, String className, int userId) {
|
||||
synchronized (mLock) {
|
||||
UserData user = getOrCreateUserDataLocked(userId);
|
||||
user.lastBackgroundedPackage = packageName;
|
||||
if (!TextUtils.equals(user.currentForegroundedPackage, packageName)) {
|
||||
Slog.w(TAG, "Eh? Last foregrounded package = " + user.currentForegroundedPackage
|
||||
+ " and now backgrounded = " + packageName);
|
||||
@@ -433,10 +423,6 @@ public class AppTimeLimitController {
|
||||
}
|
||||
}
|
||||
|
||||
private void incTotalLaunchesLocked(UserData user, String packageName) {
|
||||
// TODO: Inform UsageStatsService and aggregate the counter per app
|
||||
}
|
||||
|
||||
void dump(PrintWriter pw) {
|
||||
synchronized (mLock) {
|
||||
pw.println("\n App Time Limits");
|
||||
@@ -457,7 +443,6 @@ public class AppTimeLimitController {
|
||||
pw.println();
|
||||
pw.print(" currentForegroundedPackage=");
|
||||
pw.println(user.currentForegroundedPackage);
|
||||
pw.print(" lastBackgroundedPackage="); pw.println(user.lastBackgroundedPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,11 @@ class IntervalStats {
|
||||
endTime = timeStamp;
|
||||
}
|
||||
|
||||
void incrementAppLaunchCount(String packageName) {
|
||||
UsageStats usageStats = getOrCreateUsageStats(packageName);
|
||||
usageStats.mAppLaunchCount += 1;
|
||||
}
|
||||
|
||||
private String getCachedStringRef(String str) {
|
||||
final int index = mStringCache.indexOf(str);
|
||||
if (index < 0) {
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.app.ActivityManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IUidObserver;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.admin.DeviceAdminInfo;
|
||||
import android.app.admin.DevicePolicyManagerInternal;
|
||||
import android.app.usage.AppStandbyInfo;
|
||||
import android.app.usage.ConfigurationStats;
|
||||
import android.app.usage.IUsageStatsManager;
|
||||
@@ -110,6 +112,7 @@ public class UsageStatsService extends SystemService implements
|
||||
PackageManager mPackageManager;
|
||||
PackageManagerInternal mPackageManagerInternal;
|
||||
IDeviceIdleController mDeviceIdleController;
|
||||
DevicePolicyManagerInternal mDpmInternal;
|
||||
|
||||
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
|
||||
private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
|
||||
@@ -117,8 +120,10 @@ public class UsageStatsService extends SystemService implements
|
||||
long mRealTimeSnapshot;
|
||||
long mSystemTimeSnapshot;
|
||||
|
||||
/** Manages the standby state of apps. */
|
||||
AppStandbyController mAppStandby;
|
||||
|
||||
/** Manages app time limit observers */
|
||||
AppTimeLimitController mAppTimeLimit;
|
||||
|
||||
private UsageStatsManagerInternal.AppIdleStateChangeListener mStandbyChangeListener =
|
||||
@@ -151,6 +156,7 @@ public class UsageStatsService extends SystemService implements
|
||||
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
|
||||
mPackageManager = getContext().getPackageManager();
|
||||
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
||||
mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
|
||||
mHandler = new H(BackgroundThread.get().getLooper());
|
||||
|
||||
mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
|
||||
@@ -647,6 +653,19 @@ public class UsageStatsService extends SystemService implements
|
||||
return mode == AppOpsManager.MODE_ALLOWED;
|
||||
}
|
||||
|
||||
private boolean hasObserverPermission(String callingPackage) {
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
if (callingUid == Process.SYSTEM_UID
|
||||
|| (mDpmInternal != null
|
||||
&& mDpmInternal.isActiveAdminWithPolicy(callingUid,
|
||||
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))) {
|
||||
// Caller is the system or the profile owner, so proceed.
|
||||
return true;
|
||||
}
|
||||
return getContext().checkCallingPermission(Manifest.permission.OBSERVE_APP_USAGE)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
|
||||
long endTime, String callingPackage) {
|
||||
@@ -821,8 +840,8 @@ public class UsageStatsService extends SystemService implements
|
||||
} catch (RemoteException re) {
|
||||
throw re.rethrowFromSystemServer();
|
||||
}
|
||||
final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
|
||||
final int reason = shellCaller
|
||||
final boolean systemCaller = UserHandle.isCore(callingUid);
|
||||
final int reason = systemCaller
|
||||
? UsageStatsManager.REASON_MAIN_FORCED
|
||||
: UsageStatsManager.REASON_MAIN_PREDICTED;
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
@@ -963,8 +982,8 @@ public class UsageStatsService extends SystemService implements
|
||||
public void registerAppUsageObserver(int observerId,
|
||||
String[] packages, long timeLimitMs, PendingIntent
|
||||
callbackIntent, String callingPackage) {
|
||||
if (!hasPermission(callingPackage)) {
|
||||
throw new SecurityException("Caller doesn't have PACKAGE_USAGE_STATS permission");
|
||||
if (!hasObserverPermission(callingPackage)) {
|
||||
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
|
||||
}
|
||||
|
||||
if (packages == null || packages.length == 0) {
|
||||
@@ -989,8 +1008,8 @@ public class UsageStatsService extends SystemService implements
|
||||
|
||||
@Override
|
||||
public void unregisterAppUsageObserver(int observerId, String callingPackage) {
|
||||
if (!hasPermission(callingPackage)) {
|
||||
throw new SecurityException("Caller doesn't have PACKAGE_USAGE_STATS permission");
|
||||
if (!hasObserverPermission(callingPackage)) {
|
||||
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
|
||||
}
|
||||
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
|
||||
@@ -62,6 +62,7 @@ final class UsageStatsXmlV1 {
|
||||
private static final String TYPE_ATTR = "type";
|
||||
private static final String SHORTCUT_ID_ATTR = "shortcutId";
|
||||
private static final String STANDBY_BUCKET_ATTR = "standbyBucket";
|
||||
private static final String APP_LAUNCH_COUNT_ATTR = "appLaunchCount";
|
||||
|
||||
// Time attributes stored as an offset of the beginTime.
|
||||
private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
|
||||
@@ -81,6 +82,7 @@ final class UsageStatsXmlV1 {
|
||||
parser, LAST_TIME_ACTIVE_ATTR);
|
||||
stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
|
||||
stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
|
||||
stats.mAppLaunchCount = XmlUtils.readIntAttribute(parser, APP_LAUNCH_COUNT_ATTR, 0);
|
||||
int eventCode;
|
||||
while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
final String tag = parser.getName();
|
||||
@@ -193,6 +195,9 @@ final class UsageStatsXmlV1 {
|
||||
XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
|
||||
XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
|
||||
XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
|
||||
if (usageStats.mAppLaunchCount > 0) {
|
||||
XmlUtils.writeIntAttribute(xml, APP_LAUNCH_COUNT_ATTR, usageStats.mAppLaunchCount);
|
||||
}
|
||||
writeChooserCounts(xml, usageStats);
|
||||
xml.endTag(null, PACKAGE_TAG);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class UserUsageStatsService {
|
||||
private final UnixCalendar mDailyExpiryDate;
|
||||
private final StatsUpdatedListener mListener;
|
||||
private final String mLogPrefix;
|
||||
private String mLastBackgroundedPackage;
|
||||
private final int mUserId;
|
||||
|
||||
private static final long[] INTERVAL_LENGTH = new long[] {
|
||||
@@ -178,6 +179,17 @@ class UserUsageStatsService {
|
||||
currentDailyStats.events.put(event.mTimeStamp, event);
|
||||
}
|
||||
|
||||
boolean incrementAppLaunch = false;
|
||||
if (event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
|
||||
if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) {
|
||||
incrementAppLaunch = true;
|
||||
}
|
||||
} else if (event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND) {
|
||||
if (event.mPackage != null) {
|
||||
mLastBackgroundedPackage = event.mPackage;
|
||||
}
|
||||
}
|
||||
|
||||
for (IntervalStats stats : mCurrentStats) {
|
||||
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
|
||||
stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
|
||||
@@ -191,6 +203,9 @@ class UserUsageStatsService {
|
||||
}
|
||||
} else {
|
||||
stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
|
||||
if (incrementAppLaunch) {
|
||||
stats.incrementAppLaunchCount(event.mPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +664,7 @@ class UserUsageStatsService {
|
||||
pw.printPair("totalTime",
|
||||
formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
|
||||
pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
|
||||
pw.printPair("appLaunchCount", usageStats.mAppLaunchCount);
|
||||
pw.println();
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
>
|
||||
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
|
||||
<uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
|
||||
|
||||
<application android:label="Usage Access Test">
|
||||
<activity android:name=".UsageStatsActivity"
|
||||
|
||||
Reference in New Issue
Block a user