diff options
| author | 2018-01-19 14:23:11 -0800 | |
|---|---|---|
| committer | 2018-01-23 17:00:01 -0800 | |
| commit | 15407846682b269e6b7fd0c24b84f709257fab5d (patch) | |
| tree | a16708e73d4073daed65b1bfc67e0eb6fe47a752 | |
| parent | bab202f7cf96387f973cc9b040db29f061b0e07e (diff) | |
EBS: Exempt jobs scheduled for foreground apps
Jobs that are scheduled when the service app is in the foreground
(as opposed to the calling app being in the foreground) will be exempted
from EBS job throttling, *unless* they have time constraints.
Bug: 72125364
Test: Manual test with sync
Test: atest CtsBatterySavingTestCases
Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
Change-Id: Ied7e24b0dbb104d5d7f95f853ab939cd9a403456
10 files changed, 145 insertions, 17 deletions
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index f72ca623de10..739fca3ceb1d 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -558,4 +558,6 @@ message JobStatusDumpProto { optional int64 last_successful_run_time = 22; optional int64 last_failed_run_time = 23; + + optional int64 internal_flags = 24; } diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java index de113a6a53e0..d01685564223 100644 --- a/services/core/java/com/android/server/ForceAppStandbyTracker.java +++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java @@ -17,6 +17,7 @@ package com.android.server; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.AppOpsManager.PackageOps; import android.app.IActivityManager; @@ -825,9 +826,10 @@ public class ForceAppStandbyTracker { /** * @return whether jobs should be restricted for a UID package-name. */ - public boolean areJobsRestricted(int uid, @NonNull String packageName) { + public boolean areJobsRestricted(int uid, @NonNull String packageName, + boolean hasForegroundExemption) { return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true, - /* exemptOnBatterySaver =*/ false); + hasForegroundExemption); } /** @@ -861,7 +863,9 @@ public class ForceAppStandbyTracker { /** * @return whether a UID is in the foreground or not. * - * Note clients normally shouldn't need to access it. It's only for dumpsys. + * Note this information is based on the UID proc state callback, meaning it's updated + * asynchronously and may subtly be stale. If the fresh data is needed, use + * {@link ActivityManagerInternal#getUidProcessState} instead. */ public boolean isInForeground(int uid) { if (!UserHandle.isApp(uid)) { diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index bd1dbf9c46e8..bc427f72cc73 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.IUidObserver; import android.app.job.IJobScheduler; @@ -72,8 +73,10 @@ import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; import com.android.server.DeviceIdleController; import com.android.server.FgThread; +import com.android.server.ForceAppStandbyTracker; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob; import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob; @@ -101,6 +104,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.function.Predicate; /** * Responsible for taking jobs representing work to be performed by a client app, and determining @@ -174,8 +178,10 @@ public final class JobSchedulerService extends com.android.server.SystemService final JobSchedulerStub mJobSchedulerStub; PackageManagerInternal mLocalPM; + ActivityManagerInternal mActivityManagerInternal; IBatteryStats mBatteryStats; DeviceIdleController.LocalService mLocalDeviceIdleController; + final ForceAppStandbyTracker mForceAppStandbyTracker; /** * Set to true once we are allowed to run third party apps. @@ -777,6 +783,22 @@ public final class JobSchedulerService extends com.android.server.SystemService mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle); } + /** + * Return whether an UID is in the foreground or not. + */ + private boolean isUidInForeground(int uid) { + synchronized (mLock) { + if (mUidPriorityOverride.get(uid, 0) > 0) { + return true; + } + } + // Note UID observer may not be called in time, so we always check with the AM. + return mActivityManagerInternal.getUidProcessState(uid) + <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } + + private final Predicate<Integer> mIsUidInForegroundPredicate = this::isUidInForeground; + public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, int userId, String tag) { try { @@ -796,12 +818,25 @@ public final class JobSchedulerService extends com.android.server.SystemService // Fast path: we are adding work to an existing job, and the JobInfo is not // changing. We can just directly enqueue this work in to the job. if (toCancel.getJob().equals(job)) { + toCancel.enqueueWorkLocked(ActivityManager.getService(), work); + + // If any of work item is enqueued when the source is in the foreground, + // exempt the entire job. + toCancel.maybeAddForegroundExemption(mIsUidInForegroundPredicate); + return JobScheduler.RESULT_SUCCESS; } } JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag); + + // Give exemption if the source is in the foreground just now. + // Note if it's a sync job, this method is called on the handler so it's not exactly + // the state when requestSync() was called, but that should be fine because of the + // 1 minute foreground grace period. + jobStatus.maybeAddForegroundExemption(mIsUidInForegroundPredicate); + if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString()); // Jobs on behalf of others don't apply to the per-app job cap if (ENFORCE_MAX_JOBS && packageName == null) { @@ -1044,6 +1079,8 @@ public final class JobSchedulerService extends com.android.server.SystemService super(context); mLocalPM = LocalServices.getService(PackageManagerInternal.class); + mActivityManagerInternal = Preconditions.checkNotNull( + LocalServices.getService(ActivityManagerInternal.class)); mHandler = new JobHandler(context.getMainLooper()); mConstants = new Constants(mHandler); @@ -1075,6 +1112,8 @@ public final class JobSchedulerService extends com.android.server.SystemService mDeviceIdleJobsController = DeviceIdleJobsController.get(this); mControllers.add(mDeviceIdleJobsController); + mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context); + // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. if (!mJobs.jobTimesInflatedValid()) { @@ -1134,6 +1173,9 @@ public final class JobSchedulerService extends com.android.server.SystemService public void onBootPhase(int phase) { if (PHASE_SYSTEM_SERVICES_READY == phase) { mConstants.start(getContext().getContentResolver()); + + mForceAppStandbyTracker.start(); + // Register br for package removals and user removals. final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index 36cacd7a7d96..6da783ce110b 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -72,6 +72,9 @@ import java.util.Set; * This is important b/c {@link com.android.server.job.JobStore.WriteJobsMapToDiskRunnable} * and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that * object. + * + * Test: + * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java */ public final class JobStore { private static final String TAG = "JobStore"; @@ -427,6 +430,9 @@ public final class JobStore { out.attribute(null, "uid", Integer.toString(jobStatus.getUid())); out.attribute(null, "priority", String.valueOf(jobStatus.getPriority())); out.attribute(null, "flags", String.valueOf(jobStatus.getFlags())); + if (jobStatus.getInternalFlags() != 0) { + out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags())); + } out.attribute(null, "lastSuccessfulRunTime", String.valueOf(jobStatus.getLastSuccessfulRunTime())); @@ -689,6 +695,7 @@ public final class JobStore { int uid, sourceUserId; long lastSuccessfulRunTime; long lastFailedRunTime; + int internalFlags = 0; // Read out job identifier attributes and priority. try { @@ -704,6 +711,10 @@ public final class JobStore { if (val != null) { jobBuilder.setFlags(Integer.parseInt(val)); } + val = parser.getAttributeValue(null, "internalFlags"); + if (val != null) { + internalFlags = Integer.parseInt(val); + } val = parser.getAttributeValue(null, "sourceUserId"); sourceUserId = val == null ? -1 : Integer.parseInt(val); @@ -718,7 +729,6 @@ public final class JobStore { } String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName"); - final String sourceTag = parser.getAttributeValue(null, "sourceTag"); int eventType; @@ -857,7 +867,7 @@ public final class JobStore { appBucket, currentHeartbeat, sourceTag, elapsedRuntimes.first, elapsedRuntimes.second, lastSuccessfulRunTime, lastFailedRunTime, - (rtcIsGood) ? null : rtcRuntimes); + (rtcIsGood) ? null : rtcRuntimes, internalFlags); return js; } diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java index 1d053a500f92..2e4567ac1dff 100644 --- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -197,7 +197,9 @@ public final class BackgroundJobsController extends StateController { final int uid = jobStatus.getSourceUid(); final String packageName = jobStatus.getSourcePackageName(); - final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName); + final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName, + (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) + != 0); return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun); } diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 0f5cb0a38c83..2884dfba5e35 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -46,6 +46,7 @@ import com.android.server.job.JobStatusShortInfoProto; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.function.Predicate; /** * Uniquely identifies a job internally. @@ -184,6 +185,21 @@ public final class JobStatus { */ private int trackingControllers; + /** + * Flag for {@link #mInternalFlags}: this job was scheduled when the app that owns the job + * service (not necessarily the caller) was in the foreground and the job has no time + * constraints, which makes it exempted from the battery saver job restriction. + * + * @hide + */ + public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0; + + /** + * Versatile, persistable flags for a job that's updated within the system server, + * as opposed to {@link JobInfo#flags} that's set by callers. + */ + private int mInternalFlags; + // These are filled in by controllers when preparing for execution. public ArraySet<Uri> changedUris; public ArraySet<String> changedAuthorities; @@ -248,7 +264,7 @@ public final class JobStatus { private JobStatus(JobInfo job, int callingUid, int targetSdkVersion, String sourcePackageName, int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, - long lastSuccessfulRunTime, long lastFailedRunTime) { + long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) { this.job = job; this.callingUid = callingUid; this.targetSdkVersion = targetSdkVersion; @@ -304,6 +320,8 @@ public final class JobStatus { mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; + mInternalFlags = internalFlags; + updateEstimatedNetworkBytesLocked(); } @@ -315,7 +333,8 @@ public final class JobStatus { jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(), jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), - jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime()); + jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(), + jobStatus.getInternalFlags()); mPersistedUtcTimes = jobStatus.mPersistedUtcTimes; if (jobStatus.mPersistedUtcTimes != null) { if (DEBUG) { @@ -336,12 +355,13 @@ public final class JobStatus { int standbyBucket, long baseHeartbeat, String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, - Pair<Long, Long> persistedExecutionTimesUTC) { + Pair<Long, Long> persistedExecutionTimesUTC, + int innerFlags) { this(job, callingUid, resolveTargetSdkVersion(job), sourcePkgName, sourceUserId, standbyBucket, baseHeartbeat, sourceTag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, - lastSuccessfulRunTime, lastFailedRunTime); + lastSuccessfulRunTime, lastFailedRunTime, innerFlags); // Only during initial inflation do we record the UTC-timebase execution bounds // read from the persistent store. If we ever have to recreate the JobStatus on @@ -365,7 +385,7 @@ public final class JobStatus { rescheduling.getStandbyBucket(), newBaseHeartbeat, rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis, - lastSuccessfulRunTime, lastFailedRunTime); + lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags()); } /** @@ -395,10 +415,12 @@ public final class JobStatus { sourceUserId, elapsedNow); JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class); long currentHeartbeat = js != null ? js.currentHeartbeat() : 0; + return new JobStatus(job, callingUid, resolveTargetSdkVersion(job), sourcePkg, sourceUserId, standbyBucket, currentHeartbeat, tag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, - 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */); + 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, + /*innerFlags=*/ 0); } public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) { @@ -623,6 +645,28 @@ public final class JobStatus { return job.getFlags(); } + public int getInternalFlags() { + return mInternalFlags; + } + + public void addInternalFlags(int flags) { + mInternalFlags |= flags; + } + + public void maybeAddForegroundExemption(Predicate<Integer> uidForegroundChecker) { + // Jobs with time constraints shouldn't be exempted. + if (job.hasEarlyConstraint() || job.hasLateConstraint()) { + return; + } + // Already exempted, skip the foreground check. + if ((mInternalFlags & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) { + return; + } + if (uidForegroundChecker.test(getSourceUid())) { + addInternalFlags(INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION); + } + } + private void updateEstimatedNetworkBytesLocked() { totalNetworkBytes = computeEstimatedNetworkBytesLocked(); } @@ -1162,6 +1206,15 @@ public final class JobStatus { pw.print(prefix); pw.print(" Flags: "); pw.println(Integer.toHexString(job.getFlags())); } + if (getInternalFlags() != 0) { + pw.print(prefix); pw.print(" Internal flags: "); + pw.print(Integer.toHexString(getInternalFlags())); + + if ((getInternalFlags()&INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) { + pw.print(" HAS_FOREGROUND_EXEMPTION"); + } + pw.println(); + } pw.print(prefix); pw.print(" Requires: charging="); pw.print(job.isRequireCharging()); pw.print(" batteryNotLow="); pw.print(job.isRequireBatteryNotLow()); pw.print(" deviceIdle="); @@ -1317,6 +1370,7 @@ public final class JobStatus { proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid()); proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId()); proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName()); + proto.write(JobStatusDumpProto.INTERNAL_FLAGS, getInternalFlags()); if (full) { final long jiToken = proto.start(JobStatusDumpProto.JOB_INFO); diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java index de54e52d4d0a..a29e169cb196 100644 --- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java @@ -15,6 +15,8 @@ */ package com.android.server; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; + import static com.android.server.ForceAppStandbyTracker.TARGET_OP; import static org.junit.Assert.assertEquals; @@ -33,6 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.PackageOps; @@ -259,13 +262,19 @@ public class ForceAppStandbyTrackerTest { private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY; private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName, - int restrictionTypes) { + int restrictionTypes, boolean exemptFromBatterySaver) { assertEquals(((restrictionTypes & JOBS_ONLY) != 0), - instance.areJobsRestricted(uid, packageName)); + instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver)); assertEquals(((restrictionTypes & ALARMS_ONLY) != 0), instance.areAlarmsRestricted(uid, packageName)); } + private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName, + int restrictionTypes) { + areRestricted(instance, uid, packageName, restrictionTypes, + /*exemptFromBatterySaver=*/ false); + } + @Test public void testAll() throws Exception { final ForceAppStandbyTrackerTestable instance = newInstance(); diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 43d026d8efc3..e2064aa38f1f 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -47,6 +47,8 @@ import java.util.concurrent.TimeUnit; /** * Test reading and writing correctly from file. + * + * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java */ @RunWith(AndroidJUnit4.class) public class JobStoreTest { @@ -116,6 +118,7 @@ public class JobStoreTest { .setPersisted(true) .build(); final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null); + ts.addInternalFlags(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION); mTaskStoreUnderTest.add(ts); waitForPendingIo(); @@ -128,6 +131,8 @@ public class JobStoreTest { assertTasksEqual(task, loadedTaskStatus.getJob()); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts)); assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid()); + assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION, + loadedTaskStatus.getInternalFlags()); compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime()); compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", @@ -272,7 +277,7 @@ public class JobStoreTest { 0 /* sourceUserId */, 0, 0, "someTag", invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, - persistedExecutionTimesUTC); + persistedExecutionTimesUTC, 0 /* innerFlagg */); mTaskStoreUnderTest.add(js); waitForPendingIo(); diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index f6a749df1df6..35cba1855503 100644 --- a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -164,6 +164,6 @@ public class ConnectivityControllerTest { private static JobStatus createJobStatus(JobInfo.Builder job, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { return new JobStatus(job.build(), 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis, - latestRunTimeElapsedMillis, 0, 0, null); + latestRunTimeElapsedMillis, 0, 0, null, 0); } } diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java index 15c24ac7efd6..d78af22e49c9 100644 --- a/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -71,6 +71,6 @@ public class JobStatusTest { final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build(); return new JobStatus(job, 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis, - latestRunTimeElapsedMillis, 0, 0, null); + latestRunTimeElapsedMillis, 0, 0, null, 0); } } |