diff options
| author | 2019-04-12 10:54:36 -0700 | |
|---|---|---|
| committer | 2019-04-26 11:51:46 -0700 | |
| commit | 334d6764f79875e2023c7482d6b27a829df34311 (patch) | |
| tree | ec7525220e89e58bd5bcc6dae70bd9e671e68159 | |
| parent | 8cee482fdddc678f14eb9a9dd87ee337a3451492 (diff) | |
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
3 files changed, 270 insertions, 2 deletions
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6da7f5fc98d8..64f94c3717a8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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; diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 8ae7c7d3e0c8..043daefe0fdb 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -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) { diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java new file mode 100644 index 000000000000..d3bcff528155 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java @@ -0,0 +1,255 @@ +/* + * 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()); + } +} |