From: Amith Yamasani Date: Wed, 21 Mar 2018 02:37:46 +0000 (-0700) Subject: Provide app launch count in UsageStats X-Git-Tag: android-x86-9.0-r1~163^2~54^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=bc813eb26e3027856114a26312e36e4bad86bd86;p=android-x86%2Fframeworks-base.git Provide app launch count in UsageStats This counts the number of times the app was launched from outside the app and ignores intra-app activity transitions. Introduce a new permission for registering to observe app usage. Fixes a bug where Settings couldn't force the app into another bucket if it was recently launched. Bug: 74335821 Fixes: 76100712 Test: Manual test using Settings Test: UsageStatsTest to verify permission change Change-Id: Ibd343c1cfa37089a3ac6fc30ba3194e21a9be499 --- diff --git a/api/system-current.txt b/api/system-current.txt index 1080669e6ca5..fb24dcb195e1 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -118,6 +118,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"; @@ -726,6 +727,10 @@ package android.app.usage { field public static final int NOTIFICATION_SEEN = 10; // 0xa } + 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 getAppStandbyBuckets(); diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index 7eef85c44139..2b14841fb34b 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -16,6 +16,7 @@ package android.app.usage; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -61,6 +62,11 @@ public final class UsageStats implements Parcelable { /** * {@hide} */ + public int mAppLaunchCount; + + /** + * {@hide} + */ public int mLastEvent; /** @@ -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; } @@ -137,6 +144,16 @@ public final class UsageStats implements Parcelable { } /** + * 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. * @param right The {@link UsageStats} object to merge into this one. @@ -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) { diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 59f001c5a7c3..642c5b4df940 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -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; @@ -500,26 +501,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()); @@ -529,14 +532,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()); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 9b11a33593bd..64c50ca6daa4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3274,6 +3274,11 @@ android:protectionLevel="signature|privileged|development|appop" /> + + + > packageMap = new ArrayMap<>(); /** Map of observerId to details of the time limit group */ private SparseArray 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); } } } diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 4d458b027429..00826e057a86 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -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) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index b144545c693a..b8317b470d01 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -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 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 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(); diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 5e7e80db410b..bcfc42169374 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -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); } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index d9742825ac70..2452d139dc86 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -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(); diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml index 66af45424fba..4b1c1bd69920 100644 --- a/tests/UsageStatsTest/AndroidManifest.xml +++ b/tests/UsageStatsTest/AndroidManifest.xml @@ -10,6 +10,7 @@ > +