diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 788bfe4a0d5d2..9749c8087caf8 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -28,6 +28,7 @@ import android.annotation.NonNull; import android.app.job.JobInfo; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener; +import android.appwidget.AppWidgetManager; import android.content.Context; import android.os.Handler; import android.os.Looper; @@ -63,6 +64,13 @@ public class PrefetchController extends StateController { private final PcConstants mPcConstants; private final PcHandler mHandler; + // Note: when determining prefetch bit satisfaction, we mark the bit as satisfied for apps with + // active widgets assuming that any prefetch jobs are being used for the widget. However, we + // don't have a callback telling us when widget status changes, which is incongruent with the + // aforementioned assumption. This inconsistency _should_ be fine since any jobs scheduled + // before the widget is activated are definitely not for the widget and don't have to be updated + // to "satisfied=true". + private AppWidgetManager mAppWidgetManager; private final UsageStatsManagerInternal mUsageStatsManagerInternal; @GuardedBy("mLock") @@ -117,6 +125,11 @@ public class PrefetchController extends StateController { .registerLaunchTimeChangedListener(mEstimatedLaunchTimeChangedListener); } + @Override + public void onSystemServicesReady() { + mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class); + } + @Override @GuardedBy("mLock") public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { @@ -298,11 +311,23 @@ public class PrefetchController extends StateController { // Mark a prefetch constraint as satisfied in the following scenarios: // 1. The app is not open but it will be launched soon // 2. The app is open and the job is already running (so we let it finish) + // 3. The app is not open but has an active widget (we can't tell if a widget displays + // status/data, so this assumes the prefetch job is to update the data displayed on + // the widget). final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid()); final boolean satisfied; if (!appIsOpen) { - satisfied = willBeLaunchedSoonLocked( - jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now); + final int userId = jobStatus.getSourceUserId(); + final String pkgName = jobStatus.getSourcePackageName(); + satisfied = willBeLaunchedSoonLocked(userId, pkgName, now) + // At the time of implementation, isBoundWidgetPackage() results in a process ID + // check and then a lookup into a map. Calling the method here every time + // is based on the assumption that widgets won't change often and + // AppWidgetManager won't be a bottleneck, so having a local cache won't provide + // huge performance gains. If anything changes, we should reconsider having a + // local cache. + || (mAppWidgetManager != null + && mAppWidgetManager.isBoundWidgetPackage(pkgName, userId)); } else { satisfied = mService.isCurrentlyRunningLocked(jobStatus); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index b17ff53b166cc..95912b26e830f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -43,6 +43,7 @@ import android.app.AlarmManager; import android.app.job.JobInfo; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener; +import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; @@ -168,12 +169,15 @@ public class PrefetchControllerTest { } } - private JobStatus createJobStatus(String testTag, int jobId) { - JobInfo jobInfo = new JobInfo.Builder(jobId, + private JobInfo createJobInfo(int jobId) { + return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestPrefetchJobService")) .setPrefetch(true) .build(); - return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo); + } + + private JobStatus createJobStatus(String testTag, int jobId) { + return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, createJobInfo(jobId)); } private static JobStatus createJobStatus(String testTag, String packageName, int callingUid, @@ -330,6 +334,32 @@ public class PrefetchControllerTest { assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); } + @Test + public void testConstraintSatisfiedWhenWidget() { + final JobStatus jobNonWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 1); + final JobStatus jobWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 2); + + when(mUsageStatsManagerInternal + .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID)) + .thenReturn(sSystemClock.millis() + 100 * HOUR_IN_MILLIS); + + final AppWidgetManager appWidgetManager = mock(AppWidgetManager.class); + when(mContext.getSystemService(AppWidgetManager.class)).thenReturn(appWidgetManager); + mPrefetchController.onSystemServicesReady(); + + when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID)) + .thenReturn(false); + trackJobs(jobNonWidget); + verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS)) + .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID); + assertFalse(jobNonWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); + + when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID)) + .thenReturn(true); + trackJobs(jobWidget); + assertTrue(jobWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); + } + @Test public void testEstimatedLaunchTimeChangedToLate() { setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);