Revert "Revert "Add basic launch time prediction.""
This reverts commit 3785977ca6f5e4fd34c1dd26a53841144e063a3e. Reason for revert: Fixing Change-Id: I31425d8086e3f1d1d806b84e9339d0acd3f420d4
This commit is contained in:
parent
3785977ca6
commit
4d0d1dd6e8
@ -1,5 +1,6 @@
|
|||||||
package com.android.server.usage;
|
package com.android.server.usage;
|
||||||
|
|
||||||
|
import android.annotation.CurrentTimeMillisLong;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.UserIdInt;
|
import android.annotation.UserIdInt;
|
||||||
import android.app.usage.AppStandbyInfo;
|
import android.app.usage.AppStandbyInfo;
|
||||||
@ -23,7 +24,7 @@ public interface AppStandbyInternal {
|
|||||||
try {
|
try {
|
||||||
final Class<?> clazz = Class.forName("com.android.server.usage.AppStandbyController",
|
final Class<?> clazz = Class.forName("com.android.server.usage.AppStandbyController",
|
||||||
true, loader);
|
true, loader);
|
||||||
final Constructor<?> ctor = clazz.getConstructor(Context.class);
|
final Constructor<?> ctor = clazz.getConstructor(Context.class);
|
||||||
return (AppStandbyInternal) ctor.newInstance(context);
|
return (AppStandbyInternal) ctor.newInstance(context);
|
||||||
} catch (NoSuchMethodException | InstantiationException
|
} catch (NoSuchMethodException | InstantiationException
|
||||||
| IllegalAccessException | InvocationTargetException | ClassNotFoundException e) {
|
| IllegalAccessException | InvocationTargetException | ClassNotFoundException e) {
|
||||||
@ -71,6 +72,16 @@ public interface AppStandbyInternal {
|
|||||||
|
|
||||||
long getTimeSinceLastJobRun(String packageName, int userId);
|
long getTimeSinceLastJobRun(String packageName, int userId);
|
||||||
|
|
||||||
|
void setEstimatedLaunchTime(String packageName, int userId,
|
||||||
|
@CurrentTimeMillisLong long launchTimeMs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the saved estimated launch time for the app. Will return {@code Long#MAX_VALUE} if no
|
||||||
|
* value is saved.
|
||||||
|
*/
|
||||||
|
@CurrentTimeMillisLong
|
||||||
|
long getEstimatedLaunchTime(String packageName, int userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the time (in milliseconds) since the app was last interacted with by the user.
|
* Returns the time (in milliseconds) since the app was last interacted with by the user.
|
||||||
* This can be larger than the current elapsedRealtime, in case it happened before boot or
|
* This can be larger than the current elapsedRealtime, in case it happened before boot or
|
||||||
|
@ -25,8 +25,12 @@ import static com.android.server.job.controllers.Package.packageToString;
|
|||||||
import android.annotation.CurrentTimeMillisLong;
|
import android.annotation.CurrentTimeMillisLong;
|
||||||
import android.annotation.ElapsedRealtimeLong;
|
import android.annotation.ElapsedRealtimeLong;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.app.usage.UsageStatsManagerInternal;
|
||||||
|
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
@ -38,7 +42,9 @@ import android.util.TimeUtils;
|
|||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.internal.os.SomeArgs;
|
||||||
import com.android.server.JobSchedulerBackgroundThread;
|
import com.android.server.JobSchedulerBackgroundThread;
|
||||||
|
import com.android.server.LocalServices;
|
||||||
import com.android.server.job.JobSchedulerService;
|
import com.android.server.job.JobSchedulerService;
|
||||||
import com.android.server.utils.AlarmQueue;
|
import com.android.server.utils.AlarmQueue;
|
||||||
|
|
||||||
@ -53,6 +59,9 @@ public class PrefetchController extends StateController {
|
|||||||
|| Log.isLoggable(TAG, Log.DEBUG);
|
|| Log.isLoggable(TAG, Log.DEBUG);
|
||||||
|
|
||||||
private final PcConstants mPcConstants;
|
private final PcConstants mPcConstants;
|
||||||
|
private final PcHandler mHandler;
|
||||||
|
|
||||||
|
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private final SparseArrayMap<String, ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>();
|
private final SparseArrayMap<String, ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>();
|
||||||
@ -72,11 +81,34 @@ public class PrefetchController extends StateController {
|
|||||||
@CurrentTimeMillisLong
|
@CurrentTimeMillisLong
|
||||||
private long mLaunchTimeThresholdMs = PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS;
|
private long mLaunchTimeThresholdMs = PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS;
|
||||||
|
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
|
private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener =
|
||||||
|
new EstimatedLaunchTimeChangedListener() {
|
||||||
|
@Override
|
||||||
|
public void onEstimatedLaunchTimeChanged(int userId, @NonNull String packageName,
|
||||||
|
@CurrentTimeMillisLong long newEstimatedLaunchTime) {
|
||||||
|
final SomeArgs args = SomeArgs.obtain();
|
||||||
|
args.arg1 = packageName;
|
||||||
|
args.argi1 = userId;
|
||||||
|
args.argl1 = newEstimatedLaunchTime;
|
||||||
|
mHandler.obtainMessage(MSG_PROCESS_UPDATED_ESTIMATED_LAUNCH_TIME, args)
|
||||||
|
.sendToTarget();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final int MSG_RETRIEVE_ESTIMATED_LAUNCH_TIME = 0;
|
||||||
|
private static final int MSG_PROCESS_UPDATED_ESTIMATED_LAUNCH_TIME = 1;
|
||||||
|
|
||||||
public PrefetchController(JobSchedulerService service) {
|
public PrefetchController(JobSchedulerService service) {
|
||||||
super(service);
|
super(service);
|
||||||
mPcConstants = new PcConstants();
|
mPcConstants = new PcConstants();
|
||||||
|
mHandler = new PcHandler(mContext.getMainLooper());
|
||||||
mThresholdAlarmListener = new ThresholdAlarmListener(
|
mThresholdAlarmListener = new ThresholdAlarmListener(
|
||||||
mContext, JobSchedulerBackgroundThread.get().getLooper());
|
mContext, JobSchedulerBackgroundThread.get().getLooper());
|
||||||
|
mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
|
||||||
|
|
||||||
|
mUsageStatsManagerInternal
|
||||||
|
.registerLaunchTimeChangedListener(mEstimatedLaunchTimeChangedListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -146,11 +178,14 @@ public class PrefetchController extends StateController {
|
|||||||
@CurrentTimeMillisLong
|
@CurrentTimeMillisLong
|
||||||
private long getNextEstimatedLaunchTimeLocked(int userId, @NonNull String pkgName,
|
private long getNextEstimatedLaunchTimeLocked(int userId, @NonNull String pkgName,
|
||||||
@CurrentTimeMillisLong long now) {
|
@CurrentTimeMillisLong long now) {
|
||||||
Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
|
final Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
|
||||||
if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
|
if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
|
||||||
// TODO(194532703): get estimated time from UsageStats
|
// Don't query usage stats here because it may have to read from disk.
|
||||||
nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
|
mHandler.obtainMessage(MSG_RETRIEVE_ESTIMATED_LAUNCH_TIME, userId, 0, pkgName)
|
||||||
mEstimatedLaunchTimes.add(userId, pkgName, nextEstimatedLaunchTime);
|
.sendToTarget();
|
||||||
|
// Store something in the cache so we don't keep posting retrieval messages.
|
||||||
|
mEstimatedLaunchTimes.add(userId, pkgName, Long.MAX_VALUE);
|
||||||
|
return Long.MAX_VALUE;
|
||||||
}
|
}
|
||||||
return nextEstimatedLaunchTime;
|
return nextEstimatedLaunchTime;
|
||||||
}
|
}
|
||||||
@ -170,6 +205,42 @@ public class PrefetchController extends StateController {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processUpdatedEstimatedLaunchTime(int userId, @NonNull String pkgName,
|
||||||
|
@CurrentTimeMillisLong long newEstimatedLaunchTime) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Estimated launch time for " + packageToString(userId, pkgName)
|
||||||
|
+ " changed to " + newEstimatedLaunchTime
|
||||||
|
+ " ("
|
||||||
|
+ TimeUtils.formatDuration(newEstimatedLaunchTime - sSystemClock.millis())
|
||||||
|
+ " from now)");
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (mLock) {
|
||||||
|
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
|
||||||
|
if (jobs == null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.i(TAG,
|
||||||
|
"Not caching launch time since we haven't seen any prefetch"
|
||||||
|
+ " jobs for " + packageToString(userId, pkgName));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Don't bother caching the value unless the app has scheduled prefetch jobs
|
||||||
|
// before. This is based on the assumption that if an app has scheduled a
|
||||||
|
// prefetch job before, then it will probably schedule another one again.
|
||||||
|
mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime);
|
||||||
|
|
||||||
|
if (!jobs.isEmpty()) {
|
||||||
|
final long now = sSystemClock.millis();
|
||||||
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
|
updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
|
||||||
|
if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
|
||||||
|
mStateChangedListener.onControllerStateChanged(jobs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private boolean updateConstraintLocked(@NonNull JobStatus jobStatus,
|
private boolean updateConstraintLocked(@NonNull JobStatus jobStatus,
|
||||||
@CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
|
@CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
|
||||||
@ -289,6 +360,49 @@ public class PrefetchController extends StateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PcHandler extends Handler {
|
||||||
|
PcHandler(Looper looper) {
|
||||||
|
super(looper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_RETRIEVE_ESTIMATED_LAUNCH_TIME:
|
||||||
|
final int userId = msg.arg1;
|
||||||
|
final String pkgName = (String) msg.obj;
|
||||||
|
// It's okay to get the time without holding the lock since all updates to
|
||||||
|
// the local cache go through the handler (and therefore will be sequential).
|
||||||
|
final long nextEstimatedLaunchTime = mUsageStatsManagerInternal
|
||||||
|
.getEstimatedPackageLaunchTime(pkgName, userId);
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Retrieved launch time for "
|
||||||
|
+ packageToString(userId, pkgName)
|
||||||
|
+ " of " + nextEstimatedLaunchTime
|
||||||
|
+ " (" + TimeUtils.formatDuration(
|
||||||
|
nextEstimatedLaunchTime - sSystemClock.millis())
|
||||||
|
+ " from now)");
|
||||||
|
}
|
||||||
|
synchronized (mLock) {
|
||||||
|
final Long curEstimatedLaunchTime =
|
||||||
|
mEstimatedLaunchTimes.get(userId, pkgName);
|
||||||
|
if (curEstimatedLaunchTime == null
|
||||||
|
|| nextEstimatedLaunchTime != curEstimatedLaunchTime) {
|
||||||
|
processUpdatedEstimatedLaunchTime(
|
||||||
|
userId, pkgName, nextEstimatedLaunchTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MSG_PROCESS_UPDATED_ESTIMATED_LAUNCH_TIME:
|
||||||
|
final SomeArgs args = (SomeArgs) msg.obj;
|
||||||
|
processUpdatedEstimatedLaunchTime(args.argi1, (String) args.arg1, args.argl1);
|
||||||
|
args.recycle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
class PcConstants {
|
class PcConstants {
|
||||||
private boolean mShouldReevaluateConstraints = false;
|
private boolean mShouldReevaluateConstraints = false;
|
||||||
@ -366,7 +480,8 @@ public class PrefetchController extends StateController {
|
|||||||
final String pkgName = mEstimatedLaunchTimes.keyAt(u, p);
|
final String pkgName = mEstimatedLaunchTimes.keyAt(u, p);
|
||||||
final long estimatedLaunchTime = mEstimatedLaunchTimes.valueAt(u, p);
|
final long estimatedLaunchTime = mEstimatedLaunchTimes.valueAt(u, p);
|
||||||
|
|
||||||
pw.print("<" + userId + ">" + pkgName + ": ");
|
pw.print(packageToString(userId, pkgName));
|
||||||
|
pw.print(": ");
|
||||||
pw.print(estimatedLaunchTime);
|
pw.print(estimatedLaunchTime);
|
||||||
pw.print(" (");
|
pw.print(" (");
|
||||||
TimeUtils.formatDuration(estimatedLaunchTime - now, pw,
|
TimeUtils.formatDuration(estimatedLaunchTime - now, pw,
|
||||||
|
@ -32,6 +32,8 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
|
|||||||
|
|
||||||
import static com.android.server.usage.AppStandbyController.isUserUsage;
|
import static com.android.server.usage.AppStandbyController.isUserUsage;
|
||||||
|
|
||||||
|
import android.annotation.CurrentTimeMillisLong;
|
||||||
|
import android.annotation.ElapsedRealtimeLong;
|
||||||
import android.app.usage.AppStandbyInfo;
|
import android.app.usage.AppStandbyInfo;
|
||||||
import android.app.usage.UsageStatsManager;
|
import android.app.usage.UsageStatsManager;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
@ -115,6 +117,8 @@ public class AppIdleHistory {
|
|||||||
// Reason why the app was last marked for restriction.
|
// Reason why the app was last marked for restriction.
|
||||||
private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON =
|
private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON =
|
||||||
"lastRestrictionAttemptReason";
|
"lastRestrictionAttemptReason";
|
||||||
|
// The next estimated launch time of the app, in ms since epoch.
|
||||||
|
private static final String ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME = "nextEstimatedAppLaunchTime";
|
||||||
|
|
||||||
// device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
|
// device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
|
||||||
private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
|
private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
|
||||||
@ -151,6 +155,9 @@ public class AppIdleHistory {
|
|||||||
int lastInformedBucket;
|
int lastInformedBucket;
|
||||||
// The last time a job was run for this app, using elapsed timebase
|
// The last time a job was run for this app, using elapsed timebase
|
||||||
long lastJobRunTime;
|
long lastJobRunTime;
|
||||||
|
// The estimated time the app will be launched next, in milliseconds since epoch.
|
||||||
|
@CurrentTimeMillisLong
|
||||||
|
long nextEstimatedLaunchTime;
|
||||||
// When should the bucket active state timeout, in elapsed timebase, if greater than
|
// When should the bucket active state timeout, in elapsed timebase, if greater than
|
||||||
// lastUsedElapsedTime.
|
// lastUsedElapsedTime.
|
||||||
// This is used to keep the app in a high bucket regardless of other timeouts and
|
// This is used to keep the app in a high bucket regardless of other timeouts and
|
||||||
@ -410,6 +417,17 @@ public class AppIdleHistory {
|
|||||||
app.lastPredictedBucket = bucket;
|
app.lastPredictedBucket = bucket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the next time the app is expected to be launched, in the current millis timebase.
|
||||||
|
*/
|
||||||
|
public void setEstimatedLaunchTime(String packageName, int userId,
|
||||||
|
@ElapsedRealtimeLong long nowElapsed, @CurrentTimeMillisLong long launchTime) {
|
||||||
|
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
|
||||||
|
AppUsageHistory appUsageHistory =
|
||||||
|
getPackageHistory(userHistory, packageName, nowElapsed, true);
|
||||||
|
appUsageHistory.nextEstimatedLaunchTime = launchTime;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the last time a job was run, with the given elapsedRealtime. The time stored is
|
* Marks the last time a job was run, with the given elapsedRealtime. The time stored is
|
||||||
* based on the elapsed timebase.
|
* based on the elapsed timebase.
|
||||||
@ -442,6 +460,23 @@ public class AppIdleHistory {
|
|||||||
appUsageHistory.lastRestrictReason = reason;
|
appUsageHistory.lastRestrictReason = reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next estimated launch time of this app. Will return {@link Long#MAX_VALUE} if
|
||||||
|
* there's no estimated time.
|
||||||
|
*/
|
||||||
|
@CurrentTimeMillisLong
|
||||||
|
public long getEstimatedLaunchTime(String packageName, int userId, long nowElapsed) {
|
||||||
|
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
|
||||||
|
AppUsageHistory appUsageHistory =
|
||||||
|
getPackageHistory(userHistory, packageName, nowElapsed, false);
|
||||||
|
// Don't adjust the default, else it'll wrap around to a positive value
|
||||||
|
if (appUsageHistory == null
|
||||||
|
|| appUsageHistory.nextEstimatedLaunchTime < System.currentTimeMillis()) {
|
||||||
|
return Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
return appUsageHistory.nextEstimatedLaunchTime;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the time since the last job was run for this app. This can be larger than the
|
* Returns the time since the last job was run for this app. This can be larger than the
|
||||||
* current elapsedRealtime, in case it happened before boot or a really large value if no jobs
|
* current elapsedRealtime, in case it happened before boot or a really large value if no jobs
|
||||||
@ -671,6 +706,8 @@ public class AppIdleHistory {
|
|||||||
Slog.wtf(TAG, "Unable to read last restrict reason", nfe);
|
Slog.wtf(TAG, "Unable to read last restrict reason", nfe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
appUsageHistory.nextEstimatedLaunchTime = getLongValue(parser,
|
||||||
|
ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 0);
|
||||||
appUsageHistory.lastInformedBucket = -1;
|
appUsageHistory.lastInformedBucket = -1;
|
||||||
userHistory.put(packageName, appUsageHistory);
|
userHistory.put(packageName, appUsageHistory);
|
||||||
}
|
}
|
||||||
@ -753,6 +790,10 @@ public class AppIdleHistory {
|
|||||||
}
|
}
|
||||||
xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON,
|
xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON,
|
||||||
Integer.toHexString(history.lastRestrictReason));
|
Integer.toHexString(history.lastRestrictReason));
|
||||||
|
if (history.nextEstimatedLaunchTime > 0) {
|
||||||
|
xml.attribute(null, ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME,
|
||||||
|
Long.toString(history.nextEstimatedLaunchTime));
|
||||||
|
}
|
||||||
xml.endTag(null, TAG_PACKAGE);
|
xml.endTag(null, TAG_PACKAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -779,6 +820,7 @@ public class AppIdleHistory {
|
|||||||
idpw.println(" App Standby States:");
|
idpw.println(" App Standby States:");
|
||||||
idpw.increaseIndent();
|
idpw.increaseIndent();
|
||||||
ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
|
ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
final long elapsedRealtime = SystemClock.elapsedRealtime();
|
final long elapsedRealtime = SystemClock.elapsedRealtime();
|
||||||
final long totalElapsedTime = getElapsedTime(elapsedRealtime);
|
final long totalElapsedTime = getElapsedTime(elapsedRealtime);
|
||||||
final long screenOnTime = getScreenOnTime(elapsedRealtime);
|
final long screenOnTime = getScreenOnTime(elapsedRealtime);
|
||||||
@ -819,6 +861,10 @@ public class AppIdleHistory {
|
|||||||
idpw.print(" lastRestrictReason="
|
idpw.print(" lastRestrictReason="
|
||||||
+ UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason));
|
+ UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason));
|
||||||
}
|
}
|
||||||
|
if (appUsageHistory.nextEstimatedLaunchTime > 0) {
|
||||||
|
idpw.print(" nextEstimatedLaunchTime=");
|
||||||
|
TimeUtils.formatDuration(appUsageHistory.nextEstimatedLaunchTime - now, idpw);
|
||||||
|
}
|
||||||
idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
|
idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
|
||||||
idpw.println();
|
idpw.println();
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
|||||||
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
|
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
|
||||||
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
|
|
||||||
|
import android.annotation.CurrentTimeMillisLong;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.UserIdInt;
|
import android.annotation.UserIdInt;
|
||||||
@ -1086,6 +1087,24 @@ public class AppStandbyController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEstimatedLaunchTime(String packageName, int userId,
|
||||||
|
@CurrentTimeMillisLong long launchTime) {
|
||||||
|
final long nowElapsed = mInjector.elapsedRealtime();
|
||||||
|
synchronized (mAppIdleLock) {
|
||||||
|
mAppIdleHistory.setEstimatedLaunchTime(packageName, userId, nowElapsed, launchTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CurrentTimeMillisLong
|
||||||
|
public long getEstimatedLaunchTime(String packageName, int userId) {
|
||||||
|
final long elapsedRealtime = mInjector.elapsedRealtime();
|
||||||
|
synchronized (mAppIdleLock) {
|
||||||
|
return mAppIdleHistory.getEstimatedLaunchTime(packageName, userId, elapsedRealtime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getTimeSinceLastUsedByUser(String packageName, int userId) {
|
public long getTimeSinceLastUsedByUser(String packageName, int userId) {
|
||||||
final long elapsedRealtime = mInjector.elapsedRealtime();
|
final long elapsedRealtime = mInjector.elapsedRealtime();
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package android.app.usage;
|
package android.app.usage;
|
||||||
|
|
||||||
|
import android.annotation.CurrentTimeMillisLong;
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.SystemApi;
|
import android.annotation.SystemApi;
|
||||||
@ -584,6 +585,7 @@ public final class UsageEvents implements Parcelable {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* See {@link System#currentTimeMillis()}.
|
* See {@link System#currentTimeMillis()}.
|
||||||
*/
|
*/
|
||||||
|
@CurrentTimeMillisLong
|
||||||
public long getTimeStamp() {
|
public long getTimeStamp() {
|
||||||
return mTimeStamp;
|
return mTimeStamp;
|
||||||
}
|
}
|
||||||
@ -801,6 +803,9 @@ public final class UsageEvents implements Parcelable {
|
|||||||
* @return true if an event was available, false if there are no more events.
|
* @return true if an event was available, false if there are no more events.
|
||||||
*/
|
*/
|
||||||
public boolean getNextEvent(Event eventOut) {
|
public boolean getNextEvent(Event eventOut) {
|
||||||
|
if (eventOut == null) {
|
||||||
|
throw new IllegalArgumentException("Given eventOut must not be null");
|
||||||
|
}
|
||||||
if (mIndex >= mEventCount) {
|
if (mIndex >= mEventCount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package android.app.usage;
|
package android.app.usage;
|
||||||
|
|
||||||
|
import android.annotation.CurrentTimeMillisLong;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.UserIdInt;
|
import android.annotation.UserIdInt;
|
||||||
@ -234,6 +235,10 @@ public abstract class UsageStatsManagerInternal {
|
|||||||
public abstract void setLastJobRunTime(String packageName, @UserIdInt int userId,
|
public abstract void setLastJobRunTime(String packageName, @UserIdInt int userId,
|
||||||
long elapsedRealtime);
|
long elapsedRealtime);
|
||||||
|
|
||||||
|
/** Returns the estimated time that the app will be launched, in milliseconds since epoch. */
|
||||||
|
@CurrentTimeMillisLong
|
||||||
|
public abstract long getEstimatedPackageLaunchTime(String packageName, @UserIdInt int userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the time in millis since a job was executed for this app, in elapsed realtime
|
* Returns the time in millis since a job was executed for this app, in elapsed realtime
|
||||||
* timebase. This value can be larger than the current elapsed realtime if the job was executed
|
* timebase. This value can be larger than the current elapsed realtime if the job was executed
|
||||||
@ -340,4 +345,21 @@ public abstract class UsageStatsManagerInternal {
|
|||||||
|
|
||||||
/** Unregister a listener from being notified of every new usage event. */
|
/** Unregister a listener from being notified of every new usage event. */
|
||||||
public abstract void unregisterListener(@NonNull UsageEventListener listener);
|
public abstract void unregisterListener(@NonNull UsageEventListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface for estimated launch time changes.
|
||||||
|
*/
|
||||||
|
public interface EstimatedLaunchTimeChangedListener {
|
||||||
|
/** Callback to inform listeners when estimated launch times change. */
|
||||||
|
void onEstimatedLaunchTimeChanged(@UserIdInt int userId, @NonNull String packageName,
|
||||||
|
@CurrentTimeMillisLong long newEstimatedLaunchTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Register a listener that will be notified of every estimated launch time change. */
|
||||||
|
public abstract void registerLaunchTimeChangedListener(
|
||||||
|
@NonNull EstimatedLaunchTimeChangedListener listener);
|
||||||
|
|
||||||
|
/** Unregister a listener from being notified of every estimated launch time change. */
|
||||||
|
public abstract void unregisterLaunchTimeChangedListener(
|
||||||
|
@NonNull EstimatedLaunchTimeChangedListener listener);
|
||||||
}
|
}
|
||||||
|
@ -20,15 +20,33 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
|||||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||||
|
|
||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
|
||||||
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||||
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
|
||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
|
||||||
|
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||||
|
import static com.android.server.job.JobSchedulerService.sSystemClock;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.timeout;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.job.JobInfo;
|
||||||
|
import android.app.usage.UsageStatsManagerInternal;
|
||||||
|
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
|
|
||||||
@ -42,7 +60,9 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.ArgumentMatchers;
|
import org.mockito.ArgumentMatchers;
|
||||||
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoSession;
|
import org.mockito.MockitoSession;
|
||||||
import org.mockito.quality.Strictness;
|
import org.mockito.quality.Strictness;
|
||||||
@ -55,15 +75,26 @@ import java.util.concurrent.Executor;
|
|||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class PrefetchControllerTest {
|
public class PrefetchControllerTest {
|
||||||
|
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
|
||||||
|
private static final int SOURCE_USER_ID = 0;
|
||||||
|
private static final int CALLING_UID = 1000;
|
||||||
|
private static final long DEFAULT_WAIT_MS = 3000;
|
||||||
|
private static final String TAG_PREFETCH = "*job.prefetch*";
|
||||||
|
|
||||||
private PrefetchController mPrefetchController;
|
private PrefetchController mPrefetchController;
|
||||||
private PcConstants mPcConstants;
|
private PcConstants mPcConstants;
|
||||||
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
|
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
|
||||||
|
private EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener;
|
||||||
|
|
||||||
private MockitoSession mMockingSession;
|
private MockitoSession mMockingSession;
|
||||||
@Mock
|
@Mock
|
||||||
|
private AlarmManager mAlarmManager;
|
||||||
|
@Mock
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@Mock
|
@Mock
|
||||||
private JobSchedulerService mJobSchedulerService;
|
private JobSchedulerService mJobSchedulerService;
|
||||||
|
@Mock
|
||||||
|
private UsageStatsManagerInternal mUsageStatsManagerInternal;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@ -77,6 +108,11 @@ public class PrefetchControllerTest {
|
|||||||
// Called in StateController constructor.
|
// Called in StateController constructor.
|
||||||
when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
|
when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
|
||||||
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
|
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
|
||||||
|
// Called in PrefetchController constructor.
|
||||||
|
doReturn(mUsageStatsManagerInternal)
|
||||||
|
.when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
|
||||||
|
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
|
||||||
|
when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
|
||||||
// Used in PrefetchController.PcConstants
|
// Used in PrefetchController.PcConstants
|
||||||
doAnswer((Answer<Void>) invocationOnMock -> null)
|
doAnswer((Answer<Void>) invocationOnMock -> null)
|
||||||
.when(() -> DeviceConfig.addOnPropertiesChangedListener(
|
.when(() -> DeviceConfig.addOnPropertiesChangedListener(
|
||||||
@ -93,7 +129,7 @@ public class PrefetchControllerTest {
|
|||||||
// Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
|
// Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
|
||||||
// in the past, and PrefetchController sometimes floors values at 0, so if the test time
|
// in the past, and PrefetchController sometimes floors values at 0, so if the test time
|
||||||
// causes sessions with negative timestamps, they will fail.
|
// causes sessions with negative timestamps, they will fail.
|
||||||
JobSchedulerService.sSystemClock =
|
sSystemClock =
|
||||||
getShiftedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
|
getShiftedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
|
||||||
24 * HOUR_IN_MILLIS);
|
24 * HOUR_IN_MILLIS);
|
||||||
JobSchedulerService.sUptimeMillisClock = getShiftedClock(
|
JobSchedulerService.sUptimeMillisClock = getShiftedClock(
|
||||||
@ -105,8 +141,14 @@ public class PrefetchControllerTest {
|
|||||||
|
|
||||||
// Initialize real objects.
|
// Initialize real objects.
|
||||||
// Capture the listeners.
|
// Capture the listeners.
|
||||||
|
ArgumentCaptor<EstimatedLaunchTimeChangedListener> eltListenerCaptor =
|
||||||
|
ArgumentCaptor.forClass(EstimatedLaunchTimeChangedListener.class);
|
||||||
mPrefetchController = new PrefetchController(mJobSchedulerService);
|
mPrefetchController = new PrefetchController(mJobSchedulerService);
|
||||||
mPcConstants = mPrefetchController.getPcConstants();
|
mPcConstants = mPrefetchController.getPcConstants();
|
||||||
|
|
||||||
|
verify(mUsageStatsManagerInternal)
|
||||||
|
.registerLaunchTimeChangedListener(eltListenerCaptor.capture());
|
||||||
|
mEstimatedLaunchTimeChangedListener = eltListenerCaptor.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -116,6 +158,29 @@ public class PrefetchControllerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JobStatus createJobStatus(String testTag, int jobId) {
|
||||||
|
JobInfo jobInfo = new JobInfo.Builder(jobId,
|
||||||
|
new ComponentName(mContext, "TestPrefetchJobService"))
|
||||||
|
.setPrefetch(true)
|
||||||
|
.build();
|
||||||
|
return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JobStatus createJobStatus(String testTag, String packageName, int callingUid,
|
||||||
|
JobInfo jobInfo) {
|
||||||
|
JobStatus js = JobStatus.createFromJobInfo(
|
||||||
|
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
|
||||||
|
js.serviceInfo = mock(ServiceInfo.class);
|
||||||
|
// Make sure Doze and background-not-restricted don't affect tests.
|
||||||
|
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
|
||||||
|
/* state */ true, /* allowlisted */false);
|
||||||
|
js.setBackgroundNotRestrictedConstraintSatisfied(
|
||||||
|
sElapsedRealtimeClock.millis(), true, false);
|
||||||
|
js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
|
||||||
|
js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true);
|
||||||
|
return js;
|
||||||
|
}
|
||||||
|
|
||||||
private Clock getShiftedClock(Clock clock, long incrementMs) {
|
private Clock getShiftedClock(Clock clock, long incrementMs) {
|
||||||
return Clock.offset(clock, Duration.ofMillis(incrementMs));
|
return Clock.offset(clock, Duration.ofMillis(incrementMs));
|
||||||
}
|
}
|
||||||
@ -125,6 +190,15 @@ public class PrefetchControllerTest {
|
|||||||
synchronized (mPrefetchController.mLock) {
|
synchronized (mPrefetchController.mLock) {
|
||||||
mPrefetchController.prepareForUpdatedConstantsLocked();
|
mPrefetchController.prepareForUpdatedConstantsLocked();
|
||||||
mPcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
|
mPcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
|
||||||
|
mPrefetchController.onConstantsUpdatedLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trackJobs(JobStatus... jobs) {
|
||||||
|
for (JobStatus job : jobs) {
|
||||||
|
synchronized (mPrefetchController.mLock) {
|
||||||
|
mPrefetchController.maybeStartTrackingJobLocked(job, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,4 +221,100 @@ public class PrefetchControllerTest {
|
|||||||
|
|
||||||
assertEquals(24 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
|
assertEquals(24 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConstantsUpdating_ThresholdChangesAlarms() {
|
||||||
|
final long launchDelayMs = 11 * HOUR_IN_MILLIS;
|
||||||
|
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
|
||||||
|
when(mUsageStatsManagerInternal
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
|
||||||
|
.thenReturn(sSystemClock.millis() + launchDelayMs);
|
||||||
|
JobStatus jobStatus = createJobStatus("testConstantsUpdating_ThresholdChangesAlarms", 1);
|
||||||
|
trackJobs(jobStatus);
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mAlarmManager);
|
||||||
|
|
||||||
|
inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
|
||||||
|
.setWindow(
|
||||||
|
anyInt(), eq(sElapsedRealtimeClock.millis() + 4 * HOUR_IN_MILLIS),
|
||||||
|
anyLong(), eq(TAG_PREFETCH), any(), any());
|
||||||
|
|
||||||
|
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 3 * HOUR_IN_MILLIS);
|
||||||
|
inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
|
||||||
|
.setWindow(
|
||||||
|
anyInt(), eq(sElapsedRealtimeClock.millis() + 8 * HOUR_IN_MILLIS),
|
||||||
|
anyLong(), eq(TAG_PREFETCH), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConstraintNotSatisfiedWhenLaunchLate() {
|
||||||
|
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
|
||||||
|
|
||||||
|
final JobStatus job = createJobStatus("testConstraintNotSatisfiedWhenLaunchLate", 1);
|
||||||
|
when(mUsageStatsManagerInternal
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
|
||||||
|
.thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
|
||||||
|
trackJobs(job);
|
||||||
|
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||||
|
assertFalse(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConstraintSatisfiedWhenLaunchSoon() {
|
||||||
|
final JobStatus job = createJobStatus("testConstraintSatisfiedWhenLaunchSoon", 2);
|
||||||
|
when(mUsageStatsManagerInternal
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
|
||||||
|
.thenReturn(sSystemClock.millis() + MINUTE_IN_MILLIS);
|
||||||
|
trackJobs(job);
|
||||||
|
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||||
|
assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEstimatedLaunchTimeChangedToLate() {
|
||||||
|
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
|
||||||
|
when(mUsageStatsManagerInternal
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
|
||||||
|
.thenReturn(sSystemClock.millis() + HOUR_IN_MILLIS);
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mUsageStatsManagerInternal);
|
||||||
|
|
||||||
|
JobStatus jobStatus = createJobStatus("testEstimatedLaunchTimeChangedToLate", 1);
|
||||||
|
trackJobs(jobStatus);
|
||||||
|
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||||
|
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
|
||||||
|
|
||||||
|
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
|
||||||
|
SOURCE_PACKAGE, sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
|
||||||
|
|
||||||
|
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||||
|
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEstimatedLaunchTimeChangedToSoon() {
|
||||||
|
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
|
||||||
|
when(mUsageStatsManagerInternal
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
|
||||||
|
.thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mUsageStatsManagerInternal);
|
||||||
|
|
||||||
|
JobStatus jobStatus = createJobStatus("testEstimatedLaunchTimeChangedToSoon", 1);
|
||||||
|
trackJobs(jobStatus);
|
||||||
|
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||||
|
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
|
||||||
|
|
||||||
|
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
|
||||||
|
SOURCE_PACKAGE, sSystemClock.millis() + MINUTE_IN_MILLIS);
|
||||||
|
|
||||||
|
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
|
||||||
|
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||||
|
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,11 @@ package com.android.server.usage;
|
|||||||
|
|
||||||
import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
|
import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
|
||||||
import static android.app.usage.UsageEvents.Event.APP_COMPONENT_USED;
|
import static android.app.usage.UsageEvents.Event.APP_COMPONENT_USED;
|
||||||
|
import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Mockito.mockitoSession;
|
import static org.mockito.Mockito.mockitoSession;
|
||||||
|
|
||||||
@ -28,6 +31,7 @@ import android.app.usage.UsageEvents.Event;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
@ -47,6 +51,8 @@ import java.util.HashMap;
|
|||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class UserUsageStatsServiceTest {
|
public class UserUsageStatsServiceTest {
|
||||||
|
private static final String TAG = UserUsageStatsServiceTest.class.getSimpleName();
|
||||||
|
|
||||||
private static final int TEST_USER_ID = 0;
|
private static final int TEST_USER_ID = 0;
|
||||||
private static final String TEST_PACKAGE_NAME = "test.package";
|
private static final String TEST_PACKAGE_NAME = "test.package";
|
||||||
private static final long TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
|
private static final long TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
|
||||||
@ -54,6 +60,8 @@ public class UserUsageStatsServiceTest {
|
|||||||
private UserUsageStatsService mService;
|
private UserUsageStatsService mService;
|
||||||
private MockitoSession mMockitoSession;
|
private MockitoSession mMockitoSession;
|
||||||
|
|
||||||
|
private File mDir;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@Mock
|
@Mock
|
||||||
@ -66,8 +74,11 @@ public class UserUsageStatsServiceTest {
|
|||||||
.strictness(Strictness.LENIENT)
|
.strictness(Strictness.LENIENT)
|
||||||
.startMocking();
|
.startMocking();
|
||||||
|
|
||||||
File dir = new File(InstrumentationRegistry.getContext().getCacheDir(), "test");
|
// Deleting in tearDown() doesn't always work, so adding a unique suffix to each test
|
||||||
mService = new UserUsageStatsService(mContext, TEST_USER_ID, dir, mStatsUpdatedListener);
|
// directory to ensure sequential test runs don't interfere with each other.
|
||||||
|
mDir = new File(InstrumentationRegistry.getContext().getCacheDir(),
|
||||||
|
"test_" + System.currentTimeMillis());
|
||||||
|
mService = new UserUsageStatsService(mContext, TEST_USER_ID, mDir, mStatsUpdatedListener);
|
||||||
|
|
||||||
HashMap<String, Long> installedPkgs = new HashMap<>();
|
HashMap<String, Long> installedPkgs = new HashMap<>();
|
||||||
installedPkgs.put(TEST_PACKAGE_NAME, System.currentTimeMillis());
|
installedPkgs.put(TEST_PACKAGE_NAME, System.currentTimeMillis());
|
||||||
@ -77,6 +88,9 @@ public class UserUsageStatsServiceTest {
|
|||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
|
if (mDir != null && mDir.exists() && !mDir.delete()) {
|
||||||
|
Log.d(TAG, "Failed to delete test directory");
|
||||||
|
}
|
||||||
if (mMockitoSession != null) {
|
if (mMockitoSession != null) {
|
||||||
mMockitoSession.finishMocking();
|
mMockitoSession.finishMocking();
|
||||||
}
|
}
|
||||||
@ -88,6 +102,9 @@ public class UserUsageStatsServiceTest {
|
|||||||
event.mPackage = TEST_PACKAGE_NAME;
|
event.mPackage = TEST_PACKAGE_NAME;
|
||||||
mService.reportEvent(event);
|
mService.reportEvent(event);
|
||||||
|
|
||||||
|
// Force persist the event instead of waiting for it to be processed on the handler.
|
||||||
|
mService.persistActiveStats();
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long startTime = now - TIME_INTERVAL_MILLIS;
|
long startTime = now - TIME_INTERVAL_MILLIS;
|
||||||
UsageEvents events = mService.queryEventsForPackage(
|
UsageEvents events = mService.queryEventsForPackage(
|
||||||
@ -112,6 +129,9 @@ public class UserUsageStatsServiceTest {
|
|||||||
event.mPackage = TEST_PACKAGE_NAME;
|
event.mPackage = TEST_PACKAGE_NAME;
|
||||||
mService.reportEvent(event);
|
mService.reportEvent(event);
|
||||||
|
|
||||||
|
// Force persist the event instead of waiting for it to be processed on the handler.
|
||||||
|
mService.persistActiveStats();
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long startTime = now - TIME_INTERVAL_MILLIS;
|
long startTime = now - TIME_INTERVAL_MILLIS;
|
||||||
UsageEvents events = mService.queryEventsForPackage(
|
UsageEvents events = mService.queryEventsForPackage(
|
||||||
@ -127,4 +147,36 @@ public class UserUsageStatsServiceTest {
|
|||||||
}
|
}
|
||||||
assertFalse(hasTestEvent);
|
assertFalse(hasTestEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQueryEarliestEventsForPackage() {
|
||||||
|
Event event1 = new Event(NOTIFICATION_SEEN, SystemClock.elapsedRealtime());
|
||||||
|
event1.mPackage = TEST_PACKAGE_NAME;
|
||||||
|
mService.reportEvent(event1);
|
||||||
|
Event event2 = new Event(ACTIVITY_RESUMED, SystemClock.elapsedRealtime());
|
||||||
|
event2.mPackage = TEST_PACKAGE_NAME;
|
||||||
|
mService.reportEvent(event2);
|
||||||
|
|
||||||
|
// Force persist the events instead of waiting for them to be processed on the handler.
|
||||||
|
mService.persistActiveStats();
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long startTime = now - TIME_INTERVAL_MILLIS;
|
||||||
|
UsageEvents events = mService.queryEarliestEventsForPackage(
|
||||||
|
startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
|
||||||
|
|
||||||
|
assertNotNull(events);
|
||||||
|
boolean hasTestEvent = false;
|
||||||
|
int count = 0;
|
||||||
|
while (events.hasNextEvent()) {
|
||||||
|
count++;
|
||||||
|
Event outEvent = new Event();
|
||||||
|
events.getNextEvent(outEvent);
|
||||||
|
if (outEvent.mEventType == ACTIVITY_RESUMED) {
|
||||||
|
hasTestEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasTestEvent);
|
||||||
|
assertEquals(2, count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,9 +67,10 @@ public class UsageStatsDatabaseTest {
|
|||||||
private static final UsageStatsDatabase.StatCombiner<IntervalStats> mIntervalStatsVerifier =
|
private static final UsageStatsDatabase.StatCombiner<IntervalStats> mIntervalStatsVerifier =
|
||||||
new UsageStatsDatabase.StatCombiner<IntervalStats>() {
|
new UsageStatsDatabase.StatCombiner<IntervalStats>() {
|
||||||
@Override
|
@Override
|
||||||
public void combine(IntervalStats stats, boolean mutable,
|
public boolean combine(IntervalStats stats, boolean mutable,
|
||||||
List<IntervalStats> accResult) {
|
List<IntervalStats> accResult) {
|
||||||
accResult.add(stats);
|
accResult.add(stats);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -799,8 +799,10 @@ public class UsageStatsDatabase {
|
|||||||
* @param stats The {@link IntervalStats} object selected.
|
* @param stats The {@link IntervalStats} object selected.
|
||||||
* @param mutable Whether or not the data inside the stats object is mutable.
|
* @param mutable Whether or not the data inside the stats object is mutable.
|
||||||
* @param accumulatedResult The list to which to add extracted data.
|
* @param accumulatedResult The list to which to add extracted data.
|
||||||
|
* @return Whether or not to continue providing new stats to this combiner. If {@code false}
|
||||||
|
* is returned, then combine will no longer be called.
|
||||||
*/
|
*/
|
||||||
void combine(IntervalStats stats, boolean mutable, List<T> accumulatedResult);
|
boolean combine(IntervalStats stats, boolean mutable, List<T> accumulatedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -863,8 +865,9 @@ public class UsageStatsDatabase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
readLocked(f, stats);
|
readLocked(f, stats);
|
||||||
if (beginTime < stats.endTime) {
|
if (beginTime < stats.endTime
|
||||||
combiner.combine(stats, false, results);
|
&& !combiner.combine(stats, false, results)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Slog.e(TAG, "Failed to read usage stats file", e);
|
Slog.e(TAG, "Failed to read usage stats file", e);
|
||||||
|
@ -29,8 +29,10 @@ import static android.app.usage.UsageEvents.Event.USER_STOPPED;
|
|||||||
import static android.app.usage.UsageEvents.Event.USER_UNLOCKED;
|
import static android.app.usage.UsageEvents.Event.USER_UNLOCKED;
|
||||||
import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
|
import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
|
||||||
import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
|
import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
|
||||||
|
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.annotation.CurrentTimeMillisLong;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.UserIdInt;
|
import android.annotation.UserIdInt;
|
||||||
@ -84,6 +86,7 @@ import android.util.Slog;
|
|||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.content.PackageMonitor;
|
import com.android.internal.content.PackageMonitor;
|
||||||
import com.android.internal.os.BackgroundThread;
|
import com.android.internal.os.BackgroundThread;
|
||||||
@ -94,6 +97,7 @@ import com.android.internal.util.IndentingPrintWriter;
|
|||||||
import com.android.server.LocalServices;
|
import com.android.server.LocalServices;
|
||||||
import com.android.server.SystemService;
|
import com.android.server.SystemService;
|
||||||
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
|
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
|
||||||
|
import com.android.server.utils.AlarmQueue;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
@ -134,8 +138,15 @@ public class UsageStatsService extends SystemService implements
|
|||||||
|
|
||||||
private static final long TEN_SECONDS = 10 * 1000;
|
private static final long TEN_SECONDS = 10 * 1000;
|
||||||
private static final long TWENTY_MINUTES = 20 * 60 * 1000;
|
private static final long TWENTY_MINUTES = 20 * 60 * 1000;
|
||||||
|
private static final long ONE_DAY = 24 * HOUR_IN_MILLIS;
|
||||||
|
private static final long ONE_WEEK = 7 * ONE_DAY;
|
||||||
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
|
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
|
||||||
static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
|
static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
|
||||||
|
/**
|
||||||
|
* Used when we can't determine the next app launch time. Assume the app will get launched
|
||||||
|
* this amount of time in the future.
|
||||||
|
*/
|
||||||
|
private static final long UNKNOWN_LAUNCH_TIME_DELAY_MS = 365 * ONE_DAY;
|
||||||
|
|
||||||
private static final boolean ENABLE_KERNEL_UPDATES = true;
|
private static final boolean ENABLE_KERNEL_UPDATES = true;
|
||||||
private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
|
private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
|
||||||
@ -160,6 +171,9 @@ public class UsageStatsService extends SystemService implements
|
|||||||
static final int MSG_UNLOCKED_USER = 5;
|
static final int MSG_UNLOCKED_USER = 5;
|
||||||
static final int MSG_PACKAGE_REMOVED = 6;
|
static final int MSG_PACKAGE_REMOVED = 6;
|
||||||
static final int MSG_ON_START = 7;
|
static final int MSG_ON_START = 7;
|
||||||
|
static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;
|
||||||
|
static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIME_CHANGED = 9;
|
||||||
|
static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 10;
|
||||||
|
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
Handler mHandler;
|
Handler mHandler;
|
||||||
@ -194,8 +208,12 @@ public class UsageStatsService extends SystemService implements
|
|||||||
private final SparseArray<LinkedList<Event>> mReportedEvents = new SparseArray<>();
|
private final SparseArray<LinkedList<Event>> mReportedEvents = new SparseArray<>();
|
||||||
final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
|
final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
|
||||||
final SparseArray<ActivityData> mVisibleActivities = new SparseArray();
|
final SparseArray<ActivityData> mVisibleActivities = new SparseArray();
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final SparseArray<LaunchTimeAlarmQueue> mLaunchTimeAlarmQueues = new SparseArray<>();
|
||||||
private final ArraySet<UsageStatsManagerInternal.UsageEventListener> mUsageEventListeners =
|
private final ArraySet<UsageStatsManagerInternal.UsageEventListener> mUsageEventListeners =
|
||||||
new ArraySet<>();
|
new ArraySet<>();
|
||||||
|
private final CopyOnWriteArraySet<UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener>
|
||||||
|
mEstimatedLaunchTimeChangedListeners = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
private static class ActivityData {
|
private static class ActivityData {
|
||||||
private final String mTaskRootPackage;
|
private final String mTaskRootPackage;
|
||||||
@ -369,6 +387,11 @@ public class UsageStatsService extends SystemService implements
|
|||||||
}
|
}
|
||||||
mUserUnlockedStates.remove(userId);
|
mUserUnlockedStates.remove(userId);
|
||||||
mUserState.put(userId, null); // release the service (mainly for GC)
|
mUserState.put(userId, null); // release the service (mainly for GC)
|
||||||
|
LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
|
||||||
|
if (alarmQueue != null) {
|
||||||
|
alarmQueue.removeAllAlarms();
|
||||||
|
mLaunchTimeAlarmQueues.remove(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,6 +438,8 @@ public class UsageStatsService extends SystemService implements
|
|||||||
}
|
}
|
||||||
reportEvent(unlockEvent, userId);
|
reportEvent(unlockEvent, userId);
|
||||||
|
|
||||||
|
mHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK, userId, 0).sendToTarget();
|
||||||
|
|
||||||
// Remove all the stats stored in memory and in system DE.
|
// Remove all the stats stored in memory and in system DE.
|
||||||
mReportedEvents.remove(userId);
|
mReportedEvents.remove(userId);
|
||||||
deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats"));
|
deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats"));
|
||||||
@ -437,6 +462,7 @@ public class UsageStatsService extends SystemService implements
|
|||||||
* <br/>
|
* <br/>
|
||||||
* Note: DO NOT call this while holding the usage stats lock ({@code mLock}).
|
* Note: DO NOT call this while holding the usage stats lock ({@code mLock}).
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
private HashMap<String, Long> getInstalledPackages(int userId) {
|
private HashMap<String, Long> getInstalledPackages(int userId) {
|
||||||
if (mPackageManager == null) {
|
if (mPackageManager == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -472,6 +498,33 @@ public class UsageStatsService extends SystemService implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LaunchTimeAlarmQueue extends AlarmQueue<String> {
|
||||||
|
private final int mUserId;
|
||||||
|
|
||||||
|
LaunchTimeAlarmQueue(int userId, @NonNull Context context, @NonNull Looper looper) {
|
||||||
|
super(context, looper, "*usage.launchTime*", "Estimated launch times", true, 30_000L);
|
||||||
|
mUserId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isForUser(@NonNull String key, int userId) {
|
||||||
|
return mUserId == userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processExpiredAlarms(@NonNull ArraySet<String> expired) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Processing " + expired.size() + " expired alarms: "
|
||||||
|
+ expired.toString());
|
||||||
|
}
|
||||||
|
if (expired.size() > 0) {
|
||||||
|
mHandler.obtainMessage(
|
||||||
|
MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED, mUserId, 0, expired)
|
||||||
|
.sendToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class UserActionsReceiver extends BroadcastReceiver {
|
private class UserActionsReceiver extends BroadcastReceiver {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@ -522,6 +575,8 @@ public class UsageStatsService extends SystemService implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStatsReloaded() {
|
public void onStatsReloaded() {
|
||||||
|
// This method ends up being called with the lock held, so we need to be careful how we
|
||||||
|
// call into other things.
|
||||||
mAppStandby.postOneTimeCheckIdleStates();
|
mAppStandby.postOneTimeCheckIdleStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,7 +611,7 @@ public class UsageStatsService extends SystemService implements
|
|||||||
/**
|
/**
|
||||||
* Obfuscate both {@link UsageEvents.Event#NOTIFICATION_SEEN} and
|
* Obfuscate both {@link UsageEvents.Event#NOTIFICATION_SEEN} and
|
||||||
* {@link UsageEvents.Event#NOTIFICATION_INTERRUPTION} events if the provided calling uid does
|
* {@link UsageEvents.Event#NOTIFICATION_INTERRUPTION} events if the provided calling uid does
|
||||||
* not hold the {@link android.Manifest.permission.MANAGE_NOTIFICATIONS} permission.
|
* not hold the {@link android.Manifest.permission#MANAGE_NOTIFICATIONS} permission.
|
||||||
*/
|
*/
|
||||||
private boolean shouldObfuscateNotificationEvents(int callingPid, int callingUid) {
|
private boolean shouldObfuscateNotificationEvents(int callingPid, int callingUid) {
|
||||||
if (callingUid == Process.SYSTEM_UID) {
|
if (callingUid == Process.SYSTEM_UID) {
|
||||||
@ -953,6 +1008,23 @@ public class UsageStatsService extends SystemService implements
|
|||||||
event.mTaskRootClass, usageSourcePackage);
|
event.mTaskRootClass, usageSourcePackage);
|
||||||
resumedData.lastEvent = Event.ACTIVITY_RESUMED;
|
resumedData.lastEvent = Event.ACTIVITY_RESUMED;
|
||||||
mVisibleActivities.put(event.mInstanceId, resumedData);
|
mVisibleActivities.put(event.mInstanceId, resumedData);
|
||||||
|
final long estimatedLaunchTime =
|
||||||
|
mAppStandby.getEstimatedLaunchTime(event.mPackage, userId);
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
if (estimatedLaunchTime < now || estimatedLaunchTime > now + ONE_WEEK) {
|
||||||
|
// If the estimated launch time is in the past or more than a week into
|
||||||
|
// the future, then we re-estimate a future launch time of less than a week
|
||||||
|
// from now, so notify listeners of an estimated launch time change.
|
||||||
|
// Clear the cached value.
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, event.getPackageName()
|
||||||
|
+ " app launch resetting future launch estimate");
|
||||||
|
}
|
||||||
|
mAppStandby.setEstimatedLaunchTime(event.mPackage, userId, 0);
|
||||||
|
mHandler.obtainMessage(
|
||||||
|
MSG_NOTIFY_ESTIMATED_LAUNCH_TIME_CHANGED, userId, 0, event.mPackage)
|
||||||
|
.sendToTarget();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Event.ACTIVITY_PAUSED:
|
case Event.ACTIVITY_PAUSED:
|
||||||
ActivityData pausedData = mVisibleActivities.get(event.mInstanceId);
|
ActivityData pausedData = mVisibleActivities.get(event.mInstanceId);
|
||||||
@ -1110,6 +1182,11 @@ public class UsageStatsService extends SystemService implements
|
|||||||
Slog.i(TAG, "Removing user " + userId + " and all data.");
|
Slog.i(TAG, "Removing user " + userId + " and all data.");
|
||||||
mUserState.remove(userId);
|
mUserState.remove(userId);
|
||||||
mAppTimeLimit.onUserRemoved(userId);
|
mAppTimeLimit.onUserRemoved(userId);
|
||||||
|
final LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
|
||||||
|
if (alarmQueue != null) {
|
||||||
|
alarmQueue.removeAllAlarms();
|
||||||
|
mLaunchTimeAlarmQueues.remove(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mAppStandby.onUserRemoved(userId);
|
mAppStandby.onUserRemoved(userId);
|
||||||
// Cancel any scheduled jobs for this user since the user is being removed.
|
// Cancel any scheduled jobs for this user since the user is being removed.
|
||||||
@ -1129,6 +1206,10 @@ public class UsageStatsService extends SystemService implements
|
|||||||
// when the user service is initialized and package manager is queried.
|
// when the user service is initialized and package manager is queried.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
|
||||||
|
if (alarmQueue != null) {
|
||||||
|
alarmQueue.removeAlarmForKey(packageName);
|
||||||
|
}
|
||||||
final UserUsageStatsService userService = mUserState.get(userId);
|
final UserUsageStatsService userService = mUserState.get(userId);
|
||||||
if (userService == null) {
|
if (userService == null) {
|
||||||
return;
|
return;
|
||||||
@ -1274,6 +1355,7 @@ public class UsageStatsService extends SystemService implements
|
|||||||
/**
|
/**
|
||||||
* Called by the Binder stub.
|
* Called by the Binder stub.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
|
UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
|
||||||
String packageName, boolean includeTaskRoot) {
|
String packageName, boolean includeTaskRoot) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
@ -1290,6 +1372,183 @@ public class UsageStatsService extends SystemService implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private UsageEvents queryEarliestAppEvents(int userId, long beginTime, long endTime,
|
||||||
|
int eventType) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (!mUserUnlockedStates.contains(userId)) {
|
||||||
|
Slog.w(TAG, "Failed to query earliest events for locked user " + userId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId);
|
||||||
|
if (service == null) {
|
||||||
|
return null; // user was stopped or removed
|
||||||
|
}
|
||||||
|
return service.queryEarliestAppEvents(beginTime, endTime, eventType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private UsageEvents queryEarliestEventsForPackage(int userId, long beginTime, long endTime,
|
||||||
|
@NonNull String packageName, int eventType) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (!mUserUnlockedStates.contains(userId)) {
|
||||||
|
Slog.w(TAG, "Failed to query earliset package events for locked user " + userId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId);
|
||||||
|
if (service == null) {
|
||||||
|
return null; // user was stopped or removed
|
||||||
|
}
|
||||||
|
return service.queryEarliestEventsForPackage(
|
||||||
|
beginTime, endTime, packageName, eventType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CurrentTimeMillisLong
|
||||||
|
long getEstimatedPackageLaunchTime(int userId, String packageName) {
|
||||||
|
long estimatedLaunchTime = mAppStandby.getEstimatedLaunchTime(packageName, userId);
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
if (estimatedLaunchTime < now || estimatedLaunchTime == Long.MAX_VALUE) {
|
||||||
|
estimatedLaunchTime = calculateEstimatedPackageLaunchTime(userId, packageName);
|
||||||
|
mAppStandby.setEstimatedLaunchTime(packageName, userId, estimatedLaunchTime);
|
||||||
|
|
||||||
|
synchronized (mLock) {
|
||||||
|
LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
|
||||||
|
if (alarmQueue == null) {
|
||||||
|
alarmQueue = new LaunchTimeAlarmQueue(
|
||||||
|
userId, getContext(), BackgroundThread.get().getLooper());
|
||||||
|
mLaunchTimeAlarmQueues.put(userId, alarmQueue);
|
||||||
|
}
|
||||||
|
alarmQueue.addAlarm(packageName,
|
||||||
|
SystemClock.elapsedRealtime() + (estimatedLaunchTime - now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimatedLaunchTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CurrentTimeMillisLong
|
||||||
|
private long calculateEstimatedPackageLaunchTime(int userId, String packageName) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
final long endTime = System.currentTimeMillis();
|
||||||
|
final long beginTime = endTime - ONE_WEEK;
|
||||||
|
final long unknownTime = endTime + UNKNOWN_LAUNCH_TIME_DELAY_MS;
|
||||||
|
final UsageEvents events = queryEarliestEventsForPackage(
|
||||||
|
userId, beginTime, endTime, packageName, Event.ACTIVITY_RESUMED);
|
||||||
|
if (events == null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "No events for " + userId + ":" + packageName);
|
||||||
|
}
|
||||||
|
return unknownTime;
|
||||||
|
}
|
||||||
|
final UsageEvents.Event event = new UsageEvents.Event();
|
||||||
|
final boolean hasMoreThan24HoursOfHistory;
|
||||||
|
if (events.getNextEvent(event)) {
|
||||||
|
hasMoreThan24HoursOfHistory = endTime - event.getTimeStamp() > ONE_DAY;
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, userId + ":" + packageName + " history > 24 hours="
|
||||||
|
+ hasMoreThan24HoursOfHistory);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, userId + ":" + packageName + " has no events");
|
||||||
|
}
|
||||||
|
return unknownTime;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
if (event.getEventType() == Event.ACTIVITY_RESUMED) {
|
||||||
|
final long timestamp = event.getTimeStamp();
|
||||||
|
final long nextLaunch =
|
||||||
|
calculateNextLaunchTime(hasMoreThan24HoursOfHistory, timestamp);
|
||||||
|
if (nextLaunch > endTime) {
|
||||||
|
return nextLaunch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (events.getNextEvent(event));
|
||||||
|
return unknownTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CurrentTimeMillisLong
|
||||||
|
private static long calculateNextLaunchTime(
|
||||||
|
boolean hasMoreThan24HoursOfHistory, long eventTimestamp) {
|
||||||
|
// For our estimates, we assume the user opens an app at consistent times
|
||||||
|
// (ie. like clockwork).
|
||||||
|
// If the app has more than 24 hours of history, then we assume the user will
|
||||||
|
// reopen the app at the same time on a specific day.
|
||||||
|
// If the app has less than 24 hours of history (meaning it was likely just
|
||||||
|
// installed), then we assume the user will open it at exactly the same time
|
||||||
|
// on the following day.
|
||||||
|
if (hasMoreThan24HoursOfHistory) {
|
||||||
|
return eventTimestamp + ONE_WEEK;
|
||||||
|
} else {
|
||||||
|
return eventTimestamp + ONE_DAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEstimatedLaunchTimesOnUserUnlock(int userId) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
final long nowElapsed = SystemClock.elapsedRealtime();
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
final long beginTime = now - ONE_WEEK;
|
||||||
|
final UsageEvents events = queryEarliestAppEvents(
|
||||||
|
userId, beginTime, now, Event.ACTIVITY_RESUMED);
|
||||||
|
if (events == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ArrayMap<String, Boolean> hasMoreThan24HoursOfHistory = new ArrayMap<>();
|
||||||
|
final UsageEvents.Event event = new UsageEvents.Event();
|
||||||
|
LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
|
||||||
|
if (alarmQueue == null) {
|
||||||
|
alarmQueue = new LaunchTimeAlarmQueue(
|
||||||
|
userId, getContext(), BackgroundThread.get().getLooper());
|
||||||
|
mLaunchTimeAlarmQueues.put(userId, alarmQueue);
|
||||||
|
}
|
||||||
|
final ArraySet<String> changedTimes = new ArraySet<>();
|
||||||
|
for (boolean unprocessedEvent = events.getNextEvent(event); unprocessedEvent;
|
||||||
|
unprocessedEvent = events.getNextEvent(event)) {
|
||||||
|
final String packageName = event.getPackageName();
|
||||||
|
if (!hasMoreThan24HoursOfHistory.containsKey(packageName)) {
|
||||||
|
boolean hasHistory = now - event.getTimeStamp() > ONE_DAY;
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG,
|
||||||
|
userId + ":" + packageName + " history > 24 hours=" + hasHistory);
|
||||||
|
}
|
||||||
|
hasMoreThan24HoursOfHistory.put(packageName, hasHistory);
|
||||||
|
}
|
||||||
|
if (event.getEventType() == Event.ACTIVITY_RESUMED) {
|
||||||
|
long estimatedLaunchTime =
|
||||||
|
mAppStandby.getEstimatedLaunchTime(packageName, userId);
|
||||||
|
if (estimatedLaunchTime < now || estimatedLaunchTime == Long.MAX_VALUE) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
estimatedLaunchTime = calculateNextLaunchTime(
|
||||||
|
hasMoreThan24HoursOfHistory.get(packageName), event.getTimeStamp());
|
||||||
|
mAppStandby.setEstimatedLaunchTime(
|
||||||
|
packageName, userId, estimatedLaunchTime);
|
||||||
|
}
|
||||||
|
if (estimatedLaunchTime < now + ONE_WEEK) {
|
||||||
|
// Before a user is unlocked, we don't know when the app will be launched,
|
||||||
|
// so we give callers the UNKNOWN time. Now that we have a better estimate,
|
||||||
|
// we should notify them of the change.
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "User " + userId + " unlock resulting in"
|
||||||
|
+ " estimated launch time change for " + packageName);
|
||||||
|
}
|
||||||
|
changedTimes.add(packageName);
|
||||||
|
}
|
||||||
|
alarmQueue.addAlarm(packageName, nowElapsed + (estimatedLaunchTime - now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changedTimes.size() > 0) {
|
||||||
|
mHandler.obtainMessage(
|
||||||
|
MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED, userId, 0, changedTimes)
|
||||||
|
.sendToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called via the local interface.
|
* Called via the local interface.
|
||||||
*/
|
*/
|
||||||
@ -1309,6 +1568,22 @@ public class UsageStatsService extends SystemService implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called via the local interface.
|
||||||
|
*/
|
||||||
|
private void registerLaunchTimeChangedListener(
|
||||||
|
@NonNull UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener listener) {
|
||||||
|
mEstimatedLaunchTimeChangedListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called via the local interface.
|
||||||
|
*/
|
||||||
|
private void unregisterLaunchTimeChangedListener(
|
||||||
|
@NonNull UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener listener) {
|
||||||
|
mEstimatedLaunchTimeChangedListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
private String buildFullToken(String packageName, String token) {
|
private String buildFullToken(String packageName, String token) {
|
||||||
final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
|
final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
|
||||||
sb.append(packageName);
|
sb.append(packageName);
|
||||||
@ -1564,6 +1839,44 @@ public class UsageStatsService extends SystemService implements
|
|||||||
loadGlobalComponentUsageLocked();
|
loadGlobalComponentUsageLocked();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK: {
|
||||||
|
final int userId = msg.arg1;
|
||||||
|
handleEstimatedLaunchTimesOnUserUnlock(userId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MSG_NOTIFY_ESTIMATED_LAUNCH_TIME_CHANGED: {
|
||||||
|
final int userId = msg.arg1;
|
||||||
|
final String pkgName = (String) msg.obj;
|
||||||
|
final long nextEstimatedLaunchTime =
|
||||||
|
getEstimatedPackageLaunchTime(userId, pkgName);
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Notifying listener for " + userId + ":" + pkgName);
|
||||||
|
}
|
||||||
|
for (UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener listener :
|
||||||
|
mEstimatedLaunchTimeChangedListeners) {
|
||||||
|
listener.onEstimatedLaunchTimeChanged(
|
||||||
|
userId, pkgName, nextEstimatedLaunchTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED: {
|
||||||
|
final int userId = msg.arg1;
|
||||||
|
final ArraySet<String> pkgNames = (ArraySet<String>) msg.obj;
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Notifying listeners for " + userId + "-->" + pkgNames);
|
||||||
|
}
|
||||||
|
for (int p = pkgNames.size() - 1; p >= 0; --p) {
|
||||||
|
final String pkgName = pkgNames.valueAt(p);
|
||||||
|
final long nextEstimatedLaunchTime =
|
||||||
|
getEstimatedPackageLaunchTime(userId, pkgName);
|
||||||
|
for (UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener listener :
|
||||||
|
mEstimatedLaunchTimeChangedListeners) {
|
||||||
|
listener.onEstimatedLaunchTimeChanged(
|
||||||
|
userId, pkgName, nextEstimatedLaunchTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
super.handleMessage(msg);
|
super.handleMessage(msg);
|
||||||
break;
|
break;
|
||||||
@ -2462,6 +2775,11 @@ public class UsageStatsService extends SystemService implements
|
|||||||
mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
|
mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getEstimatedPackageLaunchTime(String packageName, int userId) {
|
||||||
|
return UsageStatsService.this.getEstimatedPackageLaunchTime(userId, packageName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getTimeSinceLastJobRun(String packageName, int userId) {
|
public long getTimeSinceLastJobRun(String packageName, int userId) {
|
||||||
return mAppStandby.getTimeSinceLastJobRun(packageName, userId);
|
return mAppStandby.getTimeSinceLastJobRun(packageName, userId);
|
||||||
@ -2527,6 +2845,18 @@ public class UsageStatsService extends SystemService implements
|
|||||||
public void unregisterListener(@NonNull UsageEventListener listener) {
|
public void unregisterListener(@NonNull UsageEventListener listener) {
|
||||||
UsageStatsService.this.unregisterListener(listener);
|
UsageStatsService.this.unregisterListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerLaunchTimeChangedListener(
|
||||||
|
@NonNull EstimatedLaunchTimeChangedListener listener) {
|
||||||
|
UsageStatsService.this.registerLaunchTimeChangedListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterLaunchTimeChangedListener(
|
||||||
|
@NonNull EstimatedLaunchTimeChangedListener listener) {
|
||||||
|
UsageStatsService.this.unregisterLaunchTimeChangedListener(listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MyPackageMonitor extends PackageMonitor {
|
private class MyPackageMonitor extends PackageMonitor {
|
||||||
|
@ -29,6 +29,8 @@ import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY;
|
|||||||
import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY;
|
import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY;
|
||||||
import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY;
|
import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.app.usage.ConfigurationStats;
|
import android.app.usage.ConfigurationStats;
|
||||||
import android.app.usage.EventList;
|
import android.app.usage.EventList;
|
||||||
import android.app.usage.EventStats;
|
import android.app.usage.EventStats;
|
||||||
@ -365,43 +367,46 @@ class UserUsageStatsService {
|
|||||||
private static final StatCombiner<UsageStats> sUsageStatsCombiner =
|
private static final StatCombiner<UsageStats> sUsageStatsCombiner =
|
||||||
new StatCombiner<UsageStats>() {
|
new StatCombiner<UsageStats>() {
|
||||||
@Override
|
@Override
|
||||||
public void combine(IntervalStats stats, boolean mutable,
|
public boolean combine(IntervalStats stats, boolean mutable,
|
||||||
List<UsageStats> accResult) {
|
List<UsageStats> accResult) {
|
||||||
if (!mutable) {
|
if (!mutable) {
|
||||||
accResult.addAll(stats.packageStats.values());
|
accResult.addAll(stats.packageStats.values());
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int statCount = stats.packageStats.size();
|
final int statCount = stats.packageStats.size();
|
||||||
for (int i = 0; i < statCount; i++) {
|
for (int i = 0; i < statCount; i++) {
|
||||||
accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
|
accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
|
private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
|
||||||
new StatCombiner<ConfigurationStats>() {
|
new StatCombiner<ConfigurationStats>() {
|
||||||
@Override
|
@Override
|
||||||
public void combine(IntervalStats stats, boolean mutable,
|
public boolean combine(IntervalStats stats, boolean mutable,
|
||||||
List<ConfigurationStats> accResult) {
|
List<ConfigurationStats> accResult) {
|
||||||
if (!mutable) {
|
if (!mutable) {
|
||||||
accResult.addAll(stats.configurations.values());
|
accResult.addAll(stats.configurations.values());
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int configCount = stats.configurations.size();
|
final int configCount = stats.configurations.size();
|
||||||
for (int i = 0; i < configCount; i++) {
|
for (int i = 0; i < configCount; i++) {
|
||||||
accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
|
accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final StatCombiner<EventStats> sEventStatsCombiner =
|
private static final StatCombiner<EventStats> sEventStatsCombiner =
|
||||||
new StatCombiner<EventStats>() {
|
new StatCombiner<EventStats>() {
|
||||||
@Override
|
@Override
|
||||||
public void combine(IntervalStats stats, boolean mutable,
|
public boolean combine(IntervalStats stats, boolean mutable,
|
||||||
List<EventStats> accResult) {
|
List<EventStats> accResult) {
|
||||||
stats.addEventStatsTo(accResult);
|
stats.addEventStatsTo(accResult);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -414,6 +419,7 @@ class UserUsageStatsService {
|
|||||||
* and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
|
* and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
|
||||||
* provided to select the stats to use from the IntervalStats object.
|
* provided to select the stats to use from the IntervalStats object.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
|
private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
|
||||||
StatCombiner<T> combiner) {
|
StatCombiner<T> combiner) {
|
||||||
if (intervalType == INTERVAL_BEST) {
|
if (intervalType == INTERVAL_BEST) {
|
||||||
@ -510,16 +516,16 @@ class UserUsageStatsService {
|
|||||||
List<Event> results = queryStats(INTERVAL_DAILY,
|
List<Event> results = queryStats(INTERVAL_DAILY,
|
||||||
beginTime, endTime, new StatCombiner<Event>() {
|
beginTime, endTime, new StatCombiner<Event>() {
|
||||||
@Override
|
@Override
|
||||||
public void combine(IntervalStats stats, boolean mutable,
|
public boolean combine(IntervalStats stats, boolean mutable,
|
||||||
List<Event> accumulatedResult) {
|
List<Event> accumulatedResult) {
|
||||||
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
||||||
final int size = stats.events.size();
|
final int size = stats.events.size();
|
||||||
for (int i = startIndex; i < size; i++) {
|
for (int i = startIndex; i < size; i++) {
|
||||||
if (stats.events.get(i).mTimeStamp >= endTime) {
|
Event event = stats.events.get(i);
|
||||||
return;
|
if (event.mTimeStamp >= endTime) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event event = stats.events.get(i);
|
|
||||||
final int eventType = event.mEventType;
|
final int eventType = event.mEventType;
|
||||||
if (eventType == Event.SHORTCUT_INVOCATION
|
if (eventType == Event.SHORTCUT_INVOCATION
|
||||||
&& (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) {
|
&& (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) {
|
||||||
@ -552,6 +558,7 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
accumulatedResult.add(event);
|
accumulatedResult.add(event);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -564,6 +571,60 @@ class UserUsageStatsService {
|
|||||||
return new UsageEvents(results, table, true);
|
return new UsageEvents(results, table, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link UsageEvents} object whose events list contains only the earliest event seen
|
||||||
|
* for each app as well as the earliest event of {@code eventType} seen for each app.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
UsageEvents queryEarliestAppEvents(final long beginTime, final long endTime,
|
||||||
|
final int eventType) {
|
||||||
|
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final ArraySet<String> names = new ArraySet<>();
|
||||||
|
final ArraySet<String> eventSuccess = new ArraySet<>();
|
||||||
|
final List<Event> results = queryStats(INTERVAL_DAILY,
|
||||||
|
beginTime, endTime, (stats, mutable, accumulatedResult) -> {
|
||||||
|
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
||||||
|
final int size = stats.events.size();
|
||||||
|
for (int i = startIndex; i < size; i++) {
|
||||||
|
final Event event = stats.events.get(i);
|
||||||
|
if (event.getTimeStamp() >= endTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (event.getPackageName() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (eventSuccess.contains(event.getPackageName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean firstEvent = names.add(event.getPackageName());
|
||||||
|
|
||||||
|
if (event.getEventType() == eventType) {
|
||||||
|
accumulatedResult.add(event);
|
||||||
|
eventSuccess.add(event.getPackageName());
|
||||||
|
} else if (firstEvent) {
|
||||||
|
// Save the earliest found event for the app, even if it doesn't match.
|
||||||
|
accumulatedResult.add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results == null || results.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Found " + results.size() + " early events for " + names.size() + " apps");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] table = names.toArray(new String[names.size()]);
|
||||||
|
Arrays.sort(table);
|
||||||
|
return new UsageEvents(results, table, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
|
UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
|
||||||
final String packageName, boolean includeTaskRoot) {
|
final String packageName, boolean includeTaskRoot) {
|
||||||
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
|
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
|
||||||
@ -576,11 +637,11 @@ class UserUsageStatsService {
|
|||||||
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
||||||
final int size = stats.events.size();
|
final int size = stats.events.size();
|
||||||
for (int i = startIndex; i < size; i++) {
|
for (int i = startIndex; i < size; i++) {
|
||||||
if (stats.events.get(i).mTimeStamp >= endTime) {
|
final Event event = stats.events.get(i);
|
||||||
return;
|
if (event.mTimeStamp >= endTime) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Event event = stats.events.get(i);
|
|
||||||
if (!packageName.equals(event.mPackage)) {
|
if (!packageName.equals(event.mPackage)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -595,6 +656,7 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
accumulatedResult.add(event);
|
accumulatedResult.add(event);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (results == null || results.isEmpty()) {
|
if (results == null || results.isEmpty()) {
|
||||||
@ -606,6 +668,48 @@ class UserUsageStatsService {
|
|||||||
return new UsageEvents(results, table, includeTaskRoot);
|
return new UsageEvents(results, table, includeTaskRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link UsageEvents} object whose events list contains only the earliest event seen
|
||||||
|
* for the package as well as the earliest event of {@code eventType} seen for the package.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
UsageEvents queryEarliestEventsForPackage(final long beginTime, final long endTime,
|
||||||
|
@NonNull final String packageName, final int eventType) {
|
||||||
|
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final List<Event> results = queryStats(INTERVAL_DAILY,
|
||||||
|
beginTime, endTime, (stats, mutable, accumulatedResult) -> {
|
||||||
|
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
||||||
|
final int size = stats.events.size();
|
||||||
|
for (int i = startIndex; i < size; i++) {
|
||||||
|
final Event event = stats.events.get(i);
|
||||||
|
if (event.getTimeStamp() >= endTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!packageName.equals(event.getPackageName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event.getEventType() == eventType) {
|
||||||
|
accumulatedResult.add(event);
|
||||||
|
// We've found the earliest of eventType. No need to keep going.
|
||||||
|
return false;
|
||||||
|
} else if (accumulatedResult.size() == 0) {
|
||||||
|
// Save the earliest found event, even if it doesn't match.
|
||||||
|
accumulatedResult.add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results == null || results.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UsageEvents(results, new String[]{packageName}, false);
|
||||||
|
}
|
||||||
|
|
||||||
void persistActiveStats() {
|
void persistActiveStats() {
|
||||||
if (mStatsChanged) {
|
if (mStatsChanged) {
|
||||||
Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
|
Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
|
||||||
@ -876,7 +980,6 @@ class UserUsageStatsService {
|
|||||||
return Long.toString(elapsedTime);
|
return Long.toString(elapsedTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) {
|
void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) {
|
||||||
pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
|
pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
|
||||||
pw.printPair("type", eventToString(event.mEventType));
|
pw.printPair("type", eventToString(event.mEventType));
|
||||||
@ -925,13 +1028,13 @@ class UserUsageStatsService {
|
|||||||
List<Event> events = queryStats(INTERVAL_DAILY,
|
List<Event> events = queryStats(INTERVAL_DAILY,
|
||||||
beginTime, endTime, new StatCombiner<Event>() {
|
beginTime, endTime, new StatCombiner<Event>() {
|
||||||
@Override
|
@Override
|
||||||
public void combine(IntervalStats stats, boolean mutable,
|
public boolean combine(IntervalStats stats, boolean mutable,
|
||||||
List<Event> accumulatedResult) {
|
List<Event> accumulatedResult) {
|
||||||
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
|
||||||
final int size = stats.events.size();
|
final int size = stats.events.size();
|
||||||
for (int i = startIndex; i < size; i++) {
|
for (int i = startIndex; i < size; i++) {
|
||||||
if (stats.events.get(i).mTimeStamp >= endTime) {
|
if (stats.events.get(i).mTimeStamp >= endTime) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event event = stats.events.get(i);
|
Event event = stats.events.get(i);
|
||||||
@ -940,6 +1043,7 @@ class UserUsageStatsService {
|
|||||||
}
|
}
|
||||||
accumulatedResult.add(event);
|
accumulatedResult.add(event);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,12 +62,13 @@ public class UsageStatsDatabasePerfTest {
|
|||||||
private static final StatCombiner<UsageEvents.Event> sUsageStatsCombiner =
|
private static final StatCombiner<UsageEvents.Event> sUsageStatsCombiner =
|
||||||
new StatCombiner<UsageEvents.Event>() {
|
new StatCombiner<UsageEvents.Event>() {
|
||||||
@Override
|
@Override
|
||||||
public void combine(IntervalStats stats, boolean mutable,
|
public boolean combine(IntervalStats stats, boolean mutable,
|
||||||
List<UsageEvents.Event> accResult) {
|
List<UsageEvents.Event> accResult) {
|
||||||
final int size = stats.events.size();
|
final int size = stats.events.size();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
accResult.add(stats.events.get(i));
|
accResult.add(stats.events.get(i));
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user