Merge "Fix QuotaController job spam throttling." into qt-dev
This commit is contained in:
@@ -43,6 +43,13 @@ public class SparseSetArray<T> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all mappings from this SparseSetArray.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
mData.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether a value exists at index n.
|
* @return whether a value exists at index n.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -478,6 +478,54 @@ message StateControllerProto {
|
|||||||
}
|
}
|
||||||
repeated TrackedJob tracked_jobs = 4;
|
repeated TrackedJob tracked_jobs = 4;
|
||||||
|
|
||||||
|
message ExecutionStats {
|
||||||
|
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
|
||||||
|
|
||||||
|
optional JobStatusDumpProto.Bucket standby_bucket = 1;
|
||||||
|
|
||||||
|
// The time after which this record should be considered invalid (out of date), in the
|
||||||
|
// elapsed realtime timebase.
|
||||||
|
optional int64 expiration_time_elapsed = 2;
|
||||||
|
optional int64 window_size_ms = 3;
|
||||||
|
|
||||||
|
/** The total amount of time the app ran in its respective bucket window size. */
|
||||||
|
optional int64 execution_time_in_window_ms = 4;
|
||||||
|
optional int32 bg_job_count_in_window = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total amount of time the app ran in the last
|
||||||
|
* {@link QuotaController#MAX_PERIOD_MS}.
|
||||||
|
*/
|
||||||
|
optional int64 execution_time_in_max_period_ms = 6;
|
||||||
|
optional int32 bg_job_count_in_max_period = 7;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time after which the sum of all the app's sessions plus
|
||||||
|
* ConstantsProto.QuotaController.in_quota_buffer_ms equals the quota. This is only
|
||||||
|
* valid if
|
||||||
|
* execution_time_in_window_ms >=
|
||||||
|
* ConstantsProto.QuotaController.allowed_time_per_period_ms
|
||||||
|
* or
|
||||||
|
* execution_time_in_max_period_ms >=
|
||||||
|
* ConstantsProto.QuotaController.max_execution_time_ms.
|
||||||
|
*/
|
||||||
|
optional int64 quota_cutoff_time_elapsed = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time after which job_count_in_allowed_time should be considered invalid, in the
|
||||||
|
* elapsed realtime timebase.
|
||||||
|
*/
|
||||||
|
optional int64 job_count_expiration_time_elapsed = 9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of jobs that ran in at least the last
|
||||||
|
* ConstantsProto.QuotaController.allowed_time_per_period_ms.
|
||||||
|
* It may contain a few stale entries since cleanup won't happen exactly every
|
||||||
|
* ConstantsProto.QuotaController.allowed_time_per_period_ms.
|
||||||
|
*/
|
||||||
|
optional int32 job_count_in_allowed_time = 10;
|
||||||
|
}
|
||||||
|
|
||||||
message Package {
|
message Package {
|
||||||
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
|
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
|
||||||
|
|
||||||
@@ -517,6 +565,8 @@ message StateControllerProto {
|
|||||||
optional Timer timer = 2;
|
optional Timer timer = 2;
|
||||||
|
|
||||||
repeated TimingSession saved_sessions = 3;
|
repeated TimingSession saved_sessions = 3;
|
||||||
|
|
||||||
|
repeated ExecutionStats execution_stats = 4;
|
||||||
}
|
}
|
||||||
repeated PackageStats package_stats = 5;
|
repeated PackageStats package_stats = 5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import android.annotation.UserIdInt;
|
|||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.ActivityManagerInternal;
|
import android.app.ActivityManagerInternal;
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
|
import android.app.AppGlobals;
|
||||||
import android.app.IUidObserver;
|
import android.app.IUidObserver;
|
||||||
import android.app.usage.UsageStatsManagerInternal;
|
import android.app.usage.UsageStatsManagerInternal;
|
||||||
import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
|
import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
|
||||||
@@ -49,6 +50,7 @@ import android.util.Log;
|
|||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.SparseBooleanArray;
|
import android.util.SparseBooleanArray;
|
||||||
|
import android.util.SparseSetArray;
|
||||||
import android.util.proto.ProtoOutputStream;
|
import android.util.proto.ProtoOutputStream;
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
@@ -277,9 +279,9 @@ public final class QuotaController extends StateController {
|
|||||||
.append(", ")
|
.append(", ")
|
||||||
.append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ")
|
.append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ")
|
||||||
.append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ")
|
.append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ")
|
||||||
.append("jobCountExpirationTime").append(jobCountExpirationTimeElapsed)
|
.append("jobCountExpirationTime=").append(jobCountExpirationTimeElapsed)
|
||||||
.append(", ")
|
.append(", ")
|
||||||
.append("jobCountInAllowedTime").append(jobCountInAllowedTime)
|
.append("jobCountInAllowedTime=").append(jobCountInAllowedTime)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,6 +340,9 @@ public final class QuotaController extends StateController {
|
|||||||
/** List of UIDs currently in the foreground. */
|
/** List of UIDs currently in the foreground. */
|
||||||
private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
|
private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
|
||||||
|
|
||||||
|
/** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
|
||||||
|
private final SparseSetArray<String> mUidToPackageCache = new SparseSetArray<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of jobs that started while the UID was in the TOP state. There will be no more than
|
* List of jobs that started while the UID was in the TOP state. There will be no more than
|
||||||
* 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
|
* 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
|
||||||
@@ -421,6 +426,22 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final BroadcastReceiver mPackageAddedReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (intent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
|
||||||
|
synchronized (mLock) {
|
||||||
|
mUidToPackageCache.remove(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The rolling window size for each standby bucket. Within each window, an app will have 10
|
* The rolling window size for each standby bucket. Within each window, an app will have 10
|
||||||
* minutes to run its jobs.
|
* minutes to run its jobs.
|
||||||
@@ -469,6 +490,9 @@ public final class QuotaController extends StateController {
|
|||||||
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
|
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
|
||||||
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
|
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
|
||||||
|
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||||
|
mContext.registerReceiverAsUser(mPackageAddedReceiver, UserHandle.ALL, filter, null, null);
|
||||||
|
|
||||||
// Set up the app standby bucketing tracker
|
// Set up the app standby bucketing tracker
|
||||||
UsageStatsManagerInternal usageStats = LocalServices.getService(
|
UsageStatsManagerInternal usageStats = LocalServices.getService(
|
||||||
UsageStatsManagerInternal.class);
|
UsageStatsManagerInternal.class);
|
||||||
@@ -502,10 +526,15 @@ public final class QuotaController extends StateController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareForExecutionLocked(JobStatus jobStatus) {
|
public void prepareForExecutionLocked(JobStatus jobStatus) {
|
||||||
if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
|
||||||
|
}
|
||||||
|
|
||||||
final int uid = jobStatus.getSourceUid();
|
final int uid = jobStatus.getSourceUid();
|
||||||
if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
|
if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, jobStatus.toShortString() + " is top started job");
|
||||||
|
}
|
||||||
mTopStartedJobs.add(jobStatus);
|
mTopStartedJobs.add(jobStatus);
|
||||||
// Top jobs won't count towards quota so there's no need to involve the Timer.
|
// Top jobs won't count towards quota so there's no need to involve the Timer.
|
||||||
return;
|
return;
|
||||||
@@ -518,7 +547,7 @@ public final class QuotaController extends StateController {
|
|||||||
timer = new Timer(uid, userId, packageName);
|
timer = new Timer(uid, userId, packageName);
|
||||||
mPkgTimers.add(userId, packageName, timer);
|
mPkgTimers.add(userId, packageName, timer);
|
||||||
}
|
}
|
||||||
timer.startTrackingJob(jobStatus);
|
timer.startTrackingJobLocked(jobStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -645,7 +674,7 @@ public final class QuotaController extends StateController {
|
|||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
if (timer.isActive()) {
|
if (timer.isActive()) {
|
||||||
Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off.");
|
Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off.");
|
||||||
timer.dropEverything();
|
timer.dropEverythingLocked();
|
||||||
}
|
}
|
||||||
mPkgTimers.delete(userId, packageName);
|
mPkgTimers.delete(userId, packageName);
|
||||||
}
|
}
|
||||||
@@ -657,6 +686,7 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
mExecutionStatsCache.delete(userId, packageName);
|
mExecutionStatsCache.delete(userId, packageName);
|
||||||
mForegroundUids.delete(uid);
|
mForegroundUids.delete(uid);
|
||||||
|
mUidToPackageCache.remove(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -666,6 +696,7 @@ public final class QuotaController extends StateController {
|
|||||||
mTimingSessions.delete(userId);
|
mTimingSessions.delete(userId);
|
||||||
mInQuotaAlarmListeners.delete(userId);
|
mInQuotaAlarmListeners.delete(userId);
|
||||||
mExecutionStatsCache.delete(userId);
|
mExecutionStatsCache.delete(userId);
|
||||||
|
mUidToPackageCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUidInForeground(int uid) {
|
private boolean isUidInForeground(int uid) {
|
||||||
@@ -678,7 +709,7 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @return true if the job was started while the app was in the TOP state. */
|
/** @return true if the job was started while the app was in the TOP state. */
|
||||||
private boolean isTopStartedJob(@NonNull final JobStatus jobStatus) {
|
private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
|
||||||
return mTopStartedJobs.contains(jobStatus);
|
return mTopStartedJobs.contains(jobStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,14 +726,14 @@ public final class QuotaController extends StateController {
|
|||||||
return jobStatus.getStandbyBucket();
|
return jobStatus.getStandbyBucket();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
|
@VisibleForTesting
|
||||||
|
boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
|
||||||
final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
|
final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
|
||||||
Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
|
|
||||||
// A job is within quota if one of the following is true:
|
// A job is within quota if one of the following is true:
|
||||||
// 1. it was started while the app was in the TOP state
|
// 1. it was started while the app was in the TOP state
|
||||||
// 2. the app is currently in the foreground
|
// 2. the app is currently in the foreground
|
||||||
// 3. the app overall is within its quota
|
// 3. the app overall is within its quota
|
||||||
return isTopStartedJob(jobStatus)
|
return isTopStartedJobLocked(jobStatus)
|
||||||
|| isUidInForeground(jobStatus.getSourceUid())
|
|| isUidInForeground(jobStatus.getSourceUid())
|
||||||
|| isWithinQuotaLocked(
|
|| isWithinQuotaLocked(
|
||||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
|
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
|
||||||
@@ -1081,7 +1112,9 @@ public final class QuotaController extends StateController {
|
|||||||
if (earliestEndElapsed == Long.MAX_VALUE) {
|
if (earliestEndElapsed == Long.MAX_VALUE) {
|
||||||
// Couldn't find a good time to clean up. Maybe this was called after we deleted all
|
// Couldn't find a good time to clean up. Maybe this was called after we deleted all
|
||||||
// timing sessions.
|
// timing sessions.
|
||||||
if (DEBUG) Slog.d(TAG, "Didn't find a time to schedule cleanup");
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Didn't find a time to schedule cleanup");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Need to keep sessions for all apps up to the max period, regardless of their current
|
// Need to keep sessions for all apps up to the max period, regardless of their current
|
||||||
@@ -1095,15 +1128,19 @@ public final class QuotaController extends StateController {
|
|||||||
mNextCleanupTimeElapsed = nextCleanupElapsed;
|
mNextCleanupTimeElapsed = nextCleanupElapsed;
|
||||||
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
|
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
|
||||||
mSessionCleanupAlarmListener, mHandler);
|
mSessionCleanupAlarmListener, mHandler);
|
||||||
if (DEBUG) Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed);
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleNewChargingStateLocked() {
|
private void handleNewChargingStateLocked() {
|
||||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
final boolean isCharging = mChargeTracker.isCharging();
|
final boolean isCharging = mChargeTracker.isCharging();
|
||||||
if (DEBUG) Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
|
||||||
|
}
|
||||||
// Deal with Timers first.
|
// Deal with Timers first.
|
||||||
mPkgTimers.forEach((t) -> t.onStateChanged(nowElapsed, isCharging));
|
mPkgTimers.forEach((t) -> t.onStateChangedLocked(nowElapsed, isCharging));
|
||||||
// Now update jobs.
|
// Now update jobs.
|
||||||
maybeUpdateAllConstraintsLocked();
|
maybeUpdateAllConstraintsLocked();
|
||||||
}
|
}
|
||||||
@@ -1140,7 +1177,7 @@ public final class QuotaController extends StateController {
|
|||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
for (int i = jobs.size() - 1; i >= 0; --i) {
|
for (int i = jobs.size() - 1; i >= 0; --i) {
|
||||||
final JobStatus js = jobs.valueAt(i);
|
final JobStatus js = jobs.valueAt(i);
|
||||||
if (isTopStartedJob(js)) {
|
if (isTopStartedJobLocked(js)) {
|
||||||
// Job was started while the app was in the TOP state so we should allow it to
|
// Job was started while the app was in the TOP state so we should allow it to
|
||||||
// finish.
|
// finish.
|
||||||
changed |= js.setQuotaConstraintSatisfied(true);
|
changed |= js.setQuotaConstraintSatisfied(true);
|
||||||
@@ -1282,7 +1319,9 @@ public final class QuotaController extends StateController {
|
|||||||
if (!alarmListener.isWaiting()
|
if (!alarmListener.isWaiting()
|
||||||
|| inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
|
|| inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
|
||||||
|| alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
|
|| alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
|
||||||
if (DEBUG) Slog.d(TAG, "Scheduling start alarm for " + pkgString);
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Scheduling start alarm for " + pkgString);
|
||||||
|
}
|
||||||
// If the next time this app will have quota is at least 3 minutes before the
|
// If the next time this app will have quota is at least 3 minutes before the
|
||||||
// alarm is supposed to go off, reschedule the alarm.
|
// alarm is supposed to go off, reschedule the alarm.
|
||||||
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
|
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
|
||||||
@@ -1430,8 +1469,8 @@ public final class QuotaController extends StateController {
|
|||||||
mUid = uid;
|
mUid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void startTrackingJob(@NonNull JobStatus jobStatus) {
|
void startTrackingJobLocked(@NonNull JobStatus jobStatus) {
|
||||||
if (isTopStartedJob(jobStatus)) {
|
if (isTopStartedJobLocked(jobStatus)) {
|
||||||
// We intentionally don't pay attention to fg state changes after a TOP job has
|
// We intentionally don't pay attention to fg state changes after a TOP job has
|
||||||
// started.
|
// started.
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -1440,27 +1479,28 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (DEBUG) Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
|
if (DEBUG) {
|
||||||
synchronized (mLock) {
|
Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
|
||||||
// Always track jobs, even when charging.
|
}
|
||||||
mRunningBgJobs.add(jobStatus);
|
// Always track jobs, even when charging.
|
||||||
if (shouldTrackLocked()) {
|
mRunningBgJobs.add(jobStatus);
|
||||||
mBgJobCount++;
|
if (shouldTrackLocked()) {
|
||||||
incrementJobCount(mPkg.userId, mPkg.packageName, 1);
|
mBgJobCount++;
|
||||||
if (mRunningBgJobs.size() == 1) {
|
incrementJobCount(mPkg.userId, mPkg.packageName, 1);
|
||||||
// Started tracking the first job.
|
if (mRunningBgJobs.size() == 1) {
|
||||||
mStartTimeElapsed = sElapsedRealtimeClock.millis();
|
// Started tracking the first job.
|
||||||
// Starting the timer means that all cached execution stats are now
|
mStartTimeElapsed = sElapsedRealtimeClock.millis();
|
||||||
// incorrect.
|
// Starting the timer means that all cached execution stats are now incorrect.
|
||||||
invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
|
invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
|
||||||
scheduleCutoff();
|
scheduleCutoff();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopTrackingJob(@NonNull JobStatus jobStatus) {
|
void stopTrackingJob(@NonNull JobStatus jobStatus) {
|
||||||
if (DEBUG) Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString());
|
if (DEBUG) {
|
||||||
|
Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString());
|
||||||
|
}
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (mRunningBgJobs.size() == 0) {
|
if (mRunningBgJobs.size() == 0) {
|
||||||
// maybeStopTrackingJobLocked can be called when an app cancels a job, so a
|
// maybeStopTrackingJobLocked can be called when an app cancels a job, so a
|
||||||
@@ -1482,7 +1522,7 @@ public final class QuotaController extends StateController {
|
|||||||
* Stops tracking all jobs and cancels any pending alarms. This should only be called if
|
* Stops tracking all jobs and cancels any pending alarms. This should only be called if
|
||||||
* the Timer is not going to be used anymore.
|
* the Timer is not going to be used anymore.
|
||||||
*/
|
*/
|
||||||
void dropEverything() {
|
void dropEverythingLocked() {
|
||||||
mRunningBgJobs.clear();
|
mRunningBgJobs.clear();
|
||||||
cancelCutoff();
|
cancelCutoff();
|
||||||
}
|
}
|
||||||
@@ -1531,25 +1571,23 @@ public final class QuotaController extends StateController {
|
|||||||
return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
|
return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onStateChanged(long nowElapsed, boolean isQuotaFree) {
|
void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) {
|
||||||
synchronized (mLock) {
|
if (isQuotaFree) {
|
||||||
if (isQuotaFree) {
|
emitSessionLocked(nowElapsed);
|
||||||
emitSessionLocked(nowElapsed);
|
} else if (!isActive() && shouldTrackLocked()) {
|
||||||
} else if (shouldTrackLocked()) {
|
// Start timing from unplug.
|
||||||
// Start timing from unplug.
|
if (mRunningBgJobs.size() > 0) {
|
||||||
if (mRunningBgJobs.size() > 0) {
|
mStartTimeElapsed = nowElapsed;
|
||||||
mStartTimeElapsed = nowElapsed;
|
// NOTE: this does have the unfortunate consequence that if the device is
|
||||||
// NOTE: this does have the unfortunate consequence that if the device is
|
// repeatedly plugged in and unplugged, or an app changes foreground state
|
||||||
// repeatedly plugged in and unplugged, or an app changes foreground state
|
// very frequently, the job count for a package may be artificially high.
|
||||||
// very frequently, the job count for a package may be artificially high.
|
mBgJobCount = mRunningBgJobs.size();
|
||||||
mBgJobCount = mRunningBgJobs.size();
|
incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
|
||||||
incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
|
// Starting the timer means that all cached execution stats are now
|
||||||
// Starting the timer means that all cached execution stats are now
|
// incorrect.
|
||||||
// incorrect.
|
invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
|
||||||
invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
|
// Schedule cutoff since we're now actively tracking for quotas again.
|
||||||
// Schedule cutoff since we're now actively tracking for quotas again.
|
scheduleCutoff();
|
||||||
scheduleCutoff();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1604,7 +1642,6 @@ public final class QuotaController extends StateController {
|
|||||||
pw.println(js.toShortString());
|
pw.println(js.toShortString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.decreaseIndent();
|
pw.decreaseIndent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1667,7 +1704,9 @@ public final class QuotaController extends StateController {
|
|||||||
@Override
|
@Override
|
||||||
public void onParoleStateChanged(final boolean isParoleOn) {
|
public void onParoleStateChanged(final boolean isParoleOn) {
|
||||||
mInParole = isParoleOn;
|
mInParole = isParoleOn;
|
||||||
if (DEBUG) Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
|
if (DEBUG) {
|
||||||
|
Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
|
||||||
|
}
|
||||||
// Update job bookkeeping out of band.
|
// Update job bookkeeping out of band.
|
||||||
BackgroundThread.getHandler().post(() -> {
|
BackgroundThread.getHandler().post(() -> {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
@@ -1712,7 +1751,9 @@ public final class QuotaController extends StateController {
|
|||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MSG_REACHED_QUOTA: {
|
case MSG_REACHED_QUOTA: {
|
||||||
Package pkg = (Package) msg.obj;
|
Package pkg = (Package) msg.obj;
|
||||||
if (DEBUG) Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
|
||||||
|
}
|
||||||
|
|
||||||
long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId,
|
long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId,
|
||||||
pkg.packageName);
|
pkg.packageName);
|
||||||
@@ -1737,7 +1778,9 @@ public final class QuotaController extends StateController {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MSG_CLEAN_UP_SESSIONS:
|
case MSG_CLEAN_UP_SESSIONS:
|
||||||
if (DEBUG) Slog.d(TAG, "Cleaning up timing sessions.");
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Cleaning up timing sessions.");
|
||||||
|
}
|
||||||
deleteObsoleteSessionsLocked();
|
deleteObsoleteSessionsLocked();
|
||||||
maybeScheduleCleanupAlarmLocked();
|
maybeScheduleCleanupAlarmLocked();
|
||||||
|
|
||||||
@@ -1745,7 +1788,9 @@ public final class QuotaController extends StateController {
|
|||||||
case MSG_CHECK_PACKAGE: {
|
case MSG_CHECK_PACKAGE: {
|
||||||
String packageName = (String) msg.obj;
|
String packageName = (String) msg.obj;
|
||||||
int userId = msg.arg1;
|
int userId = msg.arg1;
|
||||||
if (DEBUG) Slog.d(TAG, "Checking pkg " + string(userId, packageName));
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Checking pkg " + string(userId, packageName));
|
||||||
|
}
|
||||||
if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
|
if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
|
||||||
mStateChangedListener.onControllerStateChanged();
|
mStateChangedListener.onControllerStateChanged();
|
||||||
}
|
}
|
||||||
@@ -1767,13 +1812,28 @@ public final class QuotaController extends StateController {
|
|||||||
isQuotaFree = false;
|
isQuotaFree = false;
|
||||||
}
|
}
|
||||||
// Update Timers first.
|
// Update Timers first.
|
||||||
final int userIndex = mPkgTimers.indexOfKey(userId);
|
if (mPkgTimers.indexOfKey(userId) >= 0) {
|
||||||
if (userIndex != -1) {
|
ArraySet<String> packages = mUidToPackageCache.get(uid);
|
||||||
final int numPkgs = mPkgTimers.numPackagesForUser(userId);
|
if (packages == null) {
|
||||||
for (int p = 0; p < numPkgs; ++p) {
|
try {
|
||||||
Timer t = mPkgTimers.valueAt(userIndex, p);
|
String[] pkgs = AppGlobals.getPackageManager()
|
||||||
if (t != null) {
|
.getPackagesForUid(uid);
|
||||||
t.onStateChanged(nowElapsed, isQuotaFree);
|
if (pkgs != null) {
|
||||||
|
for (String pkg : pkgs) {
|
||||||
|
mUidToPackageCache.add(uid, pkg);
|
||||||
|
}
|
||||||
|
packages = mUidToPackageCache.get(uid);
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.wtf(TAG, "Failed to get package list", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (packages != null) {
|
||||||
|
for (int i = packages.size() - 1; i >= 0; --i) {
|
||||||
|
Timer t = mPkgTimers.get(userId, packages.valueAt(i));
|
||||||
|
if (t != null) {
|
||||||
|
t.onStateChangedLocked(nowElapsed, isQuotaFree);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1883,6 +1943,17 @@ public final class QuotaController extends StateController {
|
|||||||
pw.println(mForegroundUids.toString());
|
pw.println(mForegroundUids.toString());
|
||||||
pw.println();
|
pw.println();
|
||||||
|
|
||||||
|
pw.println("Cached UID->package map:");
|
||||||
|
pw.increaseIndent();
|
||||||
|
for (int i = 0; i < mUidToPackageCache.size(); ++i) {
|
||||||
|
final int uid = mUidToPackageCache.keyAt(i);
|
||||||
|
pw.print(uid);
|
||||||
|
pw.print(": ");
|
||||||
|
pw.println(mUidToPackageCache.get(uid));
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
|
pw.println();
|
||||||
|
|
||||||
mTrackedJobs.forEach((jobs) -> {
|
mTrackedJobs.forEach((jobs) -> {
|
||||||
for (int j = 0; j < jobs.size(); j++) {
|
for (int j = 0; j < jobs.size(); j++) {
|
||||||
final JobStatus js = jobs.valueAt(j);
|
final JobStatus js = jobs.valueAt(j);
|
||||||
@@ -1936,6 +2007,29 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pw.println("Cached execution stats:");
|
||||||
|
pw.increaseIndent();
|
||||||
|
for (int u = 0; u < mExecutionStatsCache.numUsers(); ++u) {
|
||||||
|
final int userId = mExecutionStatsCache.keyAt(u);
|
||||||
|
for (int p = 0; p < mExecutionStatsCache.numPackagesForUser(userId); ++p) {
|
||||||
|
final String pkgName = mExecutionStatsCache.keyAt(u, p);
|
||||||
|
ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);
|
||||||
|
|
||||||
|
pw.println(string(userId, pkgName));
|
||||||
|
pw.increaseIndent();
|
||||||
|
for (int i = 0; i < stats.length; ++i) {
|
||||||
|
ExecutionStats executionStats = stats[i];
|
||||||
|
if (executionStats != null) {
|
||||||
|
pw.print(JobStatus.bucketName(i));
|
||||||
|
pw.print(": ");
|
||||||
|
pw.println(executionStats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1995,6 +2089,49 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExecutionStats[] stats = mExecutionStatsCache.get(userId, pkgName);
|
||||||
|
if (stats != null) {
|
||||||
|
for (int bucketIndex = 0; bucketIndex < stats.length; ++bucketIndex) {
|
||||||
|
ExecutionStats es = stats[bucketIndex];
|
||||||
|
if (es == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final long esToken = proto.start(
|
||||||
|
StateControllerProto.QuotaController.PackageStats.EXECUTION_STATS);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.STANDBY_BUCKET,
|
||||||
|
bucketIndex);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.EXPIRATION_TIME_ELAPSED,
|
||||||
|
es.expirationTimeElapsed);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS,
|
||||||
|
es.windowSizeMs);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS,
|
||||||
|
es.executionTimeInWindowMs);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_WINDOW,
|
||||||
|
es.bgJobCountInWindow);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_MAX_PERIOD_MS,
|
||||||
|
es.executionTimeInMaxPeriodMs);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_MAX_PERIOD,
|
||||||
|
es.bgJobCountInMaxPeriod);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.QUOTA_CUTOFF_TIME_ELAPSED,
|
||||||
|
es.quotaCutoffTimeElapsed);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED,
|
||||||
|
es.jobCountExpirationTimeElapsed);
|
||||||
|
proto.write(
|
||||||
|
StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_ALLOWED_TIME,
|
||||||
|
es.jobCountInAllowedTime);
|
||||||
|
proto.end(esToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
proto.end(psToken);
|
proto.end(psToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.IPackageManager;
|
||||||
import android.content.pm.PackageManagerInternal;
|
import android.content.pm.PackageManagerInternal;
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.os.BatteryManagerInternal;
|
import android.os.BatteryManagerInternal;
|
||||||
@@ -224,50 +225,55 @@ public class QuotaControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setProcessState(int procState) {
|
private void setProcessState(int procState) {
|
||||||
|
setProcessState(procState, mSourceUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProcessState(int procState, int uid) {
|
||||||
try {
|
try {
|
||||||
doReturn(procState).when(mActivityMangerInternal).getUidProcessState(mSourceUid);
|
doReturn(procState).when(mActivityMangerInternal).getUidProcessState(uid);
|
||||||
SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
|
SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
|
||||||
spyOn(foregroundUids);
|
spyOn(foregroundUids);
|
||||||
mUidObserver.onUidStateChanged(mSourceUid, procState, 0);
|
mUidObserver.onUidStateChanged(uid, procState, 0);
|
||||||
if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
|
if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
|
||||||
verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1))
|
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
|
||||||
.put(eq(mSourceUid), eq(true));
|
.put(eq(uid), eq(true));
|
||||||
assertTrue(foregroundUids.get(mSourceUid));
|
assertTrue(foregroundUids.get(uid));
|
||||||
} else {
|
} else {
|
||||||
verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1)).delete(eq(mSourceUid));
|
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)).delete(eq(uid));
|
||||||
assertFalse(foregroundUids.get(mSourceUid));
|
assertFalse(foregroundUids.get(uid));
|
||||||
}
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
fail("registerUidObserver threw exception: " + e.getMessage());
|
fail("registerUidObserver threw exception: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStandbyBucket(int bucketIndex) {
|
private int bucketIndexToUsageStatsBucket(int bucketIndex) {
|
||||||
int bucket;
|
|
||||||
switch (bucketIndex) {
|
switch (bucketIndex) {
|
||||||
case ACTIVE_INDEX:
|
case ACTIVE_INDEX:
|
||||||
bucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
|
return UsageStatsManager.STANDBY_BUCKET_ACTIVE;
|
||||||
break;
|
|
||||||
case WORKING_INDEX:
|
case WORKING_INDEX:
|
||||||
bucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
|
return UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
|
||||||
break;
|
|
||||||
case FREQUENT_INDEX:
|
case FREQUENT_INDEX:
|
||||||
bucket = UsageStatsManager.STANDBY_BUCKET_FREQUENT;
|
return UsageStatsManager.STANDBY_BUCKET_FREQUENT;
|
||||||
break;
|
|
||||||
case RARE_INDEX:
|
case RARE_INDEX:
|
||||||
bucket = UsageStatsManager.STANDBY_BUCKET_RARE;
|
return UsageStatsManager.STANDBY_BUCKET_RARE;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
bucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
|
return UsageStatsManager.STANDBY_BUCKET_NEVER;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStandbyBucket(int bucketIndex) {
|
||||||
when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
|
when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
|
||||||
anyLong())).thenReturn(bucket);
|
anyLong())).thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
|
private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
|
||||||
setStandbyBucket(bucketIndex);
|
setStandbyBucket(bucketIndex);
|
||||||
for (JobStatus job : jobs) {
|
for (JobStatus job : jobs) {
|
||||||
job.setStandbyBucket(bucketIndex);
|
job.setStandbyBucket(bucketIndex);
|
||||||
|
when(mUsageStatsManager.getAppStandbyBucket(
|
||||||
|
eq(job.getSourcePackageName()), eq(job.getSourceUserId()), anyLong()))
|
||||||
|
.thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,8 +289,13 @@ public class QuotaControllerTest {
|
|||||||
new ComponentName(mContext, "TestQuotaJobService"))
|
new ComponentName(mContext, "TestQuotaJobService"))
|
||||||
.setMinimumLatency(Math.abs(jobId) + 1)
|
.setMinimumLatency(Math.abs(jobId) + 1)
|
||||||
.build();
|
.build();
|
||||||
|
return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
|
||||||
|
JobInfo jobInfo) {
|
||||||
JobStatus js = JobStatus.createFromJobInfo(
|
JobStatus js = JobStatus.createFromJobInfo(
|
||||||
jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
|
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
|
||||||
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
|
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
|
||||||
js.setStandbyBucket(FREQUENT_INDEX);
|
js.setStandbyBucket(FREQUENT_INDEX);
|
||||||
return js;
|
return js;
|
||||||
@@ -934,6 +945,115 @@ public class QuotaControllerTest {
|
|||||||
assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
|
assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS() {
|
||||||
|
setDischarging();
|
||||||
|
|
||||||
|
JobStatus jobStatus = createJobStatus(
|
||||||
|
"testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS", 1);
|
||||||
|
setStandbyBucket(ACTIVE_INDEX, jobStatus);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
|
||||||
|
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
|
||||||
|
mQuotaController.prepareForExecutionLocked(jobStatus);
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
advanceElapsedClock(SECOND_IN_MILLIS);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
|
||||||
|
}
|
||||||
|
mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
|
||||||
|
|
||||||
|
advanceElapsedClock(15 * SECOND_IN_MILLIS);
|
||||||
|
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
|
||||||
|
mQuotaController.prepareForExecutionLocked(jobStatus);
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
advanceElapsedClock(SECOND_IN_MILLIS);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
|
||||||
|
}
|
||||||
|
mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
|
||||||
|
|
||||||
|
advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS);
|
||||||
|
|
||||||
|
assertEquals(2, mQuotaController.getExecutionStatsLocked(
|
||||||
|
SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInAllowedTime);
|
||||||
|
assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps()
|
||||||
|
throws Exception {
|
||||||
|
setDischarging();
|
||||||
|
|
||||||
|
final String unaffectedPkgName = "com.android.unaffected";
|
||||||
|
final int unaffectedUid = 10987;
|
||||||
|
JobInfo unaffectedJobInfo = new JobInfo.Builder(1,
|
||||||
|
new ComponentName(unaffectedPkgName, "foo"))
|
||||||
|
.build();
|
||||||
|
JobStatus unaffected = createJobStatus(
|
||||||
|
"testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps",
|
||||||
|
unaffectedPkgName, unaffectedUid, unaffectedJobInfo);
|
||||||
|
setStandbyBucket(FREQUENT_INDEX, unaffected);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_SERVICE, unaffectedUid);
|
||||||
|
|
||||||
|
final String fgChangerPkgName = "com.android.foreground.changer";
|
||||||
|
final int fgChangerUid = 10234;
|
||||||
|
JobInfo fgChangerJobInfo = new JobInfo.Builder(2,
|
||||||
|
new ComponentName(fgChangerPkgName, "foo"))
|
||||||
|
.build();
|
||||||
|
JobStatus fgStateChanger = createJobStatus(
|
||||||
|
"testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps",
|
||||||
|
fgChangerPkgName, fgChangerUid, fgChangerJobInfo);
|
||||||
|
setStandbyBucket(ACTIVE_INDEX, fgStateChanger);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_BACKUP, fgChangerUid);
|
||||||
|
|
||||||
|
IPackageManager packageManager = AppGlobals.getPackageManager();
|
||||||
|
spyOn(packageManager);
|
||||||
|
doReturn(new String[]{unaffectedPkgName})
|
||||||
|
.when(packageManager).getPackagesForUid(unaffectedUid);
|
||||||
|
doReturn(new String[]{fgChangerPkgName})
|
||||||
|
.when(packageManager).getPackagesForUid(fgChangerUid);
|
||||||
|
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(unaffected, null);
|
||||||
|
mQuotaController.prepareForExecutionLocked(unaffected);
|
||||||
|
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null);
|
||||||
|
mQuotaController.prepareForExecutionLocked(fgStateChanger);
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
advanceElapsedClock(SECOND_IN_MILLIS);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
|
||||||
|
}
|
||||||
|
mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
|
||||||
|
|
||||||
|
advanceElapsedClock(15 * SECOND_IN_MILLIS);
|
||||||
|
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null);
|
||||||
|
mQuotaController.prepareForExecutionLocked(fgStateChanger);
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
advanceElapsedClock(SECOND_IN_MILLIS);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
|
||||||
|
}
|
||||||
|
mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
|
||||||
|
|
||||||
|
mQuotaController.maybeStopTrackingJobLocked(unaffected, null, false);
|
||||||
|
|
||||||
|
assertTrue(mQuotaController.isWithinQuotaLocked(unaffected));
|
||||||
|
assertFalse(mQuotaController.isWithinQuotaLocked(fgStateChanger));
|
||||||
|
assertEquals(1,
|
||||||
|
mQuotaController.getTimingSessions(SOURCE_USER_ID, unaffectedPkgName).size());
|
||||||
|
assertEquals(42,
|
||||||
|
mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size());
|
||||||
|
for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) {
|
||||||
|
assertEquals(42, mQuotaController.getExecutionStatsLocked(
|
||||||
|
SOURCE_USER_ID, fgChangerPkgName, i).jobCountInAllowedTime);
|
||||||
|
assertEquals(1, mQuotaController.getExecutionStatsLocked(
|
||||||
|
SOURCE_USER_ID, unaffectedPkgName, i).jobCountInAllowedTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMaybeScheduleCleanupAlarmLocked() {
|
public void testMaybeScheduleCleanupAlarmLocked() {
|
||||||
// No sessions saved yet.
|
// No sessions saved yet.
|
||||||
|
|||||||
Reference in New Issue
Block a user