diff options
6 files changed, 194 insertions, 2 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 3d145c01de11..a17f356e1fc2 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4369,4 +4369,7 @@ <bool name="config_pdp_reject_enable_retry">false</bool> <!-- pdp data reject retry delay in ms --> <integer name="config_pdp_reject_retry_delay_ms">-1</integer> + + <!-- Component names of the services which will keep critical code path warm --> + <string-array name="config_keep_warming_services" translatable="false" /> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index bfb6802a2b80..06f357e79a62 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4048,4 +4048,6 @@ <java-symbol type="string" name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" /> <java-symbol type="array" name="config_notificationMsgPkgsAllowedAsConvos" /> + + <java-symbol type="array" name="config_keep_warming_services" /> </resources> diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 135ac9a7846e..7be843f17863 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -19,6 +19,7 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; import android.app.ActivityThread; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -336,6 +337,11 @@ final class ActivityManagerConstants extends ContentObserver { */ public int PENDINGINTENT_WARNING_THRESHOLD = DEFAULT_PENDINGINTENT_WARNING_THRESHOLD; + /** + * Component names of the services which will keep critical code path of the host warm + */ + public final ArraySet<ComponentName> KEEP_WARMING_SERVICES = new ArraySet<ComponentName>(); + private List<String> mDefaultImperceptibleKillExemptPackages; private List<Integer> mDefaultImperceptibleKillExemptProcStates; @@ -442,6 +448,10 @@ final class ActivityManagerConstants extends ContentObserver { .boxed().collect(Collectors.toList()); IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages); IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.addAll(mDefaultImperceptibleKillExemptProcStates); + KEEP_WARMING_SERVICES.addAll(Arrays.stream( + context.getResources().getStringArray( + com.android.internal.R.array.config_keep_warming_services)) + .map(ComponentName::unflattenFromString).collect(Collectors.toSet())); } public void start(ContentResolver resolver) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index da5f48962130..f0343e1d807c 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -76,7 +76,11 @@ import android.app.ApplicationExitInfo; import android.app.usage.UsageEvents; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ServiceInfo; import android.os.Debug; import android.os.Handler; @@ -265,6 +269,43 @@ public final class OomAdjuster { void initSettings() { mCachedAppOptimizer.init(); + if (mService.mConstants.KEEP_WARMING_SERVICES.size() > 0) { + final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); + mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mService) { + handleUserSwitchedLocked(); + } + } + }, filter, null, mService.mHandler); + } + } + + /** + * Update the keep-warming service flags upon user switches + */ + @VisibleForTesting + @GuardedBy("mService") + void handleUserSwitchedLocked() { + final ArraySet<ComponentName> warmServices = mService.mConstants.KEEP_WARMING_SERVICES; + final ArrayList<ProcessRecord> processes = mProcessList.mLruProcesses; + for (int i = processes.size() - 1; i >= 0; i--) { + final ProcessRecord app = processes.get(i); + boolean includeWarmPkg = false; + for (int j = warmServices.size() - 1; j >= 0; j--) { + if (app.pkgList.containsKey(warmServices.valueAt(j).getPackageName())) { + includeWarmPkg = true; + break; + } + } + if (!includeWarmPkg) { + continue; + } + for (int j = app.numberOfRunningServices() - 1; j >= 0; j--) { + app.getRunningServiceAt(j).updateKeepWarmLocked(); + } + } } /** @@ -1470,7 +1511,7 @@ public final class OomAdjuster { "Raise procstate to started service: " + app); } } - if (app.hasShownUi && !app.getCachedIsHomeProcess()) { + if (!s.mKeepWarming && app.hasShownUi && !app.getCachedIsHomeProcess()) { // If this process has shown some UI, let it immediately // go to the LRU list because it may be pretty heavy with // UI stuff. We'll tag it with a label just to help @@ -1479,7 +1520,8 @@ public final class OomAdjuster { app.adjType = "cch-started-ui-services"; } } else { - if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { + if (s.mKeepWarming + || now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { // This service has seen some activity within // recent memory, so we will keep its process ahead // of the background processes. diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 9c96e6e02566..fc17ddedb39f 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -40,6 +40,7 @@ import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoUtils; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.procstats.ServiceState; import com.android.internal.os.BatteryStatsImpl; import com.android.server.LocalServices; @@ -146,6 +147,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN private int lastStartId; // identifier of most recent start request. + boolean mKeepWarming; // Whether or not it'll keep critical code path of the host warm + static class StartItem { final ServiceRecord sr; final boolean taskRemoved; @@ -514,6 +517,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN lastActivity = SystemClock.uptimeMillis(); userId = UserHandle.getUserId(appInfo.uid); createdFromFg = callerIsFg; + updateKeepWarmLocked(); } public ServiceState getTracker() { @@ -732,6 +736,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } } + @GuardedBy("ams") + void updateKeepWarmLocked() { + mKeepWarming = ams.mConstants.KEEP_WARMING_SERVICES.contains(name) + && (ams.mUserController.getCurrentUserId() == userId + || ams.isSingleton(processName, appInfo, instanceName.getClassName(), + serviceInfo.flags)); + } + public AppBindRecord retrieveAppBindingLocked(Intent intent, ProcessRecord app) { Intent.FilterComparison filter = new Intent.FilterComparison(intent); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index fde40aa77a0e..2a267c413c31 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -84,6 +84,7 @@ import android.os.Build; import android.os.IBinder; import android.os.PowerManagerInternal; import android.os.SystemClock; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; @@ -130,6 +131,7 @@ public class MockingOomAdjusterTests { private static final int MOCKAPP5_UID = MOCKAPP_UID + 4; private static final String MOCKAPP5_PROCESSNAME = "test #5"; private static final String MOCKAPP5_PACKAGENAME = "com.android.test.test5"; + private static final int MOCKAPP2_UID_OTHER = MOCKAPP2_UID + UserHandle.PER_USER_RANGE; private static Context sContext; private static PackageManagerInternal sPackageManagerInternal; private static ActivityManagerService sService; @@ -168,6 +170,8 @@ public class MockingOomAdjusterTests { mock(SparseArray.class)); setFieldValue(ActivityManagerService.class, sService, "mOomAdjProfiler", mock(OomAdjProfiler.class)); + setFieldValue(ActivityManagerService.class, sService, "mUserController", + mock(UserController.class)); doReturn(new ActivityManagerService.ProcessChangeItem()).when(sService) .enqueueProcessChangeItemLocked(anyInt(), anyInt()); sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, @@ -1621,6 +1625,117 @@ public class MockingOomAdjusterTests { assertEquals(SERVICE_ADJ, app.setAdj); } + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoAll_Service_KeepWarmingList() { + final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID_OTHER, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + final int userOwner = 0; + final int userOther = 1; + final int cachedAdj1 = CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; + final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; + doReturn(userOwner).when(sService.mUserController).getCurrentUserId(); + + final ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses; + lru.clear(); + lru.add(app2); + lru.add(app); + + final ComponentName cn = ComponentName.unflattenFromString( + MOCKAPP_PACKAGENAME + "/.TestService"); + final ComponentName cn2 = ComponentName.unflattenFromString( + MOCKAPP2_PACKAGENAME + "/.TestService"); + final long now = SystemClock.uptimeMillis(); + + sService.mConstants.KEEP_WARMING_SERVICES.clear(); + final ServiceInfo si = mock(ServiceInfo.class); + si.applicationInfo = mock(ApplicationInfo.class); + ServiceRecord s = spy(new ServiceRecord(sService, null, cn, cn, null, 0, null, + si, false, null)); + doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections(); + s.startRequested = true; + s.lastActivity = now; + + app.setCached(false); + app.startService(s); + app.hasShownUi = true; + + final ServiceInfo si2 = mock(ServiceInfo.class); + si2.applicationInfo = mock(ApplicationInfo.class); + si2.applicationInfo.uid = MOCKAPP2_UID_OTHER; + ServiceRecord s2 = spy(new ServiceRecord(sService, null, cn2, cn2, null, 0, null, + si2, false, null)); + doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s2).getConnections(); + s2.startRequested = true; + s2.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1; + + app2.setCached(false); + app2.startService(s2); + app2.hasShownUi = false; + + sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE; + sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + + assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services"); + assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services"); + + app.setProcState = PROCESS_STATE_NONEXISTENT; + app.adjType = null; + app.setAdj = UNKNOWN_ADJ; + app.hasShownUi = false; + sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + + assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); + + app.setCached(false); + app.setProcState = PROCESS_STATE_NONEXISTENT; + app.adjType = null; + app.setAdj = UNKNOWN_ADJ; + s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1; + sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + + assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); + + app.stopService(s); + app.setProcState = PROCESS_STATE_NONEXISTENT; + app.adjType = null; + app.setAdj = UNKNOWN_ADJ; + app.hasShownUi = true; + sService.mConstants.KEEP_WARMING_SERVICES.add(cn); + sService.mConstants.KEEP_WARMING_SERVICES.add(cn2); + s = spy(new ServiceRecord(sService, null, cn, cn, null, 0, null, + si, false, null)); + doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections(); + s.startRequested = true; + s.lastActivity = now; + + app.startService(s); + sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + + assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); + assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); + + app.setCached(true); + app.setProcState = PROCESS_STATE_NONEXISTENT; + app.adjType = null; + app.setAdj = UNKNOWN_ADJ; + app.hasShownUi = false; + s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1; + sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + + assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); + assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); + + doReturn(userOther).when(sService.mUserController).getCurrentUserId(); + sService.mOomAdjuster.handleUserSwitchedLocked(); + + sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); + assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); + } + private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName, String packageName, boolean hasShownUi) { long now = SystemClock.uptimeMillis(); @@ -1747,4 +1862,12 @@ public class MockingOomAdjusterTests { assertEquals(expectedAdj, app.setAdj); assertEquals(expectedSchedGroup, app.setSchedGroup); } + + private void assertProcStates(ProcessRecord app, boolean expectedCached, + int expectedProcState, int expectedAdj, String expectedAdjType) { + assertEquals(expectedCached, app.isCached()); + assertEquals(expectedProcState, app.setProcState); + assertEquals(expectedAdj, app.setAdj); + assertEquals(expectedAdjType, app.adjType); + } } |