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

Commit ca7fb4bc authored by Kweku Adams's avatar Kweku Adams
Browse files

Use non-wakeup alarm for delay constraint.

The delay alarm does not need to go off exactly when the time has
passed, so a non-wakeup alarm is suitable for the delay constraint.
A job is expected to go off as soon as its deadline constraint is
satisfied, so we leave the deadline alarm as a wakeup alarm.

Bug: 138465403
Test: atest com.android.server.job.controllers.TimeControllerTest
Test: atest CtsJobSchedulerTestCases
Change-Id: I5756bd805c68dea51b3a5aa74419b195407e1a30
parent 8cfff7b8
Loading
Loading
Loading
Loading
+113 −13
Original line number Diff line number Diff line
@@ -18,13 +18,20 @@ package com.android.server.job.controllers;

import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
@@ -32,6 +39,7 @@ import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.job.ConstantsProto;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;

@@ -55,6 +63,9 @@ public final class TimeController extends StateController {
    /** Delay alarm tag for logging purposes */
    private final String DELAY_TAG = "*job.delay*";

    private final Handler mHandler;
    private final TcConstants mTcConstants;

    private long mNextJobExpiredElapsedMillis;
    private long mNextDelayExpiredElapsedMillis;

@@ -70,6 +81,14 @@ public final class TimeController extends StateController {
        mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
        mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
        mChainedAttributionEnabled = mService.isChainedAttributionEnabled();

        mHandler = new Handler(mContext.getMainLooper());
        mTcConstants = new TcConstants(mHandler);
    }

    @Override
    public void onSystemServicesReady() {
        mTcConstants.start(mContext.getContentResolver());
    }

    /**
@@ -294,8 +313,7 @@ public final class TimeController extends StateController {
                } else {
                    if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
                        if (DEBUG) {
                            Slog.i(TAG,
                                    "Skipping " + job + " because delay won't make it ready.");
                            Slog.i(TAG, "Skipping " + job + " because delay won't make it ready.");
                        }
                        continue;
                    }
@@ -354,7 +372,8 @@ public final class TimeController extends StateController {
    /**
     * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
     * delay will expire.
     * This alarm <b>will</b> wake up the phone.
     * This alarm <b>will not</b> wake up the phone if
     * {@link TcConstants#USE_NON_WAKEUP_ALARM_FOR_DELAY} is true.
     */
    private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
        alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
@@ -362,8 +381,11 @@ public final class TimeController extends StateController {
            return;
        }
        mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
        updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
                mNextDelayExpiredElapsedMillis, ws);
        final int alarmType =
                mTcConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY
                        ? AlarmManager.ELAPSED_REALTIME : AlarmManager.ELAPSED_REALTIME_WAKEUP;
        updateAlarmWithListenerLocked(DELAY_TAG, alarmType,
                mNextDelayExpiredListener, mNextDelayExpiredElapsedMillis, ws);
    }

    /**
@@ -377,16 +399,16 @@ public final class TimeController extends StateController {
            return;
        }
        mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
        updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
                mNextJobExpiredElapsedMillis, ws);
        updateAlarmWithListenerLocked(DEADLINE_TAG, AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mDeadlineExpiredListener, mNextJobExpiredElapsedMillis, ws);
    }

    private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
        return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis());
    }

    private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
            long alarmTimeElapsed, WorkSource ws) {
    private void updateAlarmWithListenerLocked(String tag, @AlarmManager.AlarmType int alarmType,
            OnAlarmListener listener, long alarmTimeElapsed, WorkSource ws) {
        ensureAlarmServiceLocked();
        if (alarmTimeElapsed == Long.MAX_VALUE) {
            mAlarmService.cancel(listener);
@@ -394,7 +416,7 @@ public final class TimeController extends StateController {
            if (DEBUG) {
                Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
            }
            mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
            mAlarmService.set(alarmType, alarmTimeElapsed,
                    AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws);
        }
    }
@@ -422,9 +444,77 @@ public final class TimeController extends StateController {
    };

    @VisibleForTesting
    void recheckAlarmsLocked() {
        checkExpiredDeadlinesAndResetAlarm();
        checkExpiredDelaysAndResetAlarm();
    class TcConstants extends ContentObserver {
        private ContentResolver mResolver;
        private final KeyValueListParser mParser = new KeyValueListParser(',');

        private static final String KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY =
                "use_non_wakeup_delay_alarm";

        private static final boolean DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY = true;

        /**
         * Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
         * ready now.
         */
        public boolean USE_NON_WAKEUP_ALARM_FOR_DELAY = DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY;

        /**
         * Creates a content observer.
         *
         * @param handler The handler to run {@link #onChange} on, or null if none.
         */
        TcConstants(Handler handler) {
            super(handler);
        }

        private void start(ContentResolver resolver) {
            mResolver = resolver;
            mResolver.registerContentObserver(Settings.Global.getUriFor(
                    Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS), false, this);
            onChange(true, null);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            final String constants = Settings.Global.getString(
                    mResolver, Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS);

            try {
                mParser.setString(constants);
            } catch (Exception e) {
                // Failed to parse the settings string, log this and move on with defaults.
                Slog.e(TAG, "Bad jobscheduler time controller settings", e);
            }

            USE_NON_WAKEUP_ALARM_FOR_DELAY = mParser.getBoolean(
                    KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY, DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY);
            // Intentionally not calling checkExpiredDelaysAndResetAlarm() here. There's no need to
            // iterate through the entire list again for this constant change. The next delay alarm
            // that is set will make use of the new constant value.
        }

        private void dump(IndentingPrintWriter pw) {
            pw.println();
            pw.println("TimeController:");
            pw.increaseIndent();
            pw.printPair(KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY,
                    USE_NON_WAKEUP_ALARM_FOR_DELAY).println();
            pw.decreaseIndent();
        }

        private void dump(ProtoOutputStream proto) {
            final long tcToken = proto.start(ConstantsProto.TIME_CONTROLLER);
            proto.write(ConstantsProto.TimeController.USE_NON_WAKEUP_ALARM_FOR_DELAY,
                    USE_NON_WAKEUP_ALARM_FOR_DELAY);
            proto.end(tcToken);
        }
    }

    @VisibleForTesting
    @NonNull
    TcConstants getTcConstants() {
        return mTcConstants;
    }

    @Override
@@ -501,4 +591,14 @@ public final class TimeController extends StateController {
        proto.end(mToken);
        proto.end(token);
    }

    @Override
    public void dumpConstants(IndentingPrintWriter pw) {
        mTcConstants.dump(pw);
    }

    @Override
    public void dumpConstants(ProtoOutputStream proto) {
        mTcConstants.dump(proto);
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -308,6 +308,8 @@ message ConstantsProto {
        // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
        // ready now.
        reserved 1; // skip_not_ready_jobs
        // Whether or not TimeController will use a non-wakeup alarm for delay constraints.
        optional bool use_non_wakeup_alarm_for_delay = 2;
    }
    optional TimeController time_controller = 25;

+42 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ public class TimeControllerTest {
    private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
    private static final int SOURCE_USER_ID = 0;

    private TimeController.TcConstants mConstants;
    private TimeController mTimeController;

    private MockitoSession mMockingSession;
@@ -110,6 +111,7 @@ public class TimeControllerTest {

        // Initialize real objects.
        mTimeController = new TimeController(mJobSchedulerService);
        mConstants = mTimeController.getTcConstants();
        spyOn(mTimeController);
    }

@@ -527,6 +529,46 @@ public class TimeControllerTest {
                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
    }

    @Test
    public void testJobDelayWakeupAlarmToggling() {
        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();

        JobStatus job = createJobStatus(
                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
                createJob().setMinimumLatency(HOUR_IN_MILLIS));

        doReturn(true).when(mTimeController)
                .wouldBeReadyWithConstraintLocked(eq(job), anyInt());

        // Starting off with using a wakeup alarm.
        mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = false;
        InOrder inOrder = inOrder(mAlarmManager);

        mTimeController.maybeStartTrackingJobLocked(job, null);
        inOrder.verify(mAlarmManager, times(1))
                .set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(now + HOUR_IN_MILLIS), anyLong(),
                        anyLong(),
                        eq(TAG_DELAY), any(), any(), any());

        // Use a non wakeup alarm.
        mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = true;

        mTimeController.maybeStartTrackingJobLocked(job, null);
        inOrder.verify(mAlarmManager, times(1))
                .set(eq(AlarmManager.ELAPSED_REALTIME), eq(now + HOUR_IN_MILLIS), anyLong(),
                        anyLong(), eq(TAG_DELAY),
                        any(), any(), any());

        // Back off, use a wakeup alarm.
        mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = false;

        mTimeController.maybeStartTrackingJobLocked(job, null);
        inOrder.verify(mAlarmManager, times(1))
                .set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(now + HOUR_IN_MILLIS), anyLong(),
                        anyLong(),
                        eq(TAG_DELAY), any(), any(), any());
    }

    @Test
    public void testCheckExpiredDeadlinesAndResetAlarm_AllReady() {
        doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());