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

Commit c10a4c7f authored by Yisroel Forta's avatar Yisroel Forta
Browse files

Fix getHistoricalProcessStartReasons ordering

Ordering is currently done with launch timestamp, however, this is
set to monotonic time since boot which can cause an ordering issue
with persisted records after a reboot.

To fix this, introduce a private created timestamp set to
monotonic time that persists across reboots, thereby ensuring that
records remain ordered by time they were added across reboots and
time changes.

Test: Start device, let run for a bit, trigger a record, wait for
persist, reboot, trigger another record, check and confirm
ordering is correct by making sure they're ordered greatest to
lowest by created time and that timestamp 0 is not ordered.
Test: run new tests

Bug: 360299551

Flag: EXEMPT - bugfix
Change-Id: I45cbae99c6e6848498ba0c668bead099042948ce
parent aba7df49
Loading
Loading
Loading
Loading
+30 −4
Original line number Diff line number Diff line
@@ -209,6 +209,11 @@ public final class ApplicationStartInfo implements Parcelable {
    /** Clock monotonic timestamp of surfaceflinger composition complete. */
    public static final int START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE = 7;

    /**
     * @see #getMonoticCreationTimeMs
     */
    private long mMonoticCreationTimeMs;

    /**
     * @see #getStartupState
     */
@@ -486,6 +491,15 @@ public final class ApplicationStartInfo implements Parcelable {
        return mStartupState;
    }

    /**
     * Monotonic elapsed time persisted across reboots.
     *
     * @hide
     */
    public long getMonoticCreationTimeMs() {
        return mMonoticCreationTimeMs;
    }

    /**
     * The process id.
     *
@@ -669,7 +683,9 @@ public final class ApplicationStartInfo implements Parcelable {
    }

    /** @hide */
    public ApplicationStartInfo() {}
    public ApplicationStartInfo(long monotonicCreationTimeMs) {
        mMonoticCreationTimeMs = monotonicCreationTimeMs;
    }

    /** @hide */
    public ApplicationStartInfo(ApplicationStartInfo other) {
@@ -686,6 +702,7 @@ public final class ApplicationStartInfo implements Parcelable {
        mStartIntent = other.mStartIntent;
        mLaunchMode = other.mLaunchMode;
        mWasForceStopped = other.mWasForceStopped;
        mMonoticCreationTimeMs = other.mMonoticCreationTimeMs;
    }

    private ApplicationStartInfo(@NonNull Parcel in) {
@@ -708,6 +725,7 @@ public final class ApplicationStartInfo implements Parcelable {
                in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
        mLaunchMode = in.readInt();
        mWasForceStopped = in.readBoolean();
        mMonoticCreationTimeMs = in.readLong();
    }

    private static String intern(@Nullable String source) {
@@ -786,6 +804,7 @@ public final class ApplicationStartInfo implements Parcelable {
        }
        proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
        proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
        proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonoticCreationTimeMs);
        proto.end(token);
    }

@@ -869,6 +888,10 @@ public final class ApplicationStartInfo implements Parcelable {
                    mWasForceStopped = proto.readBoolean(
                            ApplicationStartInfoProto.WAS_FORCE_STOPPED);
                    break;
                case (int) ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS:
                    mMonoticCreationTimeMs = proto.readLong(
                            ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS);
                    break;
            }
        }
        proto.end(token);
@@ -881,6 +904,8 @@ public final class ApplicationStartInfo implements Parcelable {
        sb.append(prefix)
                .append("ApplicationStartInfo ").append(seqSuffix).append(':')
                .append('\n')
                .append(" monotonicCreationTimeMs=").append(mMonoticCreationTimeMs)
                .append('\n')
                .append(" pid=").append(mPid)
                .append(" realUid=").append(mRealUid)
                .append(" packageUid=").append(mPackageUid)
@@ -949,14 +974,15 @@ public final class ApplicationStartInfo implements Parcelable {
            && mDefiningUid == o.mDefiningUid && mReason == o.mReason
            && mStartupState == o.mStartupState && mStartType == o.mStartType
            && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
            && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped;
            && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped
            && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
                mStartType, mLaunchMode, mProcessName,
                mStartupTimestampsNs);
                mStartType, mLaunchMode, mProcessName, mStartupTimestampsNs,
                mMonoticCreationTimeMs);
    }

    private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
+1 −0
Original line number Diff line number Diff line
@@ -40,4 +40,5 @@ message ApplicationStartInfoProto {
    optional bytes start_intent = 10;
    optional AppStartLaunchMode launch_mode = 11;
    optional bool was_force_stopped = 12;
    optional int64 monotonic_creation_time_ms = 13;
}
+3 −1
Original line number Diff line number Diff line
@@ -1045,7 +1045,7 @@ message AppsExitInfoProto {
    repeated Package packages = 2;
}

// sync with com.android.server.am.am.ProcessList.java
// LINT.IfChange
message AppsStartInfoProto {
    option (.android.msg_privacy).dest = DEST_AUTOMATIC;

@@ -1064,4 +1064,6 @@ message AppsStartInfoProto {
        repeated User users = 2;
    }
    repeated Package packages = 2;
    optional int64 monotonic_time = 3;
}
// LINT.ThenChange(/services/core/java/com/android/server/am/AppStartInfoTracker.java)
+45 −18
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.server.am;

import static android.app.ApplicationStartInfo.START_TIMESTAMP_LAUNCH;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;

import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -51,6 +50,8 @@ import android.util.proto.WireTypeMismatchException;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ProcessMap;
import com.android.internal.os.Clock;
import com.android.internal.os.MonotonicClock;
import com.android.server.IoThread;
import com.android.server.ServiceThread;
import com.android.server.SystemServiceManager;
@@ -107,6 +108,16 @@ public final class AppStartInfoTracker {

    @VisibleForTesting boolean mEnabled = false;

    /**
     * Monotonic clock which does not reset on reboot.
     *
     * Time for offset is persisted along with records, see {@link #persistProcessStartInfo}.
     * This does not follow the recommendation of {@link MonotonicClock} to persist on shutdown as
     * it's ok in this case to lose any time change past the last persist as records added since
     * then will be lost as well and the purpose of this clock is to keep records in order.
     */
    @VisibleForTesting MonotonicClock mMonotonicClock = null;

    /** Initialized in {@link #init} and read-only after that. */
    @VisibleForTesting ActivityManagerService mService;

@@ -203,6 +214,15 @@ public final class AppStartInfoTracker {
        IoThread.getHandler().post(() -> {
            loadExistingProcessStartInfo();
        });

        if (mMonotonicClock == null) {
            // This should only happen if there are no persisted records, or if the records were
            // persisted by a version without the monotonic clock. Either way, create a new clock
            // with no offset. In the case of records with no monotonic time the value will default
            // to 0 and all new records will correctly end up in front of them.
            mMonotonicClock = new MonotonicClock(Clock.SYSTEM_CLOCK.elapsedRealtime(),
                    Clock.SYSTEM_CLOCK);
        }
    }

    /**
@@ -264,7 +284,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo();
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.setIntent(intent);
            start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
@@ -396,7 +416,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo();
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            addBaseFieldsFromProcessRecord(start, app);
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.addStartupTimestamp(
@@ -422,7 +442,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo();
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            addBaseFieldsFromProcessRecord(start, app);
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.addStartupTimestamp(
@@ -444,7 +464,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo();
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            addBaseFieldsFromProcessRecord(start, app);
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.addStartupTimestamp(
@@ -461,7 +481,7 @@ public final class AppStartInfoTracker {
            if (!mEnabled) {
                return;
            }
            ApplicationStartInfo start = new ApplicationStartInfo();
            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
            addBaseFieldsFromProcessRecord(start, app);
            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
            start.addStartupTimestamp(
@@ -632,7 +652,8 @@ public final class AppStartInfoTracker {

                    Collections.sort(
                            list, (a, b) ->
                            Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
                            Long.compare(b.getMonoticCreationTimeMs(),
                                    a.getMonoticCreationTimeMs()));
                    int size = list.size();
                    if (maxNum > 0) {
                        size = Math.min(size, maxNum);
@@ -898,6 +919,10 @@ public final class AppStartInfoTracker {
                    case (int) AppsStartInfoProto.PACKAGES:
                        loadPackagesFromProto(proto, next);
                        break;
                    case (int) AppsStartInfoProto.MONOTONIC_TIME:
                        long monotonicTime = proto.readLong(AppsStartInfoProto.MONOTONIC_TIME);
                        mMonotonicClock = new MonotonicClock(monotonicTime, Clock.SYSTEM_CLOCK);
                        break;
                }
            }
        } catch (IOException | IllegalArgumentException | WireTypeMismatchException
@@ -979,6 +1004,7 @@ public final class AppStartInfoTracker {
                    mLastAppStartInfoPersistTimestamp = now;
                }
            }
            proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTime());
            if (succeeded) {
                proto.flush();
                af.finishWrite(out);
@@ -1099,13 +1125,12 @@ public final class AppStartInfoTracker {
        }
    }

    /** Convenience method to obtain timestamp of beginning of start.*/
    private static long getStartTimestamp(ApplicationStartInfo startInfo) {
        if (startInfo.getStartupTimestamps() == null
                    || !startInfo.getStartupTimestamps().containsKey(START_TIMESTAMP_LAUNCH)) {
            return -1;
    private long getMonotonicTime() {
        if (mMonotonicClock == null) {
            // This should never happen. Return 0 to not interfere with past or future records.
            return 0;
        }
        return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
        return mMonotonicClock.monotonicTime();
    }

    /** A container class of (@link android.app.ApplicationStartInfo) */
@@ -1143,7 +1168,7 @@ public final class AppStartInfoTracker {

            // Sort records so we can remove the least recent ones.
            Collections.sort(mInfos, (a, b) ->
                    Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
                    Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));

            // Remove records and trim list object back to size.
            mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear();
@@ -1165,8 +1190,8 @@ public final class AppStartInfoTracker {
                long oldestTimeStamp = Long.MAX_VALUE;
                for (int i = 0; i < size; i++) {
                    ApplicationStartInfo startInfo = mInfos.get(i);
                    if (getStartTimestamp(startInfo) < oldestTimeStamp) {
                        oldestTimeStamp = getStartTimestamp(startInfo);
                    if (startInfo.getMonoticCreationTimeMs() < oldestTimeStamp) {
                        oldestTimeStamp = startInfo.getMonoticCreationTimeMs();
                        oldestIndex = i;
                    }
                }
@@ -1176,7 +1201,7 @@ public final class AppStartInfoTracker {
            }
            mInfos.add(info);
            Collections.sort(mInfos, (a, b) ->
                    Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
                    Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
        }

        /**
@@ -1337,7 +1362,9 @@ public final class AppStartInfoTracker {
                        mUid = proto.readInt(AppsStartInfoProto.Package.User.UID);
                        break;
                    case (int) AppsStartInfoProto.Package.User.APP_START_INFO:
                        ApplicationStartInfo info = new ApplicationStartInfo();
                        // Create record with monotonic time 0 in case the persisted record does not
                        // have a create time.
                        ApplicationStartInfo info = new ApplicationStartInfo(0);
                        info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
                        mInfos.add(info);
                        break;
+84 −4
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static com.android.server.am.ActivityManagerService.Injector;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -42,6 +43,8 @@ import android.os.Process;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;

import com.android.internal.os.Clock;
import com.android.internal.os.MonotonicClock;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.appop.AppOpsService;
@@ -121,11 +124,18 @@ public class ApplicationStartInfoTest {
        LocalServices.removeServiceForTest(PackageManagerInternal.class);
        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);

        mAppStartInfoTracker.mMonotonicClock = new MonotonicClock(
                Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
        mAppStartInfoTracker.clearProcessStartInfo(true);
        mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
        mAppStartInfoTracker.mAppStartInfoHistoryListSize =
                mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
        doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());

        mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
                AppStartInfoTracker.APP_START_STORE_DIR);
        mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
                AppStartInfoTracker.APP_START_INFO_FILE);
    }

    @After
@@ -135,11 +145,8 @@ public class ApplicationStartInfoTest {

    @Test
    public void testApplicationStartInfo() throws Exception {
        mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
                AppStartInfoTracker.APP_START_STORE_DIR);
        // Make sure we can write to the file.
        assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
        mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
                AppStartInfoTracker.APP_START_INFO_FILE);

        final long appStartTimestampIntentStarted = 1000000;
        final long appStartTimestampActivityLaunchFinished = 2000000;
@@ -482,6 +489,79 @@ public class ApplicationStartInfoTest {
        verifyInProgressRecordsSize(AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS);
    }

    /**
     * Test to make sure that records are returned in correct order, from most recently added at
     * index 0 to least recently added at index size - 1.
     */
    @Test
    public void testHistoricalRecordsOrdering() throws Exception {
        // Clear old records
        mAppStartInfoTracker.clearProcessStartInfo(false);

        // Add some records with timestamps 0 decreasing as clock increases.
        ProcessRecord app = makeProcessRecord(
                APP_1_PID_1,                     // pid
                APP_1_UID,                       // uid
                APP_1_UID,                       // packageUid
                null,                            // definingUid
                APP_1_PROCESS_NAME,              // processName
                APP_1_PACKAGE_NAME);             // packageName

        mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT),
                false /* isAlarm */);
        mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT),
                false /* isAlarm */);
        mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT),
                false /* isAlarm */);

        // Get records
        ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);

        // Confirm that records are in correct order, with index 0 representing the most recently
        // added record and index size - 1 representing the least recently added one.
        assertEquals(3, list.size());
        assertEquals(1L, list.get(0).getStartupTimestamps().get(0).longValue());
        assertEquals(2L, list.get(1).getStartupTimestamps().get(0).longValue());
        assertEquals(3L, list.get(2).getStartupTimestamps().get(0).longValue());
    }

    /**
     * Test to make sure that persist and restore correctly maintains the state of the monotonic
     * clock.
     */
    @Test
    public void testPersistAndRestoreMonotonicClock() {
        // Make sure we can write to the file.
        assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));

        // No need to persist records for this test, clear any that may be there.
        mAppStartInfoTracker.clearProcessStartInfo(false);

        // Set clock with an arbitrary 5 minute offset, just needs to be longer than it would take
        // for code to run.
        mAppStartInfoTracker.mMonotonicClock = new MonotonicClock(5 * 60 * 1000,
                Clock.SYSTEM_CLOCK);

        // Record the current time.
        long originalMonotonicTime = mAppStartInfoTracker.mMonotonicClock.monotonicTime();

        // Now persist the process start info. Records were cleared above so this should just
        // persist the monotonic time.
        mAppStartInfoTracker.persistProcessStartInfo();

        // Null out the clock to make sure its set on load.
        mAppStartInfoTracker.mMonotonicClock = null;
        assertNull(mAppStartInfoTracker.mMonotonicClock);

        // Now load from disk.
        mAppStartInfoTracker.loadExistingProcessStartInfo();

        // Confirm clock has been set and that its current time is greater than the previous one.
        assertNotNull(mAppStartInfoTracker.mMonotonicClock);
        assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime);
    }

    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
        try {
            Field field = clazz.getDeclaredField(fieldName);