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

Commit 220e3107 authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 11641371 from fbc3ba8b to 24Q3-release

Change-Id: Ia31e34b3d4f47f4853d6eb08855665253d022b21
parents 270879e8 fbc3ba8b
Loading
Loading
Loading
Loading
+67 −1
Original line number Diff line number Diff line
@@ -290,6 +290,8 @@ public class AlarmManagerService extends SystemService {

    // List of alarms per uid deferred due to user applied background restrictions on the source app
    SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();

    private boolean mStartUserBeforeScheduledAlarms;
    private long mNextWakeup;
    private long mNextNonWakeup;
    private long mNextWakeUpSetAt;
@@ -1382,6 +1384,7 @@ public class AlarmManagerService extends SystemService {
    @GuardedBy("mLock")
    AlarmStore mAlarmStore;

    UserWakeupStore mUserWakeupStore;
    // set to non-null if in idle mode; while in this mode, any alarms we don't want
    // to run during this time are rescehduled to go off after this alarm.
    Alarm mPendingIdleUntil = null;
@@ -1882,6 +1885,7 @@ public class AlarmManagerService extends SystemService {
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);

        mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
        mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms();
        if (mUseFrozenStateToDropListenerAlarms) {
            final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
                final int size = frozenStates.length;
@@ -2000,6 +2004,10 @@ public class AlarmManagerService extends SystemService {
                Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
            }
        }
        if (mStartUserBeforeScheduledAlarms) {
            mUserWakeupStore = new UserWakeupStore();
            mUserWakeupStore.init();
        }
        publishLocalService(AlarmManagerInternal.class, new LocalService());
        publishBinderService(Context.ALARM_SERVICE, mService);
    }
@@ -2041,6 +2049,9 @@ public class AlarmManagerService extends SystemService {
    public void onUserStarting(TargetUser user) {
        super.onUserStarting(user);
        final int userId = user.getUserIdentifier();
        if (mStartUserBeforeScheduledAlarms) {
            mUserWakeupStore.onUserStarting(userId);
        }
        mHandler.post(() -> {
            for (final int appId : mExactAlarmCandidates) {
                final int uid = UserHandle.getUid(userId, appId);
@@ -3150,6 +3161,9 @@ public class AlarmManagerService extends SystemService {
            pw.increaseIndent();
            pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
                    mUseFrozenStateToDropListenerAlarms);
            pw.println();
            pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS,
                    mStartUserBeforeScheduledAlarms);
            pw.decreaseIndent();
            pw.println();
            pw.println();
@@ -3398,6 +3412,12 @@ public class AlarmManagerService extends SystemService {
            pw.println("]");
            pw.println();

            if (mStartUserBeforeScheduledAlarms) {
                pw.println("Scheduled user wakeups:");
                mUserWakeupStore.dump(pw, nowELAPSED);
                pw.println();
            }

            pw.println("App Alarm history:");
            mAppWakeupHistory.dump(pw, nowELAPSED);

@@ -3945,10 +3965,19 @@ public class AlarmManagerService extends SystemService {
                        formatNextAlarm(getContext(), alarmClock, userId));
            }
            mNextAlarmClockForUser.put(userId, alarmClock);
            if (mStartUserBeforeScheduledAlarms) {
                mUserWakeupStore.addUserWakeup(userId, convertToElapsed(
                        mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC));
            }
        } else {
            if (DEBUG_ALARM_CLOCK) {
                Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): None");
            }
            if (mStartUserBeforeScheduledAlarms) {
                if (mActivityManagerInternal.isUserRunning(userId, 0)) {
                    mUserWakeupStore.removeUserWakeup(userId);
                }
            }
            mNextAlarmClockForUser.remove(userId);
        }

@@ -4003,13 +4032,20 @@ public class AlarmManagerService extends SystemService {
                DateFormat.format(pattern, info.getTriggerTime()).toString();
    }

    @GuardedBy("mLock")
    void rescheduleKernelAlarmsLocked() {
        // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
        // prior to that which contains no wakeups, we schedule that as well.
        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
        long nextNonWakeup = 0;
        if (mAlarmStore.size() > 0) {
            final long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
            long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
            if (mStartUserBeforeScheduledAlarms) {
                final long firstUserWakeup = mUserWakeupStore.getNextWakeupTime();
                if (firstUserWakeup >= 0 && firstUserWakeup < firstWakeup) {
                    firstWakeup = firstUserWakeup;
                }
            }
            final long first = mAlarmStore.getNextDeliveryTime();
            if (firstWakeup != 0) {
                mNextWakeup = firstWakeup;
@@ -4716,6 +4752,16 @@ public class AlarmManagerService extends SystemService {
                                            + ", elapsed=" + nowELAPSED);
                        }

                        if (mStartUserBeforeScheduledAlarms) {
                            final int[] userIds =
                                    mUserWakeupStore.getUserIdsToWakeup(nowELAPSED);
                            for (int i = 0; i < userIds.length; i++) {
                                if (!mActivityManagerInternal.startUserInBackground(
                                        userIds[i])) {
                                    mUserWakeupStore.removeUserWakeup(userIds[i]);
                                }
                            }
                        }
                        mLastTrigger = nowELAPSED;
                        final int wakeUps = triggerAlarmsLocked(triggerList, nowELAPSED);
                        if (wakeUps == 0 && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
@@ -5164,6 +5210,10 @@ public class AlarmManagerService extends SystemService {
            IntentFilter sdFilter = new IntentFilter();
            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
            sdFilter.addAction(Intent.ACTION_USER_STOPPED);
            if (mStartUserBeforeScheduledAlarms) {
                sdFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
                sdFilter.addAction(Intent.ACTION_USER_REMOVED);
            }
            sdFilter.addAction(Intent.ACTION_UID_REMOVED);
            getContext().registerReceiverForAllUsers(this, sdFilter,
                    /* broadcastPermission */ null, /* scheduler */ null);
@@ -5194,6 +5244,22 @@ public class AlarmManagerService extends SystemService {
                            mTemporaryQuotaReserve.removeForUser(userHandle);
                        }
                        return;
                    case Intent.ACTION_LOCKED_BOOT_COMPLETED:
                        final int handle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                        if (handle >= 0) {
                            if (mStartUserBeforeScheduledAlarms) {
                                mUserWakeupStore.onUserStarted(handle);
                            }
                        }
                        return;
                    case Intent.ACTION_USER_REMOVED:
                        final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                        if (user >= 0) {
                            if (mStartUserBeforeScheduledAlarms) {
                                mUserWakeupStore.onUserRemoved(user);
                            }
                        }
                        return;
                    case Intent.ACTION_UID_REMOVED:
                        mLastPriorityAlarmDispatch.delete(uid);
                        mRemovalHistory.delete(uid);
+381 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.alarm;


import android.annotation.Nullable;
import android.os.Environment;
import android.os.SystemClock;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.Xml;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

/**
 * User wakeup store keeps the list of user ids with the times that user needs to be started in
 * sorted list in order for alarms to execute even if user gets stopped.
 * The list of user ids with at least one alarms scheduled is also persisted to the XML file to
 * start them after the device reboot.
 */
public class UserWakeupStore {
    private static final boolean DEBUG = false;

    static final String USER_WAKEUP_TAG = UserWakeupStore.class.getSimpleName();
    private static final String TAG_USERS = "users";
    private static final String TAG_USER = "user";
    private static final String ATTR_USER_ID = "user_id";
    private static final String ATTR_VERSION = "version";

    public static final int XML_VERSION_CURRENT = 1;
    @VisibleForTesting
    static final String ROOT_DIR_NAME = "alarms";
    @VisibleForTesting
    static final String USERS_FILE_NAME = "usersWithAlarmClocks.xml";

    /**
     * Time offset of user start before the original alarm time in milliseconds.
     * Also used to schedule user start after reboot to avoid starting them simultaneously.
     */
    @VisibleForTesting
    static final long BUFFER_TIME_MS = TimeUnit.SECONDS.toMillis(30);
    /**
     * Maximum time deviation limit to introduce a 5-second time window for user starts.
     */
    @VisibleForTesting
    static final long USER_START_TIME_DEVIATION_LIMIT_MS = TimeUnit.SECONDS.toMillis(5);
    /**
     * Delay between two consecutive user starts scheduled during user wakeup store initialization.
     */
    @VisibleForTesting
    static final long INITIAL_USER_START_SCHEDULING_DELAY_MS = TimeUnit.SECONDS.toMillis(5);

    private final Object mUserWakeupLock = new Object();

    /**
     * A list of wakeups for users with scheduled alarms.
     */
    @GuardedBy("mUserWakeupLock")
    private final SparseLongArray mUserStarts = new SparseLongArray();
    /**
     * A list of users that are in a phase after they have been started but before alarms were
     * initialized.
     */
    @GuardedBy("mUserWakeupLock")
    private final SparseLongArray mStartingUsers = new SparseLongArray();
    private Executor mBackgroundExecutor;
    private static final File USER_WAKEUP_DIR = new File(Environment.getDataSystemDirectory(),
            ROOT_DIR_NAME);
    private static final Random sRandom = new Random(500);

    /**
     * Initialize mUserWakeups with persisted values.
     */
    public void init() {
        mBackgroundExecutor = BackgroundThread.getExecutor();
        mBackgroundExecutor.execute(this::readUserIdList);
    }

    /**
     * Add user wakeup for the alarm.
     * @param userId Id of the user that scheduled alarm.
     * @param alarmTime time when alarm is expected to trigger.
     */
    public void addUserWakeup(int userId, long alarmTime) {
        synchronized (mUserWakeupLock) {
            // This should not be needed, but if an app in the user is scheduling an alarm clock, we
            // consider the user start complete. There is a dedicated removal when user is started.
            mStartingUsers.delete(userId);
            mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
        }
        updateUserListFile();
    }

    /**
     * Remove wakeup scheduled for the user with given userId if present.
     */
    public void removeUserWakeup(int userId) {
        synchronized (mUserWakeupLock) {
            mUserStarts.delete(userId);
        }
        updateUserListFile();
    }

    /**
     * Get ids of users that need to be started now.
     * @param nowElapsed current time.
     * @return user ids to be started, or empty if no user needs to be started.
     */
    public int[] getUserIdsToWakeup(long nowElapsed) {
        synchronized (mUserWakeupLock) {
            final int[] userIds = new int[mUserStarts.size()];
            int index = 0;
            for (int i = mUserStarts.size() - 1; i >= 0; i--) {
                if (mUserStarts.valueAt(i) <= nowElapsed) {
                    userIds[index++] = mUserStarts.keyAt(i);
                }
            }
            return Arrays.copyOfRange(userIds, 0, index);
        }
    }

    /**
     * Persist user ids that have alarms scheduled so that they can be started after device reboot.
     */
    private void updateUserListFile() {
        mBackgroundExecutor.execute(() -> {
            try {
                writeUserIdList();
                if (DEBUG) {
                    synchronized (mUserWakeupLock) {
                        Slog.i(USER_WAKEUP_TAG, "Printing out user wakeups " + mUserStarts.size());
                        for (int i = 0; i < mUserStarts.size(); i++) {
                            Slog.i(USER_WAKEUP_TAG, "User id: " + mUserStarts.keyAt(i) + "  time: "
                                    + mUserStarts.valueAt(i));
                        }
                    }
                }
            } catch (Exception e) {
                Slog.e(USER_WAKEUP_TAG, "Failed to write " + e.getLocalizedMessage());
            }
        });
    }

    /**
     * Return scheduled start time for user or -1 if user does not have alarm set.
     */
    @VisibleForTesting
    long getWakeupTimeForUserForTest(int userId) {
        synchronized (mUserWakeupLock) {
            return mUserStarts.get(userId, -1);
        }
    }

    /**
     * Move user from wakeup list to starting user list.
     */
    public void onUserStarting(int userId) {
        synchronized (mUserWakeupLock) {
            mStartingUsers.put(userId, getWakeupTimeForUserForTest(userId));
            mUserStarts.delete(userId);
        }
    }

    /**
     * Remove userId from starting user list once start is complete.
     */
    public void onUserStarted(int userId) {
        synchronized (mUserWakeupLock) {
            mStartingUsers.delete(userId);
        }
        updateUserListFile();
    }

    /**
     * Remove userId from the store when the user is removed.
     */
    public void onUserRemoved(int userId) {
        synchronized (mUserWakeupLock) {
            mUserStarts.delete(userId);
            mStartingUsers.delete(userId);
        }
        updateUserListFile();
    }

    /**
     * Get the soonest wakeup time in the store.
     */
    public long getNextWakeupTime() {
        long nextWakeupTime = -1;
        synchronized (mUserWakeupLock) {
            for (int i = 0; i < mUserStarts.size(); i++) {
                if (mUserStarts.valueAt(i) < nextWakeupTime || nextWakeupTime == -1) {
                    nextWakeupTime = mUserStarts.valueAt(i);
                }
            }
        }
        return nextWakeupTime;
    }

    private static long getUserWakeupOffset() {
        return sRandom.nextLong(USER_START_TIME_DEVIATION_LIMIT_MS * 2)
                - USER_START_TIME_DEVIATION_LIMIT_MS;
    }

    /**
     * Write a list of ids for users who have alarm scheduled.
     * Sample XML file:
     *
     * <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
     * <users version="1">
     * <user user_id="12" />
     * <user user_id="10" />
     * </users>
     * ~
     */
    private void writeUserIdList() {
        final AtomicFile file = getUserWakeupFile();
        if (file == null) {
            return;
        }
        try (FileOutputStream fos = file.startWrite(SystemClock.uptimeMillis())) {
            final XmlSerializer out = new FastXmlSerializer();
            out.setOutput(fos, StandardCharsets.UTF_8.name());
            out.startDocument(null, true);
            out.startTag(null, TAG_USERS);
            XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
            final List<Pair<Integer, Long>> listOfUsers = new ArrayList<>();
            synchronized (mUserWakeupLock) {
                for (int i = 0; i < mUserStarts.size(); i++) {
                    listOfUsers.add(new Pair<>(mUserStarts.keyAt(i), mUserStarts.valueAt(i)));
                }
                for (int i = 0; i < mStartingUsers.size(); i++) {
                    listOfUsers.add(new Pair<>(mStartingUsers.keyAt(i), mStartingUsers.valueAt(i)));
                }
            }
            Collections.sort(listOfUsers, Comparator.comparingLong(pair -> pair.second));
            for (int i = 0; i < listOfUsers.size(); i++) {
                out.startTag(null, TAG_USER);
                XmlUtils.writeIntAttribute(out, ATTR_USER_ID, listOfUsers.get(i).first);
                out.endTag(null, TAG_USER);
            }
            out.endTag(null, TAG_USERS);
            out.endDocument();
            file.finishWrite(fos);
        } catch (IOException e) {
            Slog.wtf(USER_WAKEUP_TAG, "Error writing user wakeup data", e);
            file.delete();
        }
    }

    private void readUserIdList() {
        final AtomicFile userWakeupFile = getUserWakeupFile();
        if (userWakeupFile == null) {
            return;
        } else if (!userWakeupFile.exists()) {
            Slog.w(USER_WAKEUP_TAG, "User wakeup file not available: "
                    + userWakeupFile.getBaseFile());
            return;
        }
        synchronized (mUserWakeupLock) {
            mUserStarts.clear();
            mStartingUsers.clear();
        }
        try (FileInputStream fis = userWakeupFile.openRead()) {
            final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG
                    && type != XmlPullParser.END_DOCUMENT) {
                // Skip
            }
            if (type != XmlPullParser.START_TAG) {
                Slog.e(USER_WAKEUP_TAG, "Unable to read user list. No start tag found in "
                        + userWakeupFile.getBaseFile());
                return;
            }
            int version = -1;
            if (parser.getName().equals(TAG_USERS)) {
                version = parser.getAttributeInt(null, ATTR_VERSION, version);
            }

            long counter = 0;
            final long currentTime = SystemClock.elapsedRealtime();
            // Time delay between now and first user wakeup is scheduled.
            final long scheduleOffset = currentTime + BUFFER_TIME_MS + getUserWakeupOffset();
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                if (type == XmlPullParser.START_TAG) {
                    if (parser.getName().equals(TAG_USER)) {
                        final int id = parser.getAttributeInt(null, ATTR_USER_ID);
                        synchronized (mUserWakeupLock) {
                            mUserStarts.put(id, scheduleOffset + (counter++
                                    * INITIAL_USER_START_SCHEDULING_DELAY_MS));
                        }
                    }
                }
            }
        } catch (IOException | XmlPullParserException e) {
            Slog.wtf(USER_WAKEUP_TAG, "Error reading user wakeup data", e);
        }
    }

    @Nullable
    private AtomicFile getUserWakeupFile() {
        if (!USER_WAKEUP_DIR.exists() && !USER_WAKEUP_DIR.mkdir()) {
            Slog.wtf(USER_WAKEUP_TAG, "Failed to mkdir() user list file: " + USER_WAKEUP_DIR);
            return null;
        }
        final File userFile = new File(USER_WAKEUP_DIR, USERS_FILE_NAME);
        return new AtomicFile(userFile);
    }

    void dump(IndentingPrintWriter pw, long nowELAPSED) {
        synchronized (mUserWakeupLock) {
            pw.increaseIndent();
            pw.print("User wakeup store file path: ");
            final AtomicFile file = getUserWakeupFile();
            if (file == null) {
                pw.println("null");
            } else {
                pw.println(file.getBaseFile().getAbsolutePath());
            }
            pw.println(mUserStarts.size() + " user wakeups scheduled: ");
            for (int i = 0; i < mUserStarts.size(); i++) {
                pw.print("UserId: ");
                pw.print(mUserStarts.keyAt(i));
                pw.print(", userStartTime: ");
                TimeUtils.formatDuration(mUserStarts.valueAt(i), nowELAPSED, pw);
                pw.println();
            }
            pw.println(mStartingUsers.size() + " starting users: ");
            for (int i = 0; i < mStartingUsers.size(); i++) {
                pw.print("UserId: ");
                pw.print(mStartingUsers.keyAt(i));
                pw.print(", userStartTime: ");
                TimeUtils.formatDuration(mStartingUsers.valueAt(i), nowELAPSED, pw);
                pw.println();
            }
            pw.decreaseIndent();
        }
    }
}
+16 −6
Original line number Diff line number Diff line
@@ -304,6 +304,8 @@ public class JobSchedulerService extends com.android.server.SystemService
    private final ConnectivityController mConnectivityController;
    /** Need directly for sending uid state changes */
    private final DeviceIdleJobsController mDeviceIdleJobsController;
    /** Need direct access to this for testing. */
    private final FlexibilityController mFlexibilityController;
    /** Needed to get next estimated launch time. */
    private final PrefetchController mPrefetchController;
    /** Needed to get remaining quota time. */
@@ -2701,17 +2703,16 @@ public class JobSchedulerService extends com.android.server.SystemService
        mControllers = new ArrayList<StateController>();
        mPrefetchController = new PrefetchController(this);
        mControllers.add(mPrefetchController);
        final FlexibilityController flexibilityController =
                new FlexibilityController(this, mPrefetchController);
        mControllers.add(flexibilityController);
        mFlexibilityController = new FlexibilityController(this, mPrefetchController);
        mControllers.add(mFlexibilityController);
        mConnectivityController =
                new ConnectivityController(this, flexibilityController);
                new ConnectivityController(this, mFlexibilityController);
        mControllers.add(mConnectivityController);
        mControllers.add(new TimeController(this));
        final IdleController idleController = new IdleController(this, flexibilityController);
        final IdleController idleController = new IdleController(this, mFlexibilityController);
        mControllers.add(idleController);
        final BatteryController batteryController =
                new BatteryController(this, flexibilityController);
                new BatteryController(this, mFlexibilityController);
        mControllers.add(batteryController);
        mStorageController = new StorageController(this);
        mControllers.add(mStorageController);
@@ -5561,6 +5562,15 @@ public class JobSchedulerService extends com.android.server.SystemService
        return 0;
    }

    // Shell command infrastructure: set flex policy
    void setFlexPolicy(boolean override, int appliedConstraints) {
        if (DEBUG) {
            Slog.v(TAG, "setFlexPolicy(): " + override + "/" + appliedConstraints);
        }

        mFlexibilityController.setLocalPolicyForTesting(override, appliedConstraints);
    }

    void setMonitorBattery(boolean enabled) {
        synchronized (mLock) {
            mBatteryStateTracker.setMonitorBatteryLocked(enabled);
+88 −0

File changed.

Preview size limit exceeded, changes collapsed.

+30 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading