Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2d5fb885 authored by Kweku Adams's avatar Kweku Adams
Browse files

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
parent ddb27f8d
Loading
Loading
Loading
Loading
+27 −2
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import android.annotation.NonNull;
import android.app.job.JobInfo;
import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Context;
import android.os.Handler;
import android.os.Handler;
import android.os.Looper;
import android.os.Looper;
@@ -63,6 +64,13 @@ public class PrefetchController extends StateController {
    private final PcConstants mPcConstants;
    private final PcConstants mPcConstants;
    private final PcHandler mHandler;
    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;
    private final UsageStatsManagerInternal mUsageStatsManagerInternal;


    @GuardedBy("mLock")
    @GuardedBy("mLock")
@@ -117,6 +125,11 @@ public class PrefetchController extends StateController {
                .registerLaunchTimeChangedListener(mEstimatedLaunchTimeChangedListener);
                .registerLaunchTimeChangedListener(mEstimatedLaunchTimeChangedListener);
    }
    }


    @Override
    public void onSystemServicesReady() {
        mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
    }

    @Override
    @Override
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
    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:
        // Mark a prefetch constraint as satisfied in the following scenarios:
        //   1. The app is not open but it will be launched soon
        //   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)
        //   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 appIsOpen = mTopUids.get(jobStatus.getSourceUid());
        final boolean satisfied;
        final boolean satisfied;
        if (!appIsOpen) {
        if (!appIsOpen) {
            satisfied = willBeLaunchedSoonLocked(
            final int userId = jobStatus.getSourceUserId();
                    jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now);
            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 {
        } else {
            satisfied = mService.isCurrentlyRunningLocked(jobStatus);
            satisfied = mService.isCurrentlyRunningLocked(jobStatus);
        }
        }
+33 −3
Original line number Original line Diff line number Diff line
@@ -43,6 +43,7 @@ import android.app.AlarmManager;
import android.app.job.JobInfo;
import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.content.pm.ServiceInfo;
@@ -168,12 +169,15 @@ public class PrefetchControllerTest {
        }
        }
    }
    }


    private JobStatus createJobStatus(String testTag, int jobId) {
    private JobInfo createJobInfo(int jobId) {
        JobInfo jobInfo = new JobInfo.Builder(jobId,
        return new JobInfo.Builder(jobId,
                new ComponentName(mContext, "TestPrefetchJobService"))
                new ComponentName(mContext, "TestPrefetchJobService"))
                .setPrefetch(true)
                .setPrefetch(true)
                .build();
                .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,
    private static JobStatus createJobStatus(String testTag, String packageName, int callingUid,
@@ -330,6 +334,32 @@ public class PrefetchControllerTest {
        assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
        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
    @Test
    public void testEstimatedLaunchTimeChangedToLate() {
    public void testEstimatedLaunchTimeChangedToLate() {
        setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
        setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);