From 2d5fb8850a02709d1adeb74e0deeb63c653e2761 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Wed, 17 Nov 2021 10:35:23 -0800 Subject: [PATCH] Let prefetch jobs run for apps with active widgets. We currently can't tell if a widget is displaying data that is updated periodically. If a prefetch job is scheduled for such a widget, it should get to run since it'll be helpful for the user. Given what we know right now, we will mark the prefetch bit as satisfied for apps with active widgets. We won't relax the force-batching requirement unless we also determine that the app will be launched soon. Bug: 194532703 Test: atest FrameworksMockingServicesTests:PrefetchControllerTest Change-Id: I6d1c17c386a27e70525f72824710739b78e9ad5e --- .../job/controllers/PrefetchController.java | 29 +++++++++++++-- .../controllers/PrefetchControllerTest.java | 36 +++++++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) 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);