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

Commit 334d6764 authored by Varun Shah's avatar Varun Shah
Browse files

Update standby bucket for uninteracted foreground services.

This fixes a bug where foreground services that were started, but
never interacted with, were not transitioning to the Active bucket
after 30mins - a defined interval.

Also created unit tests for #maybeUpdateUsageStats().

Test steps: (adb shell commands are generic)
1) Install a test app which has a foreground service
   * can install test app provided in bug via adb install
2) Set the bucket of the test app to Rare:
   $ adb shell am set-stanby-bucket com.packagename rare
3) Launch the foreground service
   $ adb shell am broadcast -a com.packagename.intentfilter -n com.packagename/.MyReceiver
4) Wait for approximately 30mins
5) Observe the app bucket - should be Active (10)
   $ adb shell am get-standby-bucket com.packagename

Bug: 116189835
Test: atest OomAdjusterTests
Test: manual (steps listed above)
Change-Id: I6949466297cccdc349428c5f6172d65bd9d77a53
parent 8cee482f
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -543,7 +543,7 @@ public class ActivityManagerService extends IActivityManager.Stub
    private static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds;
    final OomAdjuster mOomAdjuster;
    OomAdjuster mOomAdjuster;
    final LowMemDetector mLowMemDetector;
    /** All system services */
@@ -1483,7 +1483,7 @@ public class ActivityManagerService extends IActivityManager.Stub
    final ServiceThread mProcStartHandlerThread;
    final Handler mProcStartHandler;
    final ActivityManagerConstants mConstants;
    ActivityManagerConstants mConstants;
    // Encapsulates the global setting "hidden_api_blacklist_exemptions"
    final HiddenApiSettings mHiddenApiBlacklist;
+13 −0
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ProcessStats;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityServiceConnectionsHolder;
@@ -1897,6 +1898,10 @@ public final class OomAdjuster {
            // For apps that sit around for a long time in the interactive state, we need
            // to report this at least once a day so they don't go idle.
            maybeUpdateUsageStatsLocked(app, nowElapsed);
        } else if (!app.reportedInteraction && (nowElapsed - app.getFgInteractionTime())
                > mConstants.SERVICE_USAGE_INTERACTION_TIME) {
            // For foreground services that sit around for a long time but are not interacted with.
            maybeUpdateUsageStatsLocked(app, nowElapsed);
        }

        if (changes != 0) {
@@ -1917,6 +1922,14 @@ public final class OomAdjuster {
        return success;
    }

    // ONLY used for unit testing in OomAdjusterTests.java
    @VisibleForTesting
    void maybeUpdateUsageStats(ProcessRecord app, long nowElapsed) {
        synchronized (mService) {
            maybeUpdateUsageStatsLocked(app, nowElapsed);
        }
    }

    @GuardedBy("mService")
    private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
        if (DEBUG_USAGE_STATS) {
+255 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.am;

import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;

import android.app.ActivityManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;

import com.android.server.LocalServices;
import com.android.server.wm.ActivityTaskManagerService;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Test class for {@link OomAdjuster}.
 *
 * Build/Install/Run:
 *  atest FrameworksServicesTests:OomAdjusterTests
 */
public class OomAdjusterTests {
    private static Context sContext;
    private static ActivityManagerService sService;

    private ProcessRecord mProcessRecord;

    private static final long ZERO = 0L;
    private static final long USAGE_STATS_INTERACTION = 2 * 60 * 60 * 1000L;
    private static final long SERVICE_USAGE_INTERACTION = 30 * 60 * 1000;

    @BeforeClass
    public static void setUpOnce() {
        sContext = getInstrumentation().getTargetContext();

        // We need to run with dexmaker share class loader to make use of
        // ActivityTaskManagerService from wm package.
        runWithDexmakerShareClassLoader(() -> {
            sService = mock(ActivityManagerService.class);
            sService.mActivityTaskManager = new ActivityTaskManagerService(sContext);
            sService.mActivityTaskManager.initialize(null, null, sContext.getMainLooper());
            sService.mAtmInternal = sService.mActivityTaskManager.getAtmInternal();

            sService.mConstants = new ActivityManagerConstants(sContext, sService,
                    sContext.getMainThreadHandler());
            sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, null);
            LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
            LocalServices.addService(UsageStatsManagerInternal.class,
                    mock(UsageStatsManagerInternal.class));
            sService.mUsageStatsService = LocalServices.getService(UsageStatsManagerInternal.class);
        });
    }

    @Before
    public void setUpProcess() {
        // Need to run with dexmaker share class loader to mock package private class.
        runWithDexmakerShareClassLoader(() -> {
            mProcessRecord = spy(new ProcessRecord(sService, sContext.getApplicationInfo(),
                    "name", 12345));
        });

        // Ensure certain services and constants are defined properly
        assertNotNull(sService.mUsageStatsService);
        assertEquals(USAGE_STATS_INTERACTION, sService.mConstants.USAGE_STATS_INTERACTION_INTERVAL);
        assertEquals(SERVICE_USAGE_INTERACTION, sService.mConstants.SERVICE_USAGE_INTERACTION_TIME);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStatePersistentUI() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, elapsedTime);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateTop() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, elapsedTime);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateTop_PreviousInteraction() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
        mProcessRecord.reportedInteraction = true;
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, ZERO);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateTop_PastUsageInterval() {
        final long elapsedTime = 3 * USAGE_STATS_INTERACTION;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
        mProcessRecord.reportedInteraction = true;
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, elapsedTime);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateBoundTop() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_TOP);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, elapsedTime);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateFGS() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(elapsedTime, false, ZERO);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateFGS_ShortInteraction() {
        final long elapsedTime = ZERO;
        final long fgInteractionTime = 1000L;
        mProcessRecord.setFgInteractionTime(fgInteractionTime);
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(fgInteractionTime, false, ZERO);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateFGS_LongInteraction() {
        final long elapsedTime = 2 * SERVICE_USAGE_INTERACTION;
        final long fgInteractionTime = 1000L;
        mProcessRecord.setFgInteractionTime(fgInteractionTime);
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(fgInteractionTime, true, elapsedTime);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateFGS_PreviousLongInteraction() {
        final long elapsedTime = 2 * SERVICE_USAGE_INTERACTION;
        final long fgInteractionTime = 1000L;
        mProcessRecord.setFgInteractionTime(fgInteractionTime);
        mProcessRecord.reportedInteraction = true;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(fgInteractionTime, true, ZERO);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateFGSLocation() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(elapsedTime, false, ZERO);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateBFGS() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, elapsedTime);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateImportantFG() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, elapsedTime);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateImportantFG_PreviousInteraction() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
        mProcessRecord.reportedInteraction = true;
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, ZERO);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateImportantFG_PastUsageInterval() {
        final long elapsedTime = 3 * USAGE_STATS_INTERACTION;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
        mProcessRecord.reportedInteraction = true;
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, true, elapsedTime);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateImportantBG() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, false, ZERO);
    }

    @Test
    public void testMaybeUpdateUsageStats_ProcStateService() {
        final long elapsedTime = ZERO;
        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_SERVICE);
        sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);

        assertProcessRecordState(ZERO, false, ZERO);
    }

    private void assertProcessRecordState(long fgInteractionTime, boolean reportedInteraction,
            long interactionEventTime) {
        assertEquals("Foreground interaction time was not updated correctly.",
                fgInteractionTime, mProcessRecord.getFgInteractionTime());
        assertEquals("Interaction was not updated correctly.",
                reportedInteraction, mProcessRecord.reportedInteraction);
        assertEquals("Interaction event time was not updated correctly.",
                interactionEventTime, mProcessRecord.getInteractionEventTime());
    }
}