diff options
Diffstat (limited to 'services')
35 files changed, 2669 insertions, 686 deletions
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index f19f7f212513..1741593ba671 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -19,12 +19,10 @@ package com.android.server; import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.Sensor; @@ -40,8 +38,6 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.os.VibrationEffect; -import android.os.Vibrator; import android.provider.Settings; import android.util.MutableBoolean; import android.util.Slog; @@ -113,19 +109,6 @@ public class GestureLauncherService extends SystemService { */ private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2; - /** Action for starting emergency alerts on Wear OS. */ - private static final String WEAR_LAUNCH_EMERGENCY_ACTION = - "com.android.systemui.action.LAUNCH_EMERGENCY"; - - /** Action for starting emergency alerts in retail mode on Wear OS. */ - private static final String WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION = - "com.android.systemui.action.LAUNCH_EMERGENCY_RETAIL"; - - /** - * Boolean extra for distinguishing intents coming from power button gesture. - */ - private static final String EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE = "launch_emergency_via_gesture"; - /** The listener that receives the gesture event. */ private final GestureEventListener mGestureListener = new GestureEventListener(); private final CameraLiftTriggerEventListener mCameraLiftTriggerListener = @@ -198,7 +181,6 @@ public class GestureLauncherService extends SystemService { private final UiEventLogger mUiEventLogger; private boolean mHasFeatureWatch; - private long mVibrateMilliSecondsForPanicGesture; @VisibleForTesting public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum { @@ -268,13 +250,6 @@ public class GestureLauncherService extends SystemService { mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); - mVibrateMilliSecondsForPanicGesture = - resources.getInteger( - com.android - .internal - .R - .integer - .config_mashPressVibrateTimeOnPowerButton); } } @@ -714,11 +689,6 @@ public class GestureLauncherService extends SystemService { userSetupComplete)); } - if (mHasFeatureWatch) { - onEmergencyGestureDetectedOnWatch(); - return true; - } - StatusBarManagerInternal service = LocalServices.getService( StatusBarManagerInternal.class); service.onEmergencyActionLaunchGestureDetected(); @@ -728,37 +698,6 @@ public class GestureLauncherService extends SystemService { } } - private void onEmergencyGestureDetectedOnWatch() { - Intent emergencyIntent = - new Intent( - isInRetailMode() - ? WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION - : WEAR_LAUNCH_EMERGENCY_ACTION); - PackageManager pm = mContext.getPackageManager(); - ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0); - if (resolveInfo == null) { - Slog.w(TAG, "Couldn't find an app to process the emergency intent " - + emergencyIntent.getAction()); - return; - } - - Vibrator vibrator = mContext.getSystemService(Vibrator.class); - vibrator.vibrate(VibrationEffect.createOneShot(mVibrateMilliSecondsForPanicGesture, - VibrationEffect.DEFAULT_AMPLITUDE)); - - emergencyIntent.setComponent( - new ComponentName( - resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)); - emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - emergencyIntent.putExtra(EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE, true); - mContext.startActivityAsUser(emergencyIntent, new UserHandle(mUserId)); - } - - private boolean isInRetailMode() { - return Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DEVICE_DEMO_MODE, 0) == 1; - } - private boolean isUserSetupComplete() { return Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index dc83125503a9..383bb2542199 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -6434,6 +6434,7 @@ public final class ActiveServices { } updateServiceConnectionActivitiesLocked(psr); psr.removeAllConnections(); + psr.removeAllSdkSandboxConnections(); psr.mAllowlistManager = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index bef53c7f31ee..faf1900a6609 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -22,6 +22,7 @@ import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; +import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean; import android.annotation.NonNull; import android.app.ActivityThread; @@ -153,6 +154,11 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time"; static final String KEY_USE_MODERN_TRIM = "use_modern_trim"; + /** + * Whether or not to enable the new oom adjuster implementation. + */ + static final String KEY_ENABLE_NEW_OOMADJ = "enable_new_oom_adj"; + private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024; private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true; private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000; @@ -216,6 +222,11 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_USE_MODERN_TRIM = true; /** + * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}. + */ + private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false; + + /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} */ private static final int @@ -1051,6 +1062,9 @@ final class ActivityManagerConstants extends ContentObserver { /** @see #KEY_USE_MODERN_TRIM */ public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM; + /** @see #KEY_ENABLE_NEW_OOMADJ */ + public boolean ENABLE_NEW_OOMADJ = DEFAULT_ENABLE_NEW_OOM_ADJ; + private final OnPropertiesChangedListener mOnDeviceConfigChangedListener = new OnPropertiesChangedListener() { @Override @@ -1308,6 +1322,7 @@ final class ActivityManagerConstants extends ContentObserver { CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES) - rawMaxEmptyProcesses) / 3; + loadNativeBootDeviceConfigConstants(); } public void start(ContentResolver resolver) { @@ -1347,6 +1362,11 @@ final class ActivityManagerConstants extends ContentObserver { DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS)); } + private void loadNativeBootDeviceConfigConstants() { + ENABLE_NEW_OOMADJ = getDeviceConfigBoolean(KEY_ENABLE_NEW_OOMADJ, + DEFAULT_ENABLE_NEW_OOM_ADJ); + } + public void setOverrideMaxCachedProcesses(int value) { mOverrideMaxCachedProcesses = value; updateMaxCachedProcesses(); @@ -1997,6 +2017,13 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_USE_MODERN_TRIM); } + private void updateEnableNewOomAdj() { + ENABLE_NEW_OOMADJ = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_ENABLE_NEW_OOMADJ, + DEFAULT_ENABLE_NEW_OOM_ADJ); + } + private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) { ForegroundServiceTypePolicy.getDefaultPolicy() .updatePermissionEnforcementFlagIfNecessary(name); @@ -2187,6 +2214,9 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME); pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME); + pw.print(" "); pw.print(KEY_ENABLE_NEW_OOMADJ); + pw.print("="); pw.println(ENABLE_NEW_OOMADJ); + pw.println(); if (mOverrideMaxCachedProcesses >= 0) { pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a0fae26fcac1..c1f2f6731e43 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2029,6 +2029,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.makeActive(mSystemThread.getApplicationThread(), mProcessStats); app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM); addPidLocked(app); + mOomAdjuster.onProcessBeginLocked(app); updateLruProcessLocked(app, false, null); updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT); } @@ -2422,7 +2423,9 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.init(this, activeUids, mPlatformCompat); mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null); mPhantomProcessList = new PhantomProcessList(this); - mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids, handlerThread); + mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread) + : new OomAdjuster(this, mProcessList, activeUids, handlerThread); mIntentFirewall = null; mProcessStats = new ProcessStatsService(this, mContext.getCacheDir()); @@ -2483,7 +2486,9 @@ public class ActivityManagerService extends IActivityManager.Stub mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), new LowMemDetector(this)); mPhantomProcessList = new PhantomProcessList(this); - mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); + mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(this, mProcessList, activeUids) + : new OomAdjuster(this, mProcessList, activeUids); // Broadcast policy parameters final BroadcastConstants foreConstants = new BroadcastConstants( @@ -4595,6 +4600,7 @@ public class ActivityManagerService extends IActivityManager.Stub EventLogTags.writeAmProcBound(app.userId, pid, app.processName); synchronized (mProcLock) { + mOomAdjuster.onProcessBeginLocked(app); mOomAdjuster.setAttachingProcessStatesLSP(app); clearProcessForegroundLocked(app); app.setDebugging(false); @@ -6980,6 +6986,7 @@ public class ActivityManagerService extends IActivityManager.Stub sdkSandboxClientAppPackage, new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION, customProcess != null ? customProcess : info.processName)); + mOomAdjuster.onProcessBeginLocked(app); updateLruProcessLocked(app, false, null); updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 8c1fd516028e..2fff79b3fb26 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -373,7 +373,7 @@ public class BroadcastConstants { * Return the {@link SystemProperty} name for the given key in our * {@link DeviceConfig} namespace. */ - private @NonNull String propertyFor(@NonNull String key) { + private static @NonNull String propertyFor(@NonNull String key) { return "persist.device_config." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key; } @@ -382,11 +382,11 @@ public class BroadcastConstants { * {@link DeviceConfig} namespace, but with a different prefix that can be * used to locally override the {@link DeviceConfig} value. */ - private @NonNull String propertyOverrideFor(@NonNull String key) { + private static @NonNull String propertyOverrideFor(@NonNull String key) { return "persist.sys." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key; } - private boolean getDeviceConfigBoolean(@NonNull String key, boolean def) { + static boolean getDeviceConfigBoolean(@NonNull String key, boolean def) { return SystemProperties.getBoolean(propertyOverrideFor(key), SystemProperties.getBoolean(propertyFor(key), def)); } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 459c6ff3504a..1f9e89e30782 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -41,6 +41,7 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; @@ -124,6 +125,7 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal.OomAdjReason; @@ -369,20 +371,21 @@ public class OomAdjuster { */ private final Handler mProcessGroupHandler; - private final int[] mTmpSchedGroup = new int[1]; + protected final int[] mTmpSchedGroup = new int[1]; - private final ActivityManagerService mService; - private final ProcessList mProcessList; - private final ActivityManagerGlobalLock mProcLock; + final ActivityManagerService mService; + final ProcessList mProcessList; + final ActivityManagerGlobalLock mProcLock; private final int mNumSlots; - private final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>(); - private final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>(); - private final ActiveUids mTmpUidRecords; - private final ArrayDeque<ProcessRecord> mTmpQueue; - private final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>(); - private final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>(); - private final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>(); + protected final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>(); + protected final ArrayList<ProcessRecord> mTmpProcessList2 = new ArrayList<ProcessRecord>(); + protected final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>(); + protected final ActiveUids mTmpUidRecords; + protected final ArrayDeque<ProcessRecord> mTmpQueue; + protected final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>(); + protected final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>(); + protected final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>(); /** * Flag to mark if there is an ongoing oomAdjUpdate: potentially the oomAdjUpdate @@ -412,7 +415,7 @@ public class OomAdjuster { this(service, processList, activeUids, createAdjusterThread()); } - private static ServiceThread createAdjusterThread() { + static ServiceThread createAdjusterThread() { // The process group is usually critical to the response time of foreground app, so the // setter should apply it as soon as possible. final ServiceThread adjusterThread = @@ -532,7 +535,7 @@ public class OomAdjuster { mPendingProcessSet.remove(app); mProcessesInCycle.clear(); - computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true); + computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true, oomAdjReason, true); if (!mProcessesInCycle.isEmpty()) { // We can't use the score here if there is a cycle, abort. for (int i = mProcessesInCycle.size() - 1; i >= 0; i--) { @@ -550,7 +553,7 @@ public class OomAdjuster { && (uidRec.getSetProcState() != uidRec.getCurProcState() || uidRec.getSetCapability() != uidRec.getCurCapability() || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) { - ActiveUids uids = mTmpUidRecords; + final ActiveUids uids = mTmpUidRecords; uids.clear(); uids.put(uidRec.getUid(), uidRec); updateUidsLSP(uids, SystemClock.elapsedRealtime()); @@ -633,19 +636,20 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { + protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { final ProcessRecord topApp = mService.getTopApp(); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); mService.mOomAdjProfiler.oomAdjStarted(); mAdjSeq++; - // Firstly, try to see if the importance of itself gets changed final ProcessStateRecord state = app.mState; final boolean wasCached = state.isCached(); final int oldAdj = state.getCurRawAdj(); final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ ? oldAdj : UNKNOWN_ADJ; + + // Firstly, try to see if the importance of itself gets changed final boolean wasBackground = ActivityManager.isProcStateBackground( state.getSetProcState()); final int oldCap = state.getSetCapability(); @@ -693,8 +697,6 @@ public class OomAdjuster { mPendingProcessSet.clear(); if (!containsCycle) { - // Reset the flag - state.setReachable(false); // Remove this app from the return list because we've done the computation on it. processes.remove(app); } @@ -718,8 +720,13 @@ public class OomAdjuster { return true; } + /** + * Collect the reachable processes from the given {@code apps}, the result will be + * returned in the given {@code processes}, which will include the processes from + * the given {@code apps}. + */ @GuardedBy("mService") - private boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps, + protected boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps, ArrayList<ProcessRecord> processes, ActiveUids uids) { final ArrayDeque<ProcessRecord> queue = mTmpQueue; queue.clear(); @@ -824,11 +831,15 @@ public class OomAdjuster { if (size > 0) { // Reverse the process list, since the updateOomAdjInnerLSP scans from the end of it. for (int l = 0, r = size - 1; l < r; l++, r--) { - ProcessRecord t = processes.get(l); - processes.set(l, processes.get(r)); + final ProcessRecord t = processes.get(l); + final ProcessRecord u = processes.get(r); + t.mState.setReachable(false); + u.mState.setReachable(false); + processes.set(l, u); processes.set(r, t); } } + return containsCycle; } @@ -928,24 +939,18 @@ public class OomAdjuster { * Update OomAdj for all processes within the given list (could be partial), or the whole LRU * list if the given list is null; when it's partial update, each process's client proc won't * get evaluated recursively here. + * + * <p>Note: If the given {@code processes} is not null, the expectation to it is, the caller + * must have called {@link collectReachableProcessesLocked} on it. */ @GuardedBy({"mService", "mProcLock"}) - private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles, boolean startProfiling) { - if (startProfiling) { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); - mService.mOomAdjProfiler.oomAdjStarted(); - } - final long now = SystemClock.uptimeMillis(); - final long nowElapsed = SystemClock.elapsedRealtime(); - final long oldTime = now - mConstants.mMaxEmptyTimeMillis; final boolean fullUpdate = processes == null; + final ArrayList<ProcessRecord> activeProcesses = fullUpdate + ? mProcessList.getLruProcessesLOSP() : processes; ActiveUids activeUids = uids; - ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.getLruProcessesLOSP() - : processes; - final int numProc = activeProcesses.size(); - if (activeUids == null) { final int numUids = mActiveUids.size(); activeUids = mTmpUidRecords; @@ -956,14 +961,14 @@ public class OomAdjuster { } } - // Reset state in all uid records. - for (int i = activeUids.size() - 1; i >= 0; i--) { - final UidRecord uidRec = activeUids.valueAt(i); - if (DEBUG_UID_OBSERVERS) { - Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); - } - uidRec.reset(); + if (startProfiling) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); } + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final long oldTime = now - mConstants.mMaxEmptyTimeMillis; + final int numProc = activeProcesses.size(); mAdjSeq++; if (fullUpdate) { @@ -971,6 +976,9 @@ public class OomAdjuster { mNewNumAServiceProcs = 0; } + // Reset state in all uid records. + resetUidRecordsLsp(activeUids); + boolean retryCycles = false; boolean computeClients = fullUpdate || potentialCycles; @@ -996,8 +1004,9 @@ public class OomAdjuster { if (!app.isKilledByAm() && app.getThread() != null) { state.setProcStateChanged(false); app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason); + // It won't enter cycle if not computing clients. computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, fullUpdate, now, false, - computeClients); // It won't enter cycle if not computing clients. + computeClients, oomAdjReason, true); // if any app encountered a cycle, we need to perform an additional loop later retryCycles |= state.containsCycle(); // Keep the completedAdjSeq to up to date. @@ -1034,7 +1043,7 @@ public class OomAdjuster { final ProcessStateRecord state = app.mState; if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) { if (computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, - true, true)) { + true, true, oomAdjReason, true)) { retryCycles = true; } } @@ -1045,10 +1054,33 @@ public class OomAdjuster { assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); + + if (startProfiling) { + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + } + + @GuardedBy({"mService", "mProcLock"}) + private void resetUidRecordsLsp(@NonNull ActiveUids activeUids) { + // Reset state in all uid records. + for (int i = activeUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = activeUids.valueAt(i); + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + + @GuardedBy({"mService", "mProcLock"}) + protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids, + long now, long nowElapsed, long oldTime) { mNumNonCachedProcs = 0; mNumCachedHiddenProcs = 0; - boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids, + final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids, oomAdjReason); mNumServiceProcs = mNewNumServiceProcs; @@ -1085,14 +1117,10 @@ public class OomAdjuster { Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms"); } } - if (startProfiling) { - mService.mOomAdjProfiler.oomAdjEnded(); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } } @GuardedBy({"mService", "mProcLock"}) - private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) { + protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) { final int numLru = lruList.size(); if (mConstants.USE_TIERED_CACHED_ADJ) { final long now = SystemClock.uptimeMillis(); @@ -1413,7 +1441,7 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) { + protected void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) { if (!app.isKilledByAm() && app.getThread() != null) { if (app.isolated && app.mServices.numberOfRunningServices() <= 0 && app.getIsolatedEntryPoint() == null) { @@ -1442,7 +1470,7 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) { + protected void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) { // This compares previously set procstate to the current procstate in regards to whether // or not the app's network access will be blocked. So, this needs to be called before // we update the UidRecord's procstate by calling {@link UidRecord#setSetProcState}. @@ -1580,7 +1608,7 @@ public class OomAdjuster { return true; } - private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = + protected final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = new ComputeOomAdjWindowCallback(); /** These methods are called inline during computeOomAdjLSP(), on the same thread */ @@ -1719,24 +1747,30 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj, + protected boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj, ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval, - boolean computeClients) { + boolean computeClients, int oomAdjReason, boolean couldRecurse) { final ProcessStateRecord state = app.mState; - if (mAdjSeq == state.getAdjSeq()) { - if (state.getAdjSeq() == state.getCompletedAdjSeq()) { - // This adjustment has already been computed successfully. - return false; - } else { - // The process is being computed, so there is a cycle. We cannot - // rely on this process's state. - state.setContainsCycle(true); - mProcessesInCycle.add(app); + if (couldRecurse) { + if (mAdjSeq == state.getAdjSeq()) { + if (state.getAdjSeq() == state.getCompletedAdjSeq()) { + // This adjustment has already been computed successfully. + return false; + } else { + // The process is being computed, so there is a cycle. We cannot + // rely on this process's state. + state.setContainsCycle(true); + mProcessesInCycle.add(app); - return false; + return false; + } } } + int prevAppAdj = getInitialAdj(app); + int prevProcState = getInitialProcState(app); + int prevCapability = getInitialCapability(app); + if (app.getThread() == null) { state.setAdjSeq(mAdjSeq); state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND); @@ -1745,6 +1779,8 @@ public class OomAdjuster { state.setCurRawAdj(CACHED_APP_MAX_ADJ); state.setCompletedAdjSeq(state.getAdjSeq()); state.setCurCapability(PROCESS_CAPABILITY_NONE); + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAppAdj); return false; } @@ -1753,7 +1789,7 @@ public class OomAdjuster { state.setAdjTarget(null); state.setEmpty(false); state.setCached(false); - if (!cycleReEval) { + if (!couldRecurse || !cycleReEval) { // Don't reset this flag when doing cycles re-evaluation. state.setNoKillOnBgRestrictedAndIdle(false); // If this UID is currently allowlisted, it should not be frozen. @@ -1764,9 +1800,6 @@ public class OomAdjuster { final int appUid = app.info.uid; final int logUid = mService.mCurOomAdjUid; - int prevAppAdj = state.getCurAdj(); - int prevProcState = state.getCurProcState(); - int prevCapability = state.getCurCapability(); final ProcessServiceRecord psr = app.mServices; if (state.getMaxAdj() <= FOREGROUND_APP_ADJ) { @@ -1812,6 +1845,8 @@ public class OomAdjuster { state.setCurRawProcState(state.getCurProcState()); state.setCurAdj(state.getMaxAdj()); state.setCompletedAdjSeq(state.getAdjSeq()); + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAppAdj); // if curAdj is less than prevAppAdj, then this process was promoted return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState; } @@ -1825,7 +1860,7 @@ public class OomAdjuster { int adj; int schedGroup; int procState; - int capability = cycleReEval ? app.mState.getCurCapability() : 0; + int capability = cycleReEval ? getInitialCapability(app) : 0; boolean foregroundActivities = false; boolean hasVisibleActivities = false; @@ -1904,7 +1939,7 @@ public class OomAdjuster { // value that the caller wants us to. adj = cachedAdj; procState = PROCESS_STATE_CACHED_EMPTY; - if (!state.containsCycle()) { + if (!couldRecurse || !state.containsCycle()) { state.setCached(true); state.setEmpty(true); state.setAdjType("cch-empty"); @@ -2169,8 +2204,10 @@ public class OomAdjuster { } } - boolean boundByNonBgRestricted = state.isCurBoundByNonBgRestrictedApp(); - boolean scheduleLikeTopApp = false; + state.setCurBoundByNonBgRestrictedApp(getInitialIsCurBoundByNonBgRestrictedApp(app)); + + state.setScheduleLikeTopApp(false); + for (int is = psr.numberOfRunningServices() - 1; is >= 0 && (adj > FOREGROUND_APP_ADJ || schedGroup == SCHED_GROUP_BACKGROUND @@ -2243,6 +2280,18 @@ public class OomAdjuster { } } + if (!couldRecurse) { + // We're entering recursive functions below, if we're told it's not a recursive + // loop, abort here. + continue; + } + + + state.setCurRawAdj(adj); + state.setCurRawProcState(procState); + state.setCurrentSchedulingGroup(schedGroup); + state.setCurCapability(capability); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections(); for (int conni = serviceConnections.size() - 1; conni >= 0 && (adj > FOREGROUND_APP_ADJ @@ -2263,335 +2312,13 @@ public class OomAdjuster { continue; } - boolean trackedProcState = false; - - ProcessRecord client = cr.binding.client; - if (app.isSdkSandbox && cr.binding.attributedClient != null) { - // For SDK sandboxes, use the attributed client (eg the app that - // requested the sandbox) - client = cr.binding.attributedClient; - } - final ProcessStateRecord cstate = client.mState; - if (computeClients) { - computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, - cycleReEval, true); - } else { - cstate.setCurRawAdj(cstate.getCurAdj()); - cstate.setCurRawProcState(cstate.getCurProcState()); - } - - int clientAdj = cstate.getCurRawAdj(); - int clientProcState = cstate.getCurRawProcState(); - - final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP; - - boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp() - || clientProcState <= PROCESS_STATE_BOUND_TOP - || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE - && !cstate.isBackgroundRestricted()); - - if (client.mOptRecord.shouldNotFreeze()) { - // Propagate the shouldNotFreeze flag down the bindings. - app.mOptRecord.setShouldNotFreeze(true); - } - - // We always propagate PROCESS_CAPABILITY_BFSL over bindings here, - // but, right before actually setting it to the process, - // we check the final procstate, and remove it if the procsate is below BFGS. - capability |= getBfslCapabilityFromClient(client); - - if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { - if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { - capability |= cstate.getCurCapability(); - } - - // If an app has network capability by default - // (by having procstate <= BFGS), then the apps it binds to will get - // elevated to a high enough procstate anyway to get network unless they - // request otherwise, so don't propagate the network capability by default - // in this case unless they explicitly request it. - if ((cstate.getCurCapability() - & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) { - if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { - // This is used to grant network access to Expedited Jobs. - if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) { - capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; - } - } else { - capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; - } - } - if ((cstate.getCurCapability() - & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) { - if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) { - // This is used to grant network access to User Initiated Jobs. - if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) { - capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; - } - } - } - - if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - continue; - } + computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll, + cycleReEval, computeClients, oomAdjReason, cachedAdj, true); - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { - // If the other app is cached for any reason, for purposes here - // we are going to consider it empty. The specific cached state - // doesn't propagate except under certain conditions. - clientProcState = PROCESS_STATE_CACHED_EMPTY; - } - String adjType = null; - if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) { - // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen. - if (clientAdj < CACHED_APP_MIN_ADJ) { - app.mOptRecord.setShouldNotFreeze(true); - } - // Not doing bind OOM management, so treat - // this guy more like a started service. - if (state.hasShownUi() && !state.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 - // debug and understand what is going on. - if (adj > clientAdj) { - adjType = "cch-bound-ui-services"; - } - state.setCached(false); - clientAdj = adj; - clientProcState = procState; - } else { - if (now >= (s.lastActivity - + mConstants.MAX_SERVICE_INACTIVITY)) { - // This service has not seen activity within - // recent memory, so allow it to drop to the - // LRU list if there is no other reason to keep - // it around. We'll also tag it with a label just - // to help debug and undertand what is going on. - if (adj > clientAdj) { - adjType = "cch-bound-services"; - } - clientAdj = adj; - } - } - } - if (adj > clientAdj) { - // If this process has recently shown UI, and - // the process that is binding to it is less - // important than being visible, then we don't - // care about the binding as much as we care - // about letting this process get into the LRU - // list to be killed and restarted if needed for - // memory. - if (state.hasShownUi() && !state.getCachedIsHomeProcess() - && clientAdj > PERCEPTIBLE_APP_ADJ) { - if (adj >= CACHED_APP_MIN_ADJ) { - adjType = "cch-bound-ui-services"; - } - } else { - int newAdj; - int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj. - if (cr.hasFlag(Context.BIND_ABOVE_CLIENT - | Context.BIND_IMPORTANT)) { - if (clientAdj >= PERSISTENT_SERVICE_ADJ) { - newAdj = clientAdj; - } else { - // make this service persistent - newAdj = PERSISTENT_SERVICE_ADJ; - schedGroup = SCHED_GROUP_DEFAULT; - procState = ActivityManager.PROCESS_STATE_PERSISTENT; - cr.trackProcState(procState, mAdjSeq); - trackedProcState = true; - } - } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE) - && clientAdj <= PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) { - newAdj = PERCEPTIBLE_LOW_APP_ADJ; - } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) - && cr.notHasFlag(Context.BIND_NOT_FOREGROUND) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { - // This is for user-initiated jobs. - // We use APP_ADJ + 1 here, so we can tell them apart from FGS. - newAdj = PERCEPTIBLE_APP_ADJ + 1; - } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) - && cr.hasFlag(Context.BIND_NOT_FOREGROUND) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) { - // This is for expedited jobs. - // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart - // EJ and short-FGS. - newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2; - } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { - newAdj = PERCEPTIBLE_APP_ADJ; - } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) { - newAdj = clientAdj; - } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) - && clientAdj <= VISIBLE_APP_ADJ - && adj > VISIBLE_APP_ADJ) { - newAdj = VISIBLE_APP_ADJ; - } else { - if (adj > VISIBLE_APP_ADJ) { - // TODO: Is this too limiting for apps bound from TOP? - newAdj = Math.max(clientAdj, lbAdj); - } else { - newAdj = adj; - } - } - if (!cstate.isCached()) { - state.setCached(false); - } - if (adj > newAdj) { - adj = newAdj; - state.setCurRawAdj(adj); - adjType = "service"; - } - } - } - if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND - | Context.BIND_IMPORTANT_BACKGROUND)) { - // This will treat important bound services identically to - // the top app, which may behave differently than generic - // foreground work. - final int curSchedGroup = cstate.getCurrentSchedulingGroup(); - if (curSchedGroup > schedGroup) { - if (cr.hasFlag(Context.BIND_IMPORTANT)) { - schedGroup = curSchedGroup; - } else { - schedGroup = SCHED_GROUP_DEFAULT; - } - } - if (clientProcState < PROCESS_STATE_TOP) { - // Special handling for above-top states (persistent - // processes). These should not bring the current process - // into the top state, since they are not on top. Instead - // give them the best bound state after that. - if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) { - clientProcState = PROCESS_STATE_FOREGROUND_SERVICE; - } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } else if (mService.mWakefulness.get() - == PowerManagerInternal.WAKEFULNESS_AWAKE - && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) - { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } else { - clientProcState = - PROCESS_STATE_IMPORTANT_FOREGROUND; - } - } else if (clientProcState == PROCESS_STATE_TOP) { - // Go at most to BOUND_TOP, unless requested to elevate - // to client's state. - clientProcState = PROCESS_STATE_BOUND_TOP; - final boolean enabled = cstate.getCachedCompatChange( - CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY); - if (enabled) { - if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { - // TOP process passes all capabilities to the service. - capability |= cstate.getCurCapability(); - } else { - // TOP process passes no capability to the service. - } - } else { - // TOP process passes all capabilities to the service. - capability |= cstate.getCurCapability(); - } - } - } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) { - if (clientProcState < - PROCESS_STATE_TRANSIENT_BACKGROUND) { - clientProcState = - PROCESS_STATE_TRANSIENT_BACKGROUND; - } - } else { - if (clientProcState < - PROCESS_STATE_IMPORTANT_BACKGROUND) { - clientProcState = - PROCESS_STATE_IMPORTANT_BACKGROUND; - } - } - - if (schedGroup < SCHED_GROUP_TOP_APP - && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) - && clientIsSystem) { - schedGroup = SCHED_GROUP_TOP_APP; - scheduleLikeTopApp = true; - } - - if (!trackedProcState) { - cr.trackProcState(clientProcState, mAdjSeq); - } - - if (procState > clientProcState) { - procState = clientProcState; - state.setCurRawProcState(procState); - if (adjType == null) { - adjType = "service"; - } - } - if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND - && cr.hasFlag(Context.BIND_SHOWING_UI)) { - app.setPendingUiClean(true); - } - if (adjType != null) { - state.setAdjType(adjType); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE); - state.setAdjSource(client); - state.setAdjSourceProcState(clientProcState); - state.setAdjTarget(s.instanceName); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType - + ": " + app + ", due to " + client - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } - } - } else { // BIND_WAIVE_PRIORITY == true - // BIND_WAIVE_PRIORITY bindings are special when it comes to the - // freezer. Processes bound via WPRI are expected to be running, - // but they are not promoted in the LRU list to keep them out of - // cached. As a result, they can freeze based on oom_adj alone. - // Normally, bindToDeath would fire when a cached app would die - // in the background, but nothing will fire when a running process - // pings a frozen process. Accordingly, any cached app that is - // bound by an unfrozen app via a WPRI binding has to remain - // unfrozen. - if (clientAdj < CACHED_APP_MIN_ADJ) { - app.mOptRecord.setShouldNotFreeze(true); - } - } - if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) { - psr.setTreatLikeActivity(true); - } - final ActivityServiceConnectionsHolder a = cr.activity; - if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) { - if (a != null && adj > FOREGROUND_APP_ADJ - && a.isActivityVisible()) { - adj = FOREGROUND_APP_ADJ; - state.setCurRawAdj(adj); - if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) { - if (cr.hasFlag(Context.BIND_IMPORTANT)) { - schedGroup = SCHED_GROUP_TOP_APP_BOUND; - } else { - schedGroup = SCHED_GROUP_DEFAULT; - } - } - state.setCached(false); - state.setAdjType("service"); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE); - state.setAdjSource(a); - state.setAdjSourceProcState(procState); - state.setAdjTarget(s.instanceName); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise to service w/activity: " + app); - } - } - } + adj = state.getCurRawAdj(); + procState = state.getCurRawProcState(); + schedGroup = state.getCurrentSchedulingGroup(); + capability = state.getCurCapability(); } } } @@ -2603,97 +2330,27 @@ public class OomAdjuster { || procState > PROCESS_STATE_TOP); provi--) { ContentProviderRecord cpr = ppr.getProviderAt(provi); - for (int i = cpr.connections.size() - 1; - i >= 0 && (adj > FOREGROUND_APP_ADJ - || schedGroup == SCHED_GROUP_BACKGROUND - || procState > PROCESS_STATE_TOP); - i--) { - ContentProviderConnection conn = cpr.connections.get(i); - ProcessRecord client = conn.client; - final ProcessStateRecord cstate = client.mState; - if (client == app) { - // Being our own client is not interesting. - continue; - } - if (computeClients) { - computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true); - } else { - cstate.setCurRawAdj(cstate.getCurAdj()); - cstate.setCurRawProcState(cstate.getCurProcState()); - } - - if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - continue; - } - - int clientAdj = cstate.getCurRawAdj(); - int clientProcState = cstate.getCurRawProcState(); - - // We always propagate PROCESS_CAPABILITY_BFSL to providers here, - // but, right before actually setting it to the process, - // we check the final procstate, and remove it if the procsate is below BFGS. - capability |= getBfslCapabilityFromClient(client); - - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { - // If the other app is cached for any reason, for purposes here - // we are going to consider it empty. - clientProcState = PROCESS_STATE_CACHED_EMPTY; - } - if (client.mOptRecord.shouldNotFreeze()) { - // Propagate the shouldNotFreeze flag down the bindings. - app.mOptRecord.setShouldNotFreeze(true); - } - - boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp() - || clientProcState <= PROCESS_STATE_BOUND_TOP - || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE - && !cstate.isBackgroundRestricted()); - - String adjType = null; - if (adj > clientAdj) { - if (state.hasShownUi() && !state.getCachedIsHomeProcess() - && clientAdj > PERCEPTIBLE_APP_ADJ) { - adjType = "cch-ui-provider"; - } else { - adj = Math.max(clientAdj, FOREGROUND_APP_ADJ); - state.setCurRawAdj(adj); - adjType = "provider"; - } - state.setCached(state.isCached() & cstate.isCached()); - } - - if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) { - if (adjType == null) { - adjType = "provider"; - } - if (clientProcState == PROCESS_STATE_TOP) { - clientProcState = PROCESS_STATE_BOUND_TOP; - } else { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } - } + if (couldRecurse) { + // We're entering recursive functions below. + state.setCurRawAdj(adj); + state.setCurRawProcState(procState); + state.setCurrentSchedulingGroup(schedGroup); + state.setCurCapability(capability); + + for (int i = cpr.connections.size() - 1; + i >= 0 && (adj > FOREGROUND_APP_ADJ + || schedGroup == SCHED_GROUP_BACKGROUND + || procState > PROCESS_STATE_TOP); + i--) { + ContentProviderConnection conn = cpr.connections.get(i); + ProcessRecord client = conn.client; + computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll, + cycleReEval, computeClients, oomAdjReason, cachedAdj, true); - conn.trackProcState(clientProcState, mAdjSeq); - if (procState > clientProcState) { - procState = clientProcState; - state.setCurRawProcState(procState); - } - if (cstate.getCurrentSchedulingGroup() > schedGroup) { - schedGroup = SCHED_GROUP_DEFAULT; - } - if (adjType != null) { - state.setAdjType(adjType); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_PROVIDER_IN_USE); - state.setAdjSource(client); - state.setAdjSourceProcState(clientProcState); - state.setAdjTarget(cpr.name); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType - + ": " + app + ", due to " + client - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } + adj = state.getCurRawAdj(); + procState = state.getCurRawProcState(); + schedGroup = state.getCurrentSchedulingGroup(); + capability = state.getCurCapability(); } } // If the provider has external (non-framework) process @@ -2799,7 +2456,7 @@ public class OomAdjuster { // restrictions on screen off if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE - && !scheduleLikeTopApp) { + && !state.shouldScheduleLikeTopApp()) { if (schedGroup > SCHED_GROUP_RESTRICTED) { schedGroup = SCHED_GROUP_RESTRICTED; } @@ -2817,6 +2474,7 @@ public class OomAdjuster { capability &= ~PROCESS_CAPABILITY_BFSL; } + state.setHasForegroundActivities(foregroundActivities); if (app.isPendingFinishAttach()) { // If the app is still starting up. We reset the computations to the @@ -2834,22 +2492,580 @@ public class OomAdjuster { // it when computing the final cached adj later. Note that we don't need to // worry about this for max adj above, since max adj will always be used to // keep it out of the cached vaues. - state.setCurAdj(adj); state.setCurCapability(capability); - state.setCurrentSchedulingGroup(schedGroup); - state.setCurProcState(procState); - state.setCurRawProcState(procState); state.updateLastInvisibleTime(hasVisibleActivities); - state.setHasForegroundActivities(foregroundActivities); state.setCompletedAdjSeq(mAdjSeq); - state.setCurBoundByNonBgRestrictedApp(boundByNonBgRestricted); + + schedGroup = setIntermediateAdjLSP(app, adj, prevAppAdj, schedGroup); + setIntermediateProcStateLSP(app, procState, prevProcState); + setIntermediateSchedGroupLSP(state, schedGroup); // if curAdj or curProcState improved, then this process was promoted return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState || state.getCurCapability() != prevCapability; } - private int getDefaultCapability(ProcessRecord app, int procState) { + /** + * @return The proposed change to the schedGroup. + */ + @GuardedBy({"mService", "mProcLock"}) + protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj, + int schedGroup) { + final ProcessStateRecord state = app.mState; + state.setCurRawAdj(adj); + + adj = app.mServices.modifyRawOomAdj(adj); + if (adj > state.getMaxAdj()) { + adj = state.getMaxAdj(); + if (adj <= PERCEPTIBLE_LOW_APP_ADJ) { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + + state.setCurAdj(adj); + + return schedGroup; + } + + @GuardedBy({"mService", "mProcLock"}) + protected void setIntermediateProcStateLSP(ProcessRecord app, int procState, + int prevProcState) { + final ProcessStateRecord state = app.mState; + state.setCurProcState(procState); + state.setCurRawProcState(procState); + } + + @GuardedBy({"mService", "mProcLock"}) + protected void setIntermediateSchedGroupLSP(ProcessStateRecord state, int schedGroup) { + // Put bound foreground services in a special sched group for additional + // restrictions on screen off + if (state.getCurProcState() >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE + && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE + && !state.shouldScheduleLikeTopApp()) { + if (schedGroup > SCHED_GROUP_RESTRICTED) { + schedGroup = SCHED_GROUP_RESTRICTED; + } + } + + state.setCurrentSchedulingGroup(schedGroup); + } + + @GuardedBy({"mService", "mProcLock"}) + protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app, + ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, + boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj, + boolean couldRecurse) { + if (app.isPendingFinishAttach()) { + // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here. + return; + } + + final ProcessStateRecord state = app.mState; + ProcessStateRecord cstate = client.mState; + + if (couldRecurse) { + if (app.isSdkSandbox && cr.binding.attributedClient != null) { + // For SDK sandboxes, use the attributed client (eg the app that + // requested the sandbox) + client = cr.binding.attributedClient; + cstate = client.mState; + } + if (computeClients) { + computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true, + oomAdjReason, true); + } else { + cstate.setCurRawAdj(cstate.getCurAdj()); + cstate.setCurRawProcState(cstate.getCurProcState()); + } + } + + int clientAdj = cstate.getCurRawAdj(); + int clientProcState = cstate.getCurRawProcState(); + + final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP; + + int adj = state.getCurRawAdj(); + int procState = state.getCurRawProcState(); + int schedGroup = state.getCurrentSchedulingGroup(); + int capability = state.getCurCapability(); + + final int prevRawAdj = adj; + final int prevProcState = procState; + final int prevSchedGroup = schedGroup; + + final int appUid = app.info.uid; + final int logUid = mService.mCurOomAdjUid; + + state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() + || cstate.isCurBoundByNonBgRestrictedApp() + || clientProcState <= PROCESS_STATE_BOUND_TOP + || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE + && !cstate.isBackgroundRestricted())); + + if (client.mOptRecord.shouldNotFreeze()) { + // Propagate the shouldNotFreeze flag down the bindings. + app.mOptRecord.setShouldNotFreeze(true); + } + + boolean trackedProcState = false; + + // We always propagate PROCESS_CAPABILITY_BFSL over bindings here, + // but, right before actually setting it to the process, + // we check the final procstate, and remove it if the procsate is below BFGS. + capability |= getBfslCapabilityFromClient(client); + + if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { + if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { + capability |= cstate.getCurCapability(); + } + + // If an app has network capability by default + // (by having procstate <= BFGS), then the apps it binds to will get + // elevated to a high enough procstate anyway to get network unless they + // request otherwise, so don't propagate the network capability by default + // in this case unless they explicitly request it. + if ((cstate.getCurCapability() + & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) { + if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + // This is used to grant network access to Expedited Jobs. + if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) { + capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; + } + } else { + capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; + } + } + if ((cstate.getCurCapability() + & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) { + if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) { + // This is used to grant network access to User Initiated Jobs. + if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) { + capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; + } + } + } + + if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { + return; + } + + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. The specific cached state + // doesn't propagate except under certain conditions. + clientProcState = PROCESS_STATE_CACHED_EMPTY; + } + String adjType = null; + if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) { + // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen. + if (clientAdj < CACHED_APP_MIN_ADJ) { + app.mOptRecord.setShouldNotFreeze(true); + } + // Not doing bind OOM management, so treat + // this guy more like a started service. + if (state.hasShownUi() && !state.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 + // debug and understand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-ui-services"; + } + state.setCached(false); + clientAdj = adj; + clientProcState = procState; + } else { + if (now >= (cr.binding.service.lastActivity + + mConstants.MAX_SERVICE_INACTIVITY)) { + // This service has not seen activity within + // recent memory, so allow it to drop to the + // LRU list if there is no other reason to keep + // it around. We'll also tag it with a label just + // to help debug and undertand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-services"; + } + clientAdj = adj; + } + } + } + if (adj > clientAdj) { + // If this process has recently shown UI, and + // the process that is binding to it is less + // important than being visible, then we don't + // care about the binding as much as we care + // about letting this process get into the LRU + // list to be killed and restarted if needed for + // memory. + if (state.hasShownUi() && !state.getCachedIsHomeProcess() + && clientAdj > PERCEPTIBLE_APP_ADJ) { + if (adj >= CACHED_APP_MIN_ADJ) { + adjType = "cch-bound-ui-services"; + } + } else { + int newAdj; + int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj. + if (cr.hasFlag(Context.BIND_ABOVE_CLIENT + | Context.BIND_IMPORTANT)) { + if (clientAdj >= PERSISTENT_SERVICE_ADJ) { + newAdj = clientAdj; + } else { + // make this service persistent + newAdj = PERSISTENT_SERVICE_ADJ; + schedGroup = SCHED_GROUP_DEFAULT; + procState = ActivityManager.PROCESS_STATE_PERSISTENT; + cr.trackProcState(procState, mAdjSeq); + trackedProcState = true; + } + } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE) + && clientAdj <= PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) { + newAdj = PERCEPTIBLE_LOW_APP_ADJ; + } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) + && cr.notHasFlag(Context.BIND_NOT_FOREGROUND) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { + // This is for user-initiated jobs. + // We use APP_ADJ + 1 here, so we can tell them apart from FGS. + newAdj = PERCEPTIBLE_APP_ADJ + 1; + } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) + && cr.hasFlag(Context.BIND_NOT_FOREGROUND) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) { + // This is for expedited jobs. + // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart + // EJ and short-FGS. + newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2; + } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { + newAdj = PERCEPTIBLE_APP_ADJ; + } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) { + newAdj = clientAdj; + } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) + && clientAdj <= VISIBLE_APP_ADJ + && adj > VISIBLE_APP_ADJ) { + newAdj = VISIBLE_APP_ADJ; + } else { + if (adj > VISIBLE_APP_ADJ) { + // TODO: Is this too limiting for apps bound from TOP? + newAdj = Math.max(clientAdj, lbAdj); + } else { + newAdj = adj; + } + } + if (!cstate.isCached()) { + state.setCached(false); + } + if (adj > newAdj) { + adj = newAdj; + state.setCurRawAdj(adj); + adjType = "service"; + } + } + } + if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND + | Context.BIND_IMPORTANT_BACKGROUND)) { + // This will treat important bound services identically to + // the top app, which may behave differently than generic + // foreground work. + final int curSchedGroup = cstate.getCurrentSchedulingGroup(); + if (curSchedGroup > schedGroup) { + if (cr.hasFlag(Context.BIND_IMPORTANT)) { + schedGroup = curSchedGroup; + } else { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + if (clientProcState < PROCESS_STATE_TOP) { + // Special handling for above-top states (persistent + // processes). These should not bring the current process + // into the top state, since they are not on top. Instead + // give them the best bound state after that. + if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) { + clientProcState = PROCESS_STATE_FOREGROUND_SERVICE; + } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } else if (mService.mWakefulness.get() + == PowerManagerInternal.WAKEFULNESS_AWAKE + && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } else { + clientProcState = + PROCESS_STATE_IMPORTANT_FOREGROUND; + } + } else if (clientProcState == PROCESS_STATE_TOP) { + // Go at most to BOUND_TOP, unless requested to elevate + // to client's state. + clientProcState = PROCESS_STATE_BOUND_TOP; + final boolean enabled = cstate.getCachedCompatChange( + CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY); + if (enabled) { + if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { + // TOP process passes all capabilities to the service. + capability |= cstate.getCurCapability(); + } else { + // TOP process passes no capability to the service. + } + } else { + // TOP process passes all capabilities to the service. + capability |= cstate.getCurCapability(); + } + } + } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) { + if (clientProcState < PROCESS_STATE_TRANSIENT_BACKGROUND) { + clientProcState = + PROCESS_STATE_TRANSIENT_BACKGROUND; + } + } else { + if (clientProcState < PROCESS_STATE_IMPORTANT_BACKGROUND) { + clientProcState = + PROCESS_STATE_IMPORTANT_BACKGROUND; + } + } + + if (schedGroup < SCHED_GROUP_TOP_APP + && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) + && clientIsSystem) { + schedGroup = SCHED_GROUP_TOP_APP; + state.setScheduleLikeTopApp(true); + } + + if (!trackedProcState) { + cr.trackProcState(clientProcState, mAdjSeq); + } + + if (procState > clientProcState) { + procState = clientProcState; + state.setCurRawProcState(procState); + if (adjType == null) { + adjType = "service"; + } + } + if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND + && cr.hasFlag(Context.BIND_SHOWING_UI)) { + app.setPendingUiClean(true); + } + if (adjType != null) { + state.setAdjType(adjType); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE); + state.setAdjSource(client); + state.setAdjSourceProcState(clientProcState); + state.setAdjTarget(cr.binding.service.instanceName); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + + ": " + app + ", due to " + client + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + } + } else { // BIND_WAIVE_PRIORITY == true + // BIND_WAIVE_PRIORITY bindings are special when it comes to the + // freezer. Processes bound via WPRI are expected to be running, + // but they are not promoted in the LRU list to keep them out of + // cached. As a result, they can freeze based on oom_adj alone. + // Normally, bindToDeath would fire when a cached app would die + // in the background, but nothing will fire when a running process + // pings a frozen process. Accordingly, any cached app that is + // bound by an unfrozen app via a WPRI binding has to remain + // unfrozen. + if (clientAdj < CACHED_APP_MIN_ADJ) { + app.mOptRecord.setShouldNotFreeze(true); + } + } + if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) { + app.mServices.setTreatLikeActivity(true); + if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY + && procState > PROCESS_STATE_CACHED_ACTIVITY) { + // This is a cached process, but somebody wants us to treat it like it has + // an activity, okay! + procState = PROCESS_STATE_CACHED_ACTIVITY; + state.setAdjType("cch-as-act"); + } + } + final ActivityServiceConnectionsHolder a = cr.activity; + if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) { + if (a != null && adj > FOREGROUND_APP_ADJ + && a.isActivityVisible()) { + adj = FOREGROUND_APP_ADJ; + state.setCurRawAdj(adj); + if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) { + if (cr.hasFlag(Context.BIND_IMPORTANT)) { + schedGroup = SCHED_GROUP_TOP_APP_BOUND; + } else { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + state.setCached(false); + state.setAdjType("service"); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE); + state.setAdjSource(a); + state.setAdjSourceProcState(procState); + state.setAdjTarget(cr.binding.service.instanceName); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise to service w/activity: " + app); + } + } + } + + capability |= getDefaultCapability(app, procState); + + // Procstates below BFGS should never have this capability. + if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + capability &= ~PROCESS_CAPABILITY_BFSL; + } + + if (adj < prevRawAdj) { + schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup); + } + if (procState < prevProcState) { + setIntermediateProcStateLSP(app, procState, prevProcState); + } + if (schedGroup > prevSchedGroup) { + setIntermediateSchedGroupLSP(state, schedGroup); + } + state.setCurCapability(capability); + + state.setEmpty(false); + } + + protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app, + ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, + boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj, + boolean couldRecurse) { + if (app.isPendingFinishAttach()) { + // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here. + return; + } + + final ProcessStateRecord state = app.mState; + final ProcessStateRecord cstate = client.mState; + + if (client == app) { + // Being our own client is not interesting. + return; + } + if (couldRecurse) { + if (computeClients) { + computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true, + oomAdjReason, true); + } else if (couldRecurse) { + cstate.setCurRawAdj(cstate.getCurAdj()); + cstate.setCurRawProcState(cstate.getCurProcState()); + } + + if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(), + cycleReEval)) { + return; + } + } + + int clientAdj = cstate.getCurRawAdj(); + int clientProcState = cstate.getCurRawProcState(); + + int adj = state.getCurRawAdj(); + int procState = state.getCurRawProcState(); + int schedGroup = state.getCurrentSchedulingGroup(); + int capability = state.getCurCapability(); + + final int prevRawAdj = adj; + final int prevProcState = procState; + final int prevSchedGroup = schedGroup; + + final int appUid = app.info.uid; + final int logUid = mService.mCurOomAdjUid; + + // We always propagate PROCESS_CAPABILITY_BFSL to providers here, + // but, right before actually setting it to the process, + // we check the final procstate, and remove it if the procsate is below BFGS. + capability |= getBfslCapabilityFromClient(client); + + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. + clientProcState = PROCESS_STATE_CACHED_EMPTY; + } + if (client.mOptRecord.shouldNotFreeze()) { + // Propagate the shouldNotFreeze flag down the bindings. + app.mOptRecord.setShouldNotFreeze(true); + } + + state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() + || cstate.isCurBoundByNonBgRestrictedApp() + || clientProcState <= PROCESS_STATE_BOUND_TOP + || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE + && !cstate.isBackgroundRestricted())); + + String adjType = null; + if (adj > clientAdj) { + if (state.hasShownUi() && !state.getCachedIsHomeProcess() + && clientAdj > PERCEPTIBLE_APP_ADJ) { + adjType = "cch-ui-provider"; + } else { + adj = Math.max(clientAdj, FOREGROUND_APP_ADJ); + state.setCurRawAdj(adj); + adjType = "provider"; + } + state.setCached(state.isCached() & cstate.isCached()); + } + + if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) { + if (adjType == null) { + adjType = "provider"; + } + if (clientProcState == PROCESS_STATE_TOP) { + clientProcState = PROCESS_STATE_BOUND_TOP; + } else { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } + } + + conn.trackProcState(clientProcState, mAdjSeq); + if (procState > clientProcState) { + procState = clientProcState; + state.setCurRawProcState(procState); + } + if (cstate.getCurrentSchedulingGroup() > schedGroup) { + schedGroup = SCHED_GROUP_DEFAULT; + } + if (adjType != null) { + state.setAdjType(adjType); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_PROVIDER_IN_USE); + state.setAdjSource(client); + state.setAdjSourceProcState(clientProcState); + state.setAdjTarget(conn.provider.name); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + + ": " + app + ", due to " + client + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + } + + // Procstates below BFGS should never have this capability. + if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + capability &= ~PROCESS_CAPABILITY_BFSL; + } + + if (adj < prevRawAdj) { + schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup); + } + if (procState < prevProcState) { + setIntermediateProcStateLSP(app, procState, prevProcState); + } + if (schedGroup > prevSchedGroup) { + setIntermediateSchedGroupLSP(state, schedGroup); + } + state.setCurCapability(capability); + + state.setEmpty(false); + } + + protected int getDefaultCapability(ProcessRecord app, int procState) { final int networkCapabilities = NetworkPolicyManager.getDefaultProcessNetworkCapabilities(procState); final int baseCapabilities; @@ -2882,7 +3098,7 @@ public class OomAdjuster { /** * @return the BFSL capability from a client (of a service binding or provider). */ - int getBfslCapabilityFromClient(ProcessRecord client) { + protected int getBfslCapabilityFromClient(ProcessRecord client) { // Procstates above FGS should always have this flag. We shouldn't need this logic, // but let's do it just in case. if (client.mState.getCurProcState() < PROCESS_STATE_FOREGROUND_SERVICE) { @@ -2967,7 +3183,7 @@ public class OomAdjuster { /** Inform the oomadj observer of changes to oomadj. Used by tests. */ @GuardedBy("mService") - private void reportOomAdjMessageLocked(String tag, String msg) { + protected void reportOomAdjMessageLocked(String tag, String msg) { Slog.d(tag, msg); synchronized (mService.mOomAdjObserverLock) { if (mService.mCurOomAdjObserver != null) { @@ -2983,7 +3199,7 @@ public class OomAdjuster { /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */ @GuardedBy({"mService", "mProcLock"}) - private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now, + protected boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now, long nowElapsed, @OomAdjReason int oomAdjReson) { boolean success = true; final ProcessStateRecord state = app.mState; @@ -3272,6 +3488,8 @@ public class OomAdjuster { int initialCapability = PROCESS_CAPABILITY_NONE; boolean initialCached = true; final ProcessStateRecord state = app.mState; + final int prevProcState = PROCESS_STATE_UNKNOWN; + final int prevAdj = UNKNOWN_ADJ; // If the process has been marked as foreground, it is starting as the top app (with // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread. if (state.hasForegroundActivities()) { @@ -3306,6 +3524,9 @@ public class OomAdjuster { state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ); state.setForcingToImportant(null); state.setHasShownUi(false); + + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAdj); } // ONLY used for unit testing in OomAdjusterTests.java @@ -3553,4 +3774,56 @@ public class OomAdjuster { } processes.clear(); } + + @GuardedBy("mService") + void onProcessBeginLocked(@NonNull ProcessRecord app) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @GuardedBy("mService") + void onProcessEndLocked(@NonNull ProcessRecord app) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + /** + * Called when the process state is changed outside of the OomAdjuster. + */ + @GuardedBy("mService") + void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + /** + * Called when the oom adj is changed outside of the OomAdjuster. + */ + @GuardedBy("mService") + void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @VisibleForTesting + void resetInternal() { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @GuardedBy("mService") + protected int getInitialAdj(@NonNull ProcessRecord app) { + return app.mState.getCurAdj(); + } + + @GuardedBy("mService") + protected int getInitialProcState(@NonNull ProcessRecord app) { + return app.mState.getCurProcState(); + } + + @GuardedBy("mService") + protected int getInitialCapability(@NonNull ProcessRecord app) { + return app.mState.getCurCapability(); + } + + @GuardedBy("mService") + protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) { + // The caller will set the initial value in this implementation. + return app.mState.isCurBoundByNonBgRestrictedApp(); + } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md index 16091d1c162d..da5e12ef21fa 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.md +++ b/services/core/java/com/android/server/am/OomAdjuster.md @@ -130,3 +130,28 @@ The Oom Adjuster maintains a global sequence ID `mAdjSeq` to track the current O * Iterate the processes from least important to most important ones. * A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3. +## The Modern Implementation + +As aforementioned, the OomAdjuster makes the computation in a recursive way, while this is inefficient in dealing with the cycles. The overall code complexity should be around **O((1 + num(retries)) * num(procs) * num(binding connections))**. In addition, depending on the ordering of the input, the algorithm may produce different results and sometimes it's wrong. + +The new "Modern Implementation" is based on the rationale that, apps can't promote the service/provider it connects to, to a higher bucket than itself. We are introducing a bucket based, breadth first search algorithm, as illustrated below: + +``` +for all processes in the process list + compute the state of each process, but, excluding its clients + put each process to the corresponding bucket according to the state value +done + +for each bucket, starting from the top most to the bottom most + for each process in the bucket + for each process it binds to + if the state of the bindee process could be elevated because of the binding; then + move the bindee process to the higher bucket + fi + done + done +done +``` + +The overall code complexity should be around **O(num(procs) * num(binding connections))**, which saves the retry time from the existing algorithm. + diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java new file mode 100644 index 000000000000..b852ef56fceb --- /dev/null +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -0,0 +1,1125 @@ +/* + * Copyright (C) 2023 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.app.ActivityManager.PROCESS_STATE_BACKUP; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_RECENT; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; +import static android.app.ActivityManager.PROCESS_STATE_HOME; +import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; +import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; +import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; +import static android.app.ActivityManager.PROCESS_STATE_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING; +import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; + +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; +import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; +import static com.android.server.am.ProcessList.BACKUP_APP_ADJ; +import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; +import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ; +import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ; +import static com.android.server.am.ProcessList.HOME_APP_ADJ; +import static com.android.server.am.ProcessList.NATIVE_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ; +import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ; +import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; +import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; +import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; +import static com.android.server.am.ProcessList.SERVICE_ADJ; +import static com.android.server.am.ProcessList.SERVICE_B_ADJ; +import static com.android.server.am.ProcessList.SYSTEM_ADJ; +import static com.android.server.am.ProcessList.UNKNOWN_ADJ; +import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal.OomAdjReason; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.SystemClock; +import android.os.Trace; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ServiceThread; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Consumer; + +/** + * A modern implementation of the oom adjuster. + */ +public class OomAdjusterModernImpl extends OomAdjuster { + static final String TAG = "OomAdjusterModernImpl"; + + // The ADJ_SLOT_INVALID is NOT an actual slot. + static final int ADJ_SLOT_INVALID = -1; + static final int ADJ_SLOT_NATIVE = 0; + static final int ADJ_SLOT_SYSTEM = 1; + static final int ADJ_SLOT_PERSISTENT_PROC = 2; + static final int ADJ_SLOT_PERSISTENT_SERVICE = 3; + static final int ADJ_SLOT_FOREGROUND_APP = 4; + static final int ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP = 5; + static final int ADJ_SLOT_VISIBLE_APP = 6; + static final int ADJ_SLOT_PERCEPTIBLE_APP = 7; + static final int ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP = 8; + static final int ADJ_SLOT_PERCEPTIBLE_LOW_APP = 9; + static final int ADJ_SLOT_BACKUP_APP = 10; + static final int ADJ_SLOT_HEAVY_WEIGHT_APP = 11; + static final int ADJ_SLOT_SERVICE = 12; + static final int ADJ_SLOT_HOME_APP = 13; + static final int ADJ_SLOT_PREVIOUS_APP = 14; + static final int ADJ_SLOT_SERVICE_B = 15; + static final int ADJ_SLOT_CACHED_APP = 16; + static final int ADJ_SLOT_UNKNOWN = 17; + + @IntDef(prefix = { "ADJ_SLOT_" }, value = { + ADJ_SLOT_INVALID, + ADJ_SLOT_NATIVE, + ADJ_SLOT_SYSTEM, + ADJ_SLOT_PERSISTENT_PROC, + ADJ_SLOT_PERSISTENT_SERVICE, + ADJ_SLOT_FOREGROUND_APP, + ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP, + ADJ_SLOT_VISIBLE_APP, + ADJ_SLOT_PERCEPTIBLE_APP, + ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP, + ADJ_SLOT_PERCEPTIBLE_LOW_APP, + ADJ_SLOT_BACKUP_APP, + ADJ_SLOT_HEAVY_WEIGHT_APP, + ADJ_SLOT_SERVICE, + ADJ_SLOT_HOME_APP, + ADJ_SLOT_PREVIOUS_APP, + ADJ_SLOT_SERVICE_B, + ADJ_SLOT_CACHED_APP, + ADJ_SLOT_UNKNOWN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AdjSlot{} + + static final int[] ADJ_SLOT_VALUES = new int[] { + NATIVE_ADJ, + SYSTEM_ADJ, + PERSISTENT_PROC_ADJ, + PERSISTENT_SERVICE_ADJ, + FOREGROUND_APP_ADJ, + PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, + VISIBLE_APP_ADJ, + PERCEPTIBLE_APP_ADJ, + PERCEPTIBLE_MEDIUM_APP_ADJ, + PERCEPTIBLE_LOW_APP_ADJ, + BACKUP_APP_ADJ, + HEAVY_WEIGHT_APP_ADJ, + SERVICE_ADJ, + HOME_APP_ADJ, + PREVIOUS_APP_ADJ, + SERVICE_B_ADJ, + CACHED_APP_MIN_ADJ, + UNKNOWN_ADJ, + }; + + /** + * Note: Always use the raw adj to call this API. + */ + static @AdjSlot int adjToSlot(int adj) { + if (adj >= ADJ_SLOT_VALUES[0] && adj <= ADJ_SLOT_VALUES[ADJ_SLOT_VALUES.length - 1]) { + // Conduct a binary search, in most of the cases it'll get a hit. + final int index = Arrays.binarySearch(ADJ_SLOT_VALUES, adj); + if (index >= 0) { + return index; + } + // If not found, the returned index above should be (-(insertion point) - 1), + // let's return the first slot that's less than the adj value. + return -(index + 1) - 1; + } + return ADJ_SLOT_VALUES.length - 1; + } + + static final int[] PROC_STATE_SLOTS = new int[] { + PROCESS_STATE_PERSISTENT, // 0 + PROCESS_STATE_PERSISTENT_UI, + PROCESS_STATE_TOP, + PROCESS_STATE_BOUND_TOP, + PROCESS_STATE_FOREGROUND_SERVICE, + PROCESS_STATE_BOUND_FOREGROUND_SERVICE, + PROCESS_STATE_IMPORTANT_FOREGROUND, + PROCESS_STATE_IMPORTANT_BACKGROUND, + PROCESS_STATE_TRANSIENT_BACKGROUND, + PROCESS_STATE_BACKUP, + PROCESS_STATE_SERVICE, + PROCESS_STATE_RECEIVER, + PROCESS_STATE_TOP_SLEEPING, + PROCESS_STATE_HEAVY_WEIGHT, + PROCESS_STATE_HOME, + PROCESS_STATE_LAST_ACTIVITY, + PROCESS_STATE_CACHED_ACTIVITY, + PROCESS_STATE_CACHED_ACTIVITY_CLIENT, + PROCESS_STATE_CACHED_RECENT, + PROCESS_STATE_CACHED_EMPTY, + PROCESS_STATE_UNKNOWN, // -1 + }; + + static int processStateToSlot(@ActivityManager.ProcessState int state) { + if (state >= PROCESS_STATE_PERSISTENT && state <= PROCESS_STATE_CACHED_EMPTY) { + return state; + } + return PROC_STATE_SLOTS.length - 1; + } + + /** + * A container node in the {@link LinkedProcessRecordList}, + * holding the references to {@link ProcessRecord}. + */ + static class ProcessRecordNode { + static final int NODE_TYPE_PROC_STATE = 0; + static final int NODE_TYPE_ADJ = 1; + + @IntDef(prefix = { "NODE_TYPE_" }, value = { + NODE_TYPE_PROC_STATE, + NODE_TYPE_ADJ, + }) + @Retention(RetentionPolicy.SOURCE) + @interface NodeType {} + + static final int NUM_NODE_TYPE = NODE_TYPE_ADJ + 1; + + @Nullable ProcessRecordNode mPrev; + @Nullable ProcessRecordNode mNext; + final @Nullable ProcessRecord mApp; + + ProcessRecordNode(@Nullable ProcessRecord app) { + mApp = app; + } + + void unlink() { + if (mPrev != null) { + mPrev.mNext = mNext; + } + if (mNext != null) { + mNext.mPrev = mPrev; + } + mPrev = mNext = null; + } + + boolean isLinked() { + return mPrev != null && mNext != null; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("ProcessRecordNode{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + sb.append(mApp); + sb.append(' '); + sb.append(mApp != null ? mApp.mState.getCurProcState() : PROCESS_STATE_UNKNOWN); + sb.append(' '); + sb.append(mApp != null ? mApp.mState.getCurAdj() : UNKNOWN_ADJ); + sb.append(' '); + sb.append(Integer.toHexString(System.identityHashCode(mPrev))); + sb.append(' '); + sb.append(Integer.toHexString(System.identityHashCode(mNext))); + sb.append('}'); + return sb.toString(); + } + } + + private class ProcessRecordNodes { + private final @ProcessRecordNode.NodeType int mType; + + private final LinkedProcessRecordList[] mProcessRecordNodes; + // The last node besides the tail. + private final ProcessRecordNode[] mLastNode; + + ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) { + mType = type; + mProcessRecordNodes = new LinkedProcessRecordList[size]; + for (int i = 0; i < size; i++) { + mProcessRecordNodes[i] = new LinkedProcessRecordList(type); + } + mLastNode = new ProcessRecordNode[size]; + } + + int size() { + return mProcessRecordNodes.length; + } + + @VisibleForTesting + void reset() { + for (int i = 0; i < mProcessRecordNodes.length; i++) { + mProcessRecordNodes[i].reset(); + mLastNode[i] = null; + } + } + + void resetLastNodes() { + for (int i = 0; i < mProcessRecordNodes.length; i++) { + mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail(); + } + } + + void setLastNodeToHead(int slot) { + mLastNode[slot] = mProcessRecordNodes[slot].HEAD; + } + + void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) { + ProcessRecordNode node = mLastNode[slot].mNext; + final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL; + while (node != tail) { + mTmpOomAdjusterArgs.mApp = node.mApp; + // Save the next before calling callback, since that may change the node.mNext. + final ProcessRecordNode next = node.mNext; + callback.accept(mTmpOomAdjusterArgs); + // There are couple of cases: + // a) The current node is moved to another slot + // - for this case, we'd need to keep using the "next" node. + // b) There are one or more new nodes being appended to this slot + // - for this case, we'd need to make sure we scan the new node too. + // Based on the assumption that case a) is only possible with + // the computeInitialOomAdjLSP(), where the movings are for single node only, + // we may safely assume that, if the "next" used to be the "tail" here, and it's + // now a new tail somewhere else, that's case a); otherwise, it's case b); + node = next == tail && node.mNext != null && node.mNext.mNext != null + ? node.mNext : next; + } + } + + int getNumberOfSlots() { + return mProcessRecordNodes.length; + } + + void moveAppTo(@NonNull ProcessRecord app, int prevSlot, int newSlot) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + if (prevSlot != ADJ_SLOT_INVALID) { + if (mLastNode[prevSlot] == node) { + mLastNode[prevSlot] = node.mPrev; + } + node.unlink(); + } + mProcessRecordNodes[newSlot].append(node); + } + + void moveAllNodesTo(int fromSlot, int toSlot) { + final LinkedProcessRecordList fromList = mProcessRecordNodes[fromSlot]; + final LinkedProcessRecordList toList = mProcessRecordNodes[toSlot]; + if (fromSlot != toSlot && fromList.HEAD.mNext != fromList.TAIL) { + fromList.moveTo(toList); + mLastNode[fromSlot] = fromList.getLastNodeBeforeTail(); + } + } + + void moveAppToTail(ProcessRecord app) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + int slot; + switch (mType) { + case ProcessRecordNode.NODE_TYPE_PROC_STATE: + slot = processStateToSlot(app.mState.getCurProcState()); + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + mProcessRecordNodes[slot].moveNodeToTail(node); + break; + case ProcessRecordNode.NODE_TYPE_ADJ: + slot = adjToSlot(app.mState.getCurRawAdj()); + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + mProcessRecordNodes[slot].moveNodeToTail(node); + break; + default: + return; + } + + } + + void reset(int slot) { + mProcessRecordNodes[slot].reset(); + } + + void unlink(@NonNull ProcessRecord app) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + final int slot = getCurrentSlot(app); + if (slot != ADJ_SLOT_INVALID) { + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + } + node.unlink(); + } + + void append(@NonNull ProcessRecord app) { + append(app, getCurrentSlot(app)); + } + + void append(@NonNull ProcessRecord app, int targetSlot) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + mProcessRecordNodes[targetSlot].append(node); + } + + private int getCurrentSlot(@NonNull ProcessRecord app) { + switch (mType) { + case ProcessRecordNode.NODE_TYPE_PROC_STATE: + return processStateToSlot(app.mState.getCurProcState()); + case ProcessRecordNode.NODE_TYPE_ADJ: + return adjToSlot(app.mState.getCurRawAdj()); + } + return ADJ_SLOT_INVALID; + } + + String toString(int slot, int logUid) { + return "lastNode=" + mLastNode[slot] + " " + mProcessRecordNodes[slot].toString(logUid); + } + + /** + * A simple version of {@link java.util.LinkedList}, as here we don't allocate new node + * while adding an object to it. + */ + private static class LinkedProcessRecordList { + // Sentinel head/tail, to make bookkeeping work easier. + final ProcessRecordNode HEAD = new ProcessRecordNode(null); + final ProcessRecordNode TAIL = new ProcessRecordNode(null); + final @ProcessRecordNode.NodeType int mNodeType; + + LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) { + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + mNodeType = nodeType; + } + + void append(@NonNull ProcessRecordNode node) { + node.mNext = TAIL; + node.mPrev = TAIL.mPrev; + TAIL.mPrev.mNext = node; + TAIL.mPrev = node; + } + + void moveTo(@NonNull LinkedProcessRecordList toList) { + if (HEAD.mNext != TAIL) { + toList.TAIL.mPrev.mNext = HEAD.mNext; + HEAD.mNext.mPrev = toList.TAIL.mPrev; + toList.TAIL.mPrev = TAIL.mPrev; + TAIL.mPrev.mNext = toList.TAIL; + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + } + } + + void moveNodeToTail(@NonNull ProcessRecordNode node) { + node.unlink(); + append(node); + } + + @NonNull ProcessRecordNode getLastNodeBeforeTail() { + return TAIL.mPrev; + } + + @VisibleForTesting + void reset() { + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + } + + String toString(int logUid) { + final StringBuilder sb = new StringBuilder(); + sb.append("LinkedProcessRecordList{"); + sb.append(HEAD); + sb.append(' '); + sb.append(TAIL); + sb.append('['); + ProcessRecordNode node = HEAD.mNext; + while (node != TAIL) { + if (node.mApp != null && node.mApp.uid == logUid) { + sb.append(node); + sb.append(','); + } + node = node.mNext; + } + sb.append(']'); + sb.append('}'); + return sb.toString(); + } + } + } + + /** + * A data class for holding the parameters in computing oom adj. + */ + private class OomAdjusterArgs { + ProcessRecord mApp; + ProcessRecord mTopApp; + long mNow; + int mCachedAdj; + @OomAdjReason int mOomAdjReason; + @NonNull ActiveUids mUids; + boolean mFullUpdate; + + void update(ProcessRecord topApp, long now, int cachedAdj, + @OomAdjReason int oomAdjReason, @NonNull ActiveUids uids, boolean fullUpdate) { + mTopApp = topApp; + mNow = now; + mCachedAdj = cachedAdj; + mOomAdjReason = oomAdjReason; + mUids = uids; + mFullUpdate = fullUpdate; + } + } + + OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, + ActiveUids activeUids) { + this(service, processList, activeUids, createAdjusterThread()); + } + + OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, + ActiveUids activeUids, ServiceThread adjusterThread) { + super(service, processList, activeUids, adjusterThread); + } + + private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes( + ProcessRecordNode.NODE_TYPE_PROC_STATE, PROC_STATE_SLOTS.length); + private final ProcessRecordNodes mProcessRecordAdjNodes = new ProcessRecordNodes( + ProcessRecordNode.NODE_TYPE_ADJ, ADJ_SLOT_VALUES.length); + private final OomAdjusterArgs mTmpOomAdjusterArgs = new OomAdjusterArgs(); + + void linkProcessRecordToList(@NonNull ProcessRecord app) { + mProcessRecordProcStateNodes.append(app); + mProcessRecordAdjNodes.append(app); + } + + void unlinkProcessRecordFromList(@NonNull ProcessRecord app) { + mProcessRecordProcStateNodes.unlink(app); + mProcessRecordAdjNodes.unlink(app); + } + + @Override + @VisibleForTesting + void resetInternal() { + mProcessRecordProcStateNodes.reset(); + mProcessRecordAdjNodes.reset(); + } + + @GuardedBy("mService") + @Override + void onProcessBeginLocked(@NonNull ProcessRecord app) { + // Check one type should be good enough. + if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] == null) { + for (int i = 0; i < app.mLinkedNodes.length; i++) { + app.mLinkedNodes[i] = new ProcessRecordNode(app); + } + } + if (!app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) { + linkProcessRecordToList(app); + } + } + + @GuardedBy("mService") + @Override + void onProcessEndLocked(@NonNull ProcessRecord app) { + if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] != null + && app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) { + unlinkProcessRecordFromList(app); + } + } + + @GuardedBy("mService") + @Override + void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) { + updateProcStateSlotIfNecessary(app, prevProcState); + } + + @GuardedBy("mService") + void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) { + updateAdjSlotIfNecessary(app, prevAdj); + } + + @GuardedBy("mService") + @Override + protected int getInitialAdj(@NonNull ProcessRecord app) { + return UNKNOWN_ADJ; + } + + @GuardedBy("mService") + @Override + protected int getInitialProcState(@NonNull ProcessRecord app) { + return PROCESS_STATE_UNKNOWN; + } + + @GuardedBy("mService") + @Override + protected int getInitialCapability(@NonNull ProcessRecord app) { + return 0; + } + + @GuardedBy("mService") + @Override + protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) { + return false; + } + + private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) { + if (app.mState.getCurRawAdj() != prevRawAdj) { + final int slot = adjToSlot(app.mState.getCurRawAdj()); + final int prevSlot = adjToSlot(prevRawAdj); + if (slot != prevSlot && slot != ADJ_SLOT_INVALID) { + mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot); + } + } + } + + private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) { + if (app.mState.getCurProcState() != prevProcState) { + final int slot = processStateToSlot(app.mState.getCurProcState()); + final int prevSlot = processStateToSlot(prevProcState); + if (slot != prevSlot) { + mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot); + } + } + } + + @Override + protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { + final ProcessRecord topApp = mService.getTopApp(); + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); + mAdjSeq++; + + final ProcessStateRecord state = app.mState; + final int oldAdj = state.getCurRawAdj(); + final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ + ? oldAdj : UNKNOWN_ADJ; + + final ActiveUids uids = mTmpUidRecords; + final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet; + final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList; + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + + uids.clear(); + targetProcesses.clear(); + targetProcesses.add(app); + reachableProcesses.clear(); + + // Find out all reachable processes from this app. + collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids); + + // Copy all of the reachable processes into the target process set. + targetProcesses.addAll(reachableProcesses); + reachableProcesses.clear(); + + final boolean result = performNewUpdateOomAdjLSP(oomAdjReason, + topApp, targetProcesses, uids, false, now, cachedAdj); + + reachableProcesses.addAll(targetProcesses); + assignCachedAdjIfNecessary(reachableProcesses); + for (int i = uids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = uids.valueAt(i); + uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP); + } + updateUidsLSP(uids, nowElapsed); + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason); + } + targetProcesses.clear(); + reachableProcesses.clear(); + + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + return result; + } + + @GuardedBy({"mService", "mProcLock"}) + @Override + protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles, + boolean startProfiling) { + final boolean fullUpdate = processes == null; + final ArrayList<ProcessRecord> activeProcesses = fullUpdate + ? mProcessList.getLruProcessesLOSP() : processes; + ActiveUids activeUids = uids; + if (activeUids == null) { + final int numUids = mActiveUids.size(); + activeUids = mTmpUidRecords; + activeUids.clear(); + for (int i = 0; i < numUids; i++) { + UidRecord uidRec = mActiveUids.valueAt(i); + activeUids.put(uidRec.getUid(), uidRec); + } + } + + if (startProfiling) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); + } + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final long oldTime = now - mConstants.mMaxEmptyTimeMillis; + final int numProc = activeProcesses.size(); + + mAdjSeq++; + if (fullUpdate) { + mNewNumServiceProcs = 0; + mNewNumAServiceProcs = 0; + } + + final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet; + targetProcesses.clear(); + if (!fullUpdate) { + targetProcesses.addAll(activeProcesses); + } + + performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids, + fullUpdate, now, UNKNOWN_ADJ); + + if (fullUpdate) { + assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); + } else { + activeProcesses.clear(); + activeProcesses.addAll(targetProcesses); + assignCachedAdjIfNecessary(activeProcesses); + + for (int i = activeUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = activeUids.valueAt(i); + uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP); + } + updateUidsLSP(activeUids, nowElapsed); + + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason); + } + + activeProcesses.clear(); + } + targetProcesses.clear(); + + if (startProfiling) { + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + return; + } + + /** + * Perform the oom adj update on the given {@code targetProcesses}. + * + * <p>Note: The expectation to the given {@code targetProcesses} is, the caller + * must have called {@link collectReachableProcessesLocked} on it. + */ + private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason, + ProcessRecord topApp, ArraySet<ProcessRecord> targetProcesses, ActiveUids uids, + boolean fullUpdate, long now, int cachedAdj) { + + final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2; + clientProcesses.clear(); + + // We'll need to collect the upstream processes of the target apps here, because those + // processes would potentially impact the procstate/adj via bindings. + if (!fullUpdate) { + final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses, + clientProcesses); + + // If any of its upstream processes are in a cycle, + // move them into the candidate targets. + if (containsCycle) { + // Add all client apps to the target process list. + for (int i = 0, size = clientProcesses.size(); i < size; i++) { + final ProcessRecord client = clientProcesses.get(i); + final UidRecord uidRec = client.getUidRecord(); + targetProcesses.add(client); + if (uidRec != null) { + uids.put(uidRec.getUid(), uidRec); + } + } + clientProcesses.clear(); + } + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + final ProcessRecord app = targetProcesses.valueAt(i); + app.mState.resetCachedInfo(); + final UidRecord uidRec = app.getUidRecord(); + if (uidRec != null) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + } else { + final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP(); + for (int i = 0, size = lru.size(); i < size; i++) { + final ProcessRecord app = lru.get(i); + app.mState.resetCachedInfo(); + final UidRecord uidRec = app.getUidRecord(); + if (uidRec != null) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + } + + updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids, + cachedAdj, now, fullUpdate); + + clientProcesses.clear(); + + return true; + } + + /** + * Collect the reversed reachable processes from the given {@code apps}, the result will be + * returned in the given {@code processes}, which will <em>NOT</em> include the processes from + * the given {@code apps}. + */ + @GuardedBy("mService") + private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps, + ArrayList<ProcessRecord> clientProcesses) { + final ArrayDeque<ProcessRecord> queue = mTmpQueue; + queue.clear(); + clientProcesses.clear(); + for (int i = 0, size = apps.size(); i < size; i++) { + final ProcessRecord app = apps.valueAt(i); + app.mState.setReachable(true); + app.mState.setReversedReachable(true); + queue.offer(app); + } + + // Track if any of them reachables could include a cycle + boolean containsCycle = false; + + // Scan upstreams of the process record + for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) { + if (!pr.mState.isReachable()) { + // If not in the given initial set of apps, add it. + clientProcesses.add(pr); + } + final ProcessServiceRecord psr = pr.mServices; + for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) { + final ServiceRecord s = psr.getRunningServiceAt(i); + final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int j = serviceConnections.size() - 1; j >= 0; j--) { + final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j); + for (int k = clist.size() - 1; k >= 0; k--) { + final ConnectionRecord cr = clist.get(k); + final ProcessRecord client = cr.binding.client; + containsCycle |= client.mState.isReversedReachable(); + if (client.mState.isReversedReachable()) { + continue; + } + queue.offer(client); + client.mState.setReversedReachable(true); + } + } + } + final ProcessProviderRecord ppr = pr.mProviders; + for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) { + final ContentProviderRecord cpr = ppr.getProviderAt(i); + for (int j = cpr.connections.size() - 1; j >= 0; j--) { + final ContentProviderConnection conn = cpr.connections.get(j); + final ProcessRecord client = conn.client; + containsCycle |= client.mState.isReversedReachable(); + if (client.mState.isReversedReachable()) { + continue; + } + queue.offer(client); + client.mState.setReversedReachable(true); + } + } + // If this process is a sandbox itself, also add the app on whose behalf + // its running + if (pr.isSdkSandbox) { + for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) { + ServiceRecord s = psr.getRunningServiceAt(is); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); + for (int i = clist.size() - 1; i >= 0; i--) { + ConnectionRecord cr = clist.get(i); + ProcessRecord attributedApp = cr.binding.attributedClient; + if (attributedApp == null || attributedApp == pr) { + continue; + } + containsCycle |= attributedApp.mState.isReversedReachable(); + if (attributedApp.mState.isReversedReachable()) { + continue; + } + queue.offer(attributedApp); + attributedApp.mState.setReversedReachable(true); + } + } + } + } + } + + // Reset the temporary bits. + for (int i = clientProcesses.size() - 1; i >= 0; i--) { + clientProcesses.get(i).mState.setReversedReachable(false); + } + for (int i = 0, size = apps.size(); i < size; i++) { + final ProcessRecord app = apps.valueAt(i); + app.mState.setReachable(false); + app.mState.setReversedReachable(false); + } + return containsCycle; + } + + @GuardedBy({"mService", "mProcLock"}) + private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses, + ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) { + mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate); + + mProcessRecordProcStateNodes.resetLastNodes(); + mProcessRecordAdjNodes.resetLastNodes(); + + final int procStateTarget = mProcessRecordProcStateNodes.size() - 1; + final int adjTarget = mProcessRecordAdjNodes.size() - 1; + + final int appUid = !fullUpdate && targetProcesses.size() > 0 + ? targetProcesses.valueAt(0).uid : -1; + final int logUid = mService.mCurOomAdjUid; + + mAdjSeq++; + // All apps to be updated will be moved to the lowest slot. + if (fullUpdate) { + // Move all the process record node to the lowest slot, we'll do recomputation on all of + // them. Use the processes from the lru list, because the scanning order matters here. + final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP(); + for (int i = procStateTarget; i >= 0; i--) { + mProcessRecordProcStateNodes.reset(i); + // Force the last node to the head since we'll recompute all of them. + mProcessRecordProcStateNodes.setLastNodeToHead(i); + } + // enqueue the targets in the reverse order of the lru list. + for (int i = lruList.size() - 1; i >= 0; i--) { + mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget); + } + // Do the same to the adj nodes. + for (int i = adjTarget; i >= 0; i--) { + mProcessRecordAdjNodes.reset(i); + // Force the last node to the head since we'll recompute all of them. + mProcessRecordAdjNodes.setLastNodeToHead(i); + } + for (int i = lruList.size() - 1; i >= 0; i--) { + mProcessRecordAdjNodes.append(lruList.get(i), adjTarget); + } + } else { + // Move the target processes to the lowest slot. + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + final ProcessRecord app = targetProcesses.valueAt(i); + final int procStateSlot = processStateToSlot(app.mState.getCurProcState()); + final int adjSlot = adjToSlot(app.mState.getCurRawAdj()); + mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget); + mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget); + } + // Move the "lastNode" to head to make sure we scan all nodes in this slot. + mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget); + mProcessRecordAdjNodes.setLastNodeToHead(adjTarget); + } + + // All apps to be updated have been moved to the lowest slot. + // Do an initial pass of the computation. + mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1, + this::computeInitialOomAdjLSP); + + if (!fullUpdate) { + // We didn't update the client processes with the computeInitialOomAdjLSP + // because they don't need to do so. But they'll be playing vital roles in + // computing the bindings. So include them into the scan list below. + for (int i = 0, size = clientProcesses.size(); i < size; i++) { + mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i)); + } + // We don't update the adj list since we're resetting it below. + } + + // Now nodes are set into their slots, without facting in the bindings. + // The nodes between the `lastNode` pointer and the TAIL should be the new nodes. + // + // The whole rationale here is that, the bindings from client to host app, won't elevate + // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT + // is a special case here, but client app's raw adj is still no less than the host app's). + // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes, + // check its bindings, elevate its host app's slot if necessary. + // + // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list. + // Because the procstate and adj are not always in sync - there are cases where + // the processes with lower proc state could be getting a higher oom adj score. + // And because of this, the procstate and adj node lists are basically two priority heaps. + // + // As the 2nd pass with the adj node lists potentially includes a significant amount of + // duplicated scans as the 1st pass has done, we'll reset the last node pointers for + // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot + // gets bumped, we'll only scan those in 2nd pass. + + mProcessRecordAdjNodes.resetLastNodes(); + + // 1st pass, scan each slot in the procstate node list. + for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) { + mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP); + } + + // 2nd pass, scan each slot in the adj node list. + for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) { + mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP); + } + } + + @GuardedBy({"mService", "mProcLock"}) + private void computeInitialOomAdjLSP(OomAdjusterArgs args) { + final ProcessRecord app = args.mApp; + final int cachedAdj = args.mCachedAdj; + final ProcessRecord topApp = args.mTopApp; + final long now = args.mNow; + final int oomAdjReason = args.mOomAdjReason; + final ActiveUids uids = args.mUids; + final boolean fullUpdate = args.mFullUpdate; + + if (DEBUG_OOM_ADJ) { + Slog.i(TAG, "OOM ADJ initial args app=" + app + + " cachedAdj=" + cachedAdj + + " topApp=" + topApp + + " now=" + now + + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason) + + " fullUpdate=" + fullUpdate); + } + + if (uids != null) { + final UidRecord uidRec = app.getUidRecord(); + + if (uidRec != null) { + uids.put(uidRec.getUid(), uidRec); + } + } + + computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason, + false); + } + + /** + * @return The proposed change to the schedGroup. + */ + @GuardedBy({"mService", "mProcLock"}) + @Override + protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj, + int schedGroup) { + schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup); + + updateAdjSlotIfNecessary(app, prevRawAppAdj); + + return schedGroup; + } + + @GuardedBy({"mService", "mProcLock"}) + @Override + protected void setIntermediateProcStateLSP(ProcessRecord app, int procState, + int prevProcState) { + super.setIntermediateProcStateLSP(app, procState, prevProcState); + + updateProcStateSlotIfNecessary(app, prevProcState); + } + + @GuardedBy({"mService", "mProcLock"}) + private void computeHostOomAdjLSP(OomAdjusterArgs args) { + final ProcessRecord app = args.mApp; + final int cachedAdj = args.mCachedAdj; + final ProcessRecord topApp = args.mTopApp; + final long now = args.mNow; + final @OomAdjReason int oomAdjReason = args.mOomAdjReason; + final boolean fullUpdate = args.mFullUpdate; + final ActiveUids uids = args.mUids; + + final ProcessServiceRecord psr = app.mServices; + for (int i = psr.numberOfConnections() - 1; i >= 0; i--) { + ConnectionRecord cr = psr.getConnectionAt(i); + ProcessRecord service = cr.hasFlag(ServiceInfo.FLAG_ISOLATED_PROCESS) + ? cr.binding.service.isolationHostProc : cr.binding.service.app; + if (service == null || service == app + || (service.mState.getMaxAdj() >= SYSTEM_ADJ + && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + + computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + + for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) { + final ConnectionRecord cr = psr.getSdkSandboxConnectionAt(i); + final ProcessRecord service = cr.binding.service.app; + if (service == null || service == app + || (service.mState.getMaxAdj() >= SYSTEM_ADJ + && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + + final ProcessProviderRecord ppr = app.mProviders; + for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) { + ContentProviderConnection cpc = ppr.getProviderConnectionAt(i); + ProcessRecord provider = cpc.provider.proc; + if (provider == null || provider == app + || (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ + && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + } +} diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index f532122c10d9..7037fecb2713 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -22,6 +22,7 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.MY_PID; +import static com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode.NUM_NODE_TYPE; import static java.util.Objects.requireNonNull; @@ -63,6 +64,7 @@ import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.Zygote; import com.android.server.FgThread; +import com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode; import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; @@ -434,6 +436,8 @@ class ProcessRecord implements WindowProcessListener { */ volatile boolean mSkipProcessGroupCreation; + final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE]; + void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo, long startUptime, long startElapsedTime) { this.mStartUid = startUid; @@ -1114,6 +1118,7 @@ class ProcessRecord implements WindowProcessListener { mState.onCleanupApplicationRecordLSP(); mServices.onCleanupApplicationRecordLocked(); mReceivers.onCleanupApplicationRecordLocked(); + mService.mOomAdjuster.onProcessEndLocked(this); return mProviders.onCleanupApplicationRecordLocked(allowRestart); } diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 7ff6d116baaf..a165e8897aa4 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.pm.ServiceInfo; @@ -134,6 +135,11 @@ final class ProcessServiceRecord { private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>(); /** + * All ConnectionRecord this process holds indirectly to SDK sandbox processes. + */ + private @Nullable ArraySet<ConnectionRecord> mSdkSandboxConnections; + + /** * A set of UIDs of all bound clients. */ private ArraySet<Integer> mBoundClientUids = new ArraySet<>(); @@ -490,13 +496,18 @@ final class ProcessServiceRecord { void addConnection(ConnectionRecord connection) { mConnections.add(connection); + addSdkSandboxConnectionIfNecessary(connection); } void removeConnection(ConnectionRecord connection) { mConnections.remove(connection); + removeSdkSandboxConnectionIfNecessary(connection); } void removeAllConnections() { + for (int i = 0, size = mConnections.size(); i < size; i++) { + removeSdkSandboxConnectionIfNecessary(mConnections.valueAt(i)); + } mConnections.clear(); } @@ -508,6 +519,39 @@ final class ProcessServiceRecord { return mConnections.size(); } + private void addSdkSandboxConnectionIfNecessary(ConnectionRecord connection) { + final ProcessRecord attributedClient = connection.binding.attributedClient; + if (attributedClient != null && connection.binding.service.isSdkSandbox) { + if (attributedClient.mServices.mSdkSandboxConnections == null) { + attributedClient.mServices.mSdkSandboxConnections = new ArraySet<>(); + } + attributedClient.mServices.mSdkSandboxConnections.add(connection); + } + } + + private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) { + final ProcessRecord attributedClient = connection.binding.attributedClient; + if (attributedClient != null && connection.binding.service.isSdkSandbox) { + if (attributedClient.mServices.mSdkSandboxConnections == null) { + attributedClient.mServices.mSdkSandboxConnections.remove(connection); + } + } + } + + void removeAllSdkSandboxConnections() { + if (mSdkSandboxConnections != null) { + mSdkSandboxConnections.clear(); + } + } + + ConnectionRecord getSdkSandboxConnectionAt(int index) { + return mSdkSandboxConnections != null ? mSdkSandboxConnections.valueAt(index) : null; + } + + int numberOfSdkSandboxConnections() { + return mSdkSandboxConnections != null ? mSdkSandboxConnections.size() : 0; + } + void addBoundClientUid(int clientUid, String clientPackageName, long bindFlags) { mBoundClientUids.add(clientUid); mApp.getWindowProcessController() diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index db341d253818..a9c388c232ed 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -378,6 +378,12 @@ final class ProcessStateRecord { private boolean mReachable; /** + * Whether or not this process is reversed reachable from given process. + */ + @GuardedBy("mService") + private boolean mReversedReachable; + + /** * The most recent time when the last visible activity within this process became invisible. * * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is @@ -454,6 +460,9 @@ final class ProcessStateRecord { @GuardedBy("mService") private int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + @GuardedBy("mService") + private boolean mScheduleLikeTopApp = false; + ProcessStateRecord(ProcessRecord app) { mApp = app; mService = app.mService; @@ -614,9 +623,11 @@ final class ProcessStateRecord { void forceProcessStateUpTo(int newState) { if (mRepProcState > newState) { synchronized (mProcLock) { + final int prevProcState = mRepProcState; setReportedProcState(newState); setCurProcState(newState); setCurRawProcState(newState); + mService.mOomAdjuster.onProcessStateChanged(mApp, prevProcState); } } } @@ -985,6 +996,16 @@ final class ProcessStateRecord { } @GuardedBy("mService") + boolean isReversedReachable() { + return mReversedReachable; + } + + @GuardedBy("mService") + void setReversedReachable(boolean reversedReachable) { + mReversedReachable = reversedReachable; + } + + @GuardedBy("mService") void resetCachedInfo() { mCachedHasActivities = VALUE_INVALID; mCachedIsHeavyWeight = VALUE_INVALID; @@ -1134,6 +1155,16 @@ final class ProcessStateRecord { return mCachedSchedGroup; } + @GuardedBy("mService") + boolean shouldScheduleLikeTopApp() { + return mScheduleLikeTopApp; + } + + @GuardedBy("mService") + void setScheduleLikeTopApp(boolean scheduleLikeTopApp) { + mScheduleLikeTopApp = scheduleLikeTopApp; + } + @GuardedBy(anyOf = {"mService", "mProcLock"}) public String makeAdjReason() { if (mAdjSource != null || mAdjTarget != null) { diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 4f7a2ba58570..619186157cce 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -80,8 +80,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { @VisibleForTesting static final String UNIQUE_ID_PREFIX = "virtual:"; - private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = - new ArrayMap<IBinder, VirtualDisplayDevice>(); + private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = new ArrayMap<>(); private final Handler mHandler; private final SurfaceControlDisplayFactory mSurfaceControlDisplayFactory; @@ -113,9 +112,16 @@ public class VirtualDisplayAdapter extends DisplayAdapter { public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback, IMediaProjection projection, int ownerUid, String ownerPackageName, Surface surface, int flags, VirtualDisplayConfig virtualDisplayConfig) { + IBinder appToken = callback.asBinder(); + if (mVirtualDisplayDevices.containsKey(appToken)) { + Slog.wtfStack(TAG, + "Can't create virtual display, display with same appToken already exists"); + return null; + } + String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; - IBinder appToken = callback.asBinder(); + IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, virtualDisplayConfig.getRequestedRefreshRate()); final String baseUniqueId = diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java index f0bf1ea80570..d0c346a63889 100644 --- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java +++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java @@ -334,7 +334,10 @@ public class ArtStatsLogUtils { ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN), cancellationReason, durationMs, - 0); // deprecated, used to be durationIncludingSleepMs + 0, // deprecated, used to be durationIncludingSleepMs + 0, // optimizedPackagesCount + 0, // packagesDependingOnBootClasspathCount + 0); // totalPackagesCount } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 2c866abe614a..8673b90c966d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.Manifest.permission.BIND_VOICE_INTERACTION; import static android.Manifest.permission.CHANGE_CONFIGURATION; +import static android.Manifest.permission.CONTROL_KEYGUARD; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.DETECT_SCREEN_CAPTURE; import static android.Manifest.permission.INTERACT_ACROSS_USERS; @@ -3549,6 +3550,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void keyguardGoingAway(int flags) { + mAmInternal.enforceCallingPermission(CONTROL_KEYGUARD, "unlock keyguard"); enforceNotIsolatedCaller("keyguardGoingAway"); final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6d0fd2a51855..029f46f92dbe 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5682,6 +5682,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // TODO(b/233286785): Add sync support to wallpaper. return true; } + if (mActivityRecord != null && mViewVisibility != View.VISIBLE + && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION + && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) { + // Skip sync for invisible app windows which are not managed by activity lifecycle. + return false; + } // In the WindowContainer implementation we immediately mark ready // since a generic WindowContainer only needs to wait for its // children to finish and is immediately ready from its own diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index c0cfa53a0a98..486ddb4cb354 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -420,14 +420,11 @@ public class MidiService extends IMidiManager.Stub { setDeviceServer(server); } - @RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS, - Manifest.permission.CREATE_USERS, - Manifest.permission.MANAGE_USERS}) public Device(BluetoothDevice bluetoothDevice) { mBluetoothDevice = bluetoothDevice; mServiceInfo = null; mUid = mBluetoothServiceUid; - mUserId = mUserManager.getMainUser().getIdentifier(); + mUserId = UserHandle.getUserId(mUid); } private void setDeviceServer(IMidiDeviceServer server) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt index 6108ad298800..ce4aa4446698 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt @@ -16,6 +16,9 @@ package com.android.server.permission.access.immutable +/** + * Immutable list with index-based access. + */ sealed class IndexedList<T>( internal val list: ArrayList<T> ) : Immutable<MutableIndexedList<T>> { @@ -34,6 +37,9 @@ sealed class IndexedList<T>( override fun toString(): String = list.toString() } +/** + * Mutable list with index-based access. + */ class MutableIndexedList<T>( list: ArrayList<T> = ArrayList() ) : IndexedList<T>(list) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt index 1202c817d97b..77e71baf0ab7 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt @@ -16,6 +16,9 @@ package com.android.server.permission.access.immutable +/** + * Immutable set with index-based access, implemented using a list. + */ sealed class IndexedListSet<T>( internal val list: ArrayList<T> ) : Immutable<MutableIndexedListSet<T>> { @@ -36,6 +39,9 @@ sealed class IndexedListSet<T>( override fun toString(): String = list.toString() } +/** + * Mutable set with index-based access, implemented using a list. + */ class MutableIndexedListSet<T>( list: ArrayList<T> = ArrayList() ) : IndexedListSet<T>(list) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt index 5c75de83792f..299cc89d9a07 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt @@ -18,6 +18,9 @@ package com.android.server.permission.access.immutable import android.util.ArrayMap +/** + * Immutable map with index-based access. + */ sealed class IndexedMap<K, V>( internal val map: ArrayMap<K, V> ) : Immutable<MutableIndexedMap<K, V>> { @@ -42,6 +45,9 @@ sealed class IndexedMap<K, V>( override fun toString(): String = map.toString() } +/** + * Mutable map with index-based access. + */ class MutableIndexedMap<K, V>( map: ArrayMap<K, V> = ArrayMap() ) : IndexedMap<K, V>(map) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt index 8c963aae1967..ff76a4745c8b 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt @@ -18,6 +18,11 @@ package com.android.server.permission.access.immutable import android.util.ArrayMap +/** + * Immutable map with index-based access and mutable data structure values. + * + * @see MutableReference + */ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( internal val map: ArrayMap<K, MutableReference<I, M>> ) : Immutable<MutableIndexedReferenceMap<K, I, M>> { @@ -42,6 +47,11 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( override fun toString(): String = map.toString() } +/** + * Mutable map with index-based access and mutable data structure values. + * + * @see MutableReference + */ class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( map: ArrayMap<K, MutableReference<I, M>> = ArrayMap() ) : IndexedReferenceMap<K, I, M>(map) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt index 986861624e4b..547e56cef62a 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt @@ -18,6 +18,9 @@ package com.android.server.permission.access.immutable import android.util.ArraySet +/** + * Immutable set with index-based access. + */ sealed class IndexedSet<T>( internal val set: ArraySet<T> ) : Immutable<MutableIndexedSet<T>> { @@ -37,6 +40,9 @@ sealed class IndexedSet<T>( override fun toString(): String = set.toString() } +/** + * Mutable set with index-based access. + */ class MutableIndexedSet<T>( set: ArraySet<T> = ArraySet() ) : IndexedSet<T>(set) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt index b7d8b4ccddd0..7ed29e8813ac 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt @@ -18,6 +18,9 @@ package com.android.server.permission.access.immutable import android.util.SparseArray +/** + * Immutable map with index-based access and [Int] keys. + */ sealed class IntMap<T>( internal val array: SparseArray<T> ) : Immutable<MutableIntMap<T>> { @@ -41,6 +44,9 @@ sealed class IntMap<T>( override fun toString(): String = array.toString() } +/** + * Mutable map with index-based access and [Int] keys. + */ class MutableIntMap<T>( array: SparseArray<T> = SparseArray() ) : IntMap<T>(array) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt index 22fa8f2a7c7e..160b2279a0ba 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt @@ -18,6 +18,11 @@ package com.android.server.permission.access.immutable import android.util.SparseArray +/** + * Immutable map with index-based access, [Int] keys and mutable data structure values. + * + * @see MutableReference + */ sealed class IntReferenceMap<I : Immutable<M>, M : I>( internal val array: SparseArray<MutableReference<I, M>> ) : Immutable<MutableIntReferenceMap<I, M>> { @@ -42,6 +47,11 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( override fun toString(): String = array.toString() } +/** + * Mutable map with index-based access, [Int] keys and mutable data structure values. + * + * @see MutableReference + */ class MutableIntReferenceMap<I : Immutable<M>, M : I>( array: SparseArray<MutableReference<I, M>> = SparseArray() ) : IntReferenceMap<I, M>(array) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt index 9da36717db7e..21f2af20c3a9 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt @@ -18,6 +18,9 @@ package com.android.server.permission.access.immutable import android.util.SparseBooleanArray +/** + * Immutable set with index-based access and [Int] elements. + */ sealed class IntSet( internal val array: SparseBooleanArray ) : Immutable<MutableIntSet> { @@ -37,6 +40,9 @@ sealed class IntSet( override fun toString(): String = array.toString() } +/** + * Mutable set with index-based access and [Int] elements. + */ class MutableIntSet( array: SparseBooleanArray = SparseBooleanArray() ) : IntSet(array) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt index e39a3bbc8942..171cfeb4379d 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt @@ -16,14 +16,39 @@ package com.android.server.permission.access.immutable +/** + * Wrapper class for reference to a mutable data structure instance. + * + * This class encapsulates the logic to mutate/copy a mutable data structure instance and update the + * reference to the new mutated instance. It also remembers the mutated instance so that it can be + * reused during further mutations. + * + * Instances of this class should be kept private within a data structure, with the [get] method + * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate] + * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the + * data structure is mutated/copied, a new instance of this class should be obtained with + * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents + * further modifications to a data structure accessed with its immutable interface. + * + * @see MutableIndexedReferenceMap + * @see MutableIntReferenceMap + */ class MutableReference<I : Immutable<M>, M : I> private constructor( private var immutable: I, private var mutable: M? ) { constructor(mutable: M) : this(mutable, mutable) + /** + * Return an immutable reference to the wrapped mutable data structure. + */ fun get(): I = immutable + /** + * Make the wrapped mutable data structure mutable, by either calling [Immutable.toMutable] and + * replacing the wrapped reference with its result, or reusing the existing reference if it's + * already mutable. + */ fun mutate(): M { mutable?.let { return it } return immutable.toMutable().also { @@ -32,6 +57,10 @@ class MutableReference<I : Immutable<M>, M : I> private constructor( } } + /** + * Create a new [MutableReference] instance with the wrapped mutable data structure being + * immutable-only again. + */ fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null) override fun equals(other: Any?): Boolean { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index d16c9c59bb1b..bf2311761891 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -248,6 +248,8 @@ public class DisplayManagerServiceTest { @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal; @Mock IVirtualDisplayCallback.Stub mMockAppToken; @Mock IVirtualDisplayCallback.Stub mMockAppToken2; + + @Mock IVirtualDisplayCallback.Stub mMockAppToken3; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock LightsManager mMockLightsManager; @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter; @@ -838,6 +840,7 @@ public class DisplayManagerServiceTest { registerDefaultDisplays(displayManager); when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); @@ -851,7 +854,7 @@ public class DisplayManagerServiceTest { int displayId1 = localService.createVirtualDisplay( builder1.build(), - mMockAppToken /* callback */, + mMockAppToken2 /* callback */, virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); @@ -893,6 +896,7 @@ public class DisplayManagerServiceTest { registerDefaultDisplays(displayManager); when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); @@ -927,7 +931,7 @@ public class DisplayManagerServiceTest { int displayId2 = localService.createVirtualDisplay( builder2.build(), - mMockAppToken /* callback */, + mMockAppToken2 /* callback */, virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); @@ -950,6 +954,8 @@ public class DisplayManagerServiceTest { registerDefaultDisplays(displayManager); when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2); + when(mMockAppToken3.asBinder()).thenReturn(mMockAppToken3); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); @@ -999,7 +1005,7 @@ public class DisplayManagerServiceTest { int ownDisplayGroupDisplayId = localService.createVirtualDisplay( ownDisplayGroupConfig, - mMockAppToken /* callback */, + mMockAppToken2 /* callback */, virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); @@ -1024,7 +1030,7 @@ public class DisplayManagerServiceTest { int defaultDisplayGroupDisplayId = localService.createVirtualDisplay( defaultDisplayGroupConfig, - mMockAppToken /* callback */, + mMockAppToken3 /* callback */, null /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index e58ec450dd61..01e49f2e207d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -59,6 +59,7 @@ import android.view.Display; import android.view.DisplayInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -303,6 +304,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_BothDpcsSupportNits() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -356,6 +358,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -388,6 +391,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -418,6 +422,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -450,6 +455,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_AutomaticBrightness() { Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, @@ -521,6 +527,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -612,6 +619,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() { DisplayPowerControllerHolder followerHolder = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java new file mode 100644 index 000000000000..8bbacc494efd --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 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.display; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; +import android.os.IBinder; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VirtualDisplayAdapterTest { + + @Mock + Context mContextMock; + + @Mock + VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory; + + @Mock + DisplayAdapter.Listener mMockListener; + + @Mock + IVirtualDisplayCallback mMockCallback; + + @Mock + IBinder mMockBinder; + + private TestHandler mHandler; + + private VirtualDisplayAdapter mVirtualDisplayAdapter; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mHandler = new TestHandler(null); + mVirtualDisplayAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(), + mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory); + + when(mMockCallback.asBinder()).thenReturn(mMockBinder); + } + + @Test + public void testCreatesVirtualDisplay() { + VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1, + /* height= */ 1, /* densityDpi= */ 1).build(); + + DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, + /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", + /* surface= */ null, /* flags= */ 0, config); + + assertNotNull(result); + } + + @Test + public void testDoesNotCreateVirtualDisplayForSameCallback() { + VirtualDisplayConfig config1 = new VirtualDisplayConfig.Builder("test", /* width= */ 1, + /* height= */ 1, /* densityDpi= */ 1).build(); + VirtualDisplayConfig config2 = new VirtualDisplayConfig.Builder("test2", /* width= */ 1, + /* height= */ 1, /* densityDpi= */ 1).build(); + mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, /* projection= */ null, + /* ownerUid= */ 10, /* packageName= */ "testpackage", /* surface= */ null, + /* flags= */ 0, config1); + + DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, + /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", + /* surface= */ null, /* flags= */ 0, config2); + + assertNull(result); + } +} 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 1f4563fb2682..976e74083849 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -105,6 +105,7 @@ import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.ActivityTaskManagerService; import com.android.server.wm.WindowProcessController; +import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -206,8 +207,10 @@ public class MockingOomAdjusterTests { setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object()); doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr) .enqueueProcessChangeItemLocked(anyInt(), anyInt()); - sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, - new ActiveUids(sService, false)); + sService.mOomAdjuster = sService.mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(sService, sService.mProcessList, + new ActiveUids(sService, false)) + : new OomAdjuster(sService, sService.mProcessList, new ActiveUids(sService, false)); sService.mOomAdjuster.mAdjSeq = 10000; sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE); if (sService.mConstants.USE_TIERED_CACHED_ADJ) { @@ -220,6 +223,11 @@ public class MockingOomAdjusterTests { LocalServices.removeServiceForTest(PackageManagerInternal.class); } + @After + public void tearDown() { + sService.mOomAdjuster.resetInternal(); + } + private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { try { Field field = clazz.getDeclaredField(fieldName); @@ -249,6 +257,9 @@ public class MockingOomAdjusterTests { ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP(); lru.clear(); Collections.addAll(lru, apps); + for (ProcessRecord app : apps) { + sService.mOomAdjuster.onProcessBeginLocked(app); + } } /** @@ -259,6 +270,7 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") private void updateOomAdj(ProcessRecord... apps) { if (apps.length == 1) { + sService.mOomAdjuster.onProcessBeginLocked(apps[0]); sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE); } else { setProcessesToLru(apps); @@ -600,10 +612,13 @@ public class MockingOomAdjusterTests { s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime; s.getConnections().clear(); app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } // Out of grace period but valid binding allows the adjustment. @@ -620,10 +635,13 @@ public class MockingOomAdjusterTests { s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs; app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } // Out of grace period and no valid binding so no adjustment. @@ -641,10 +659,13 @@ public class MockingOomAdjusterTests { nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs; s.getConnections().clear(); app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } } @@ -657,11 +678,12 @@ public class MockingOomAdjusterTests { MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true)); system.mState.setMaxAdj(PERSISTENT_PROC_ADJ); system.mState.setHasTopUi(true); + sService.mOomAdjuster.onProcessBeginLocked(system); // Simulate the system starting and binding to a service in the app. ServiceRecord s = bindService(app, system, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(system, app); assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT); @@ -850,6 +872,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + client.mServices.setTreatLikeActivity(true); bindService(app, client, null, Context.BIND_WAIVE_PRIORITY | Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -1006,7 +1029,7 @@ public class MockingOomAdjusterTests { bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState()); assertNoBfsl(app); @@ -1132,7 +1155,7 @@ public class MockingOomAdjusterTests { assertNoBfsl(app); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj()); assertBfsl(app); @@ -1148,7 +1171,7 @@ public class MockingOomAdjusterTests { bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class)); client.mState.setRunningRemoteAnimation(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj()); } @@ -1199,6 +1222,8 @@ public class MockingOomAdjusterTests { updateOomAdj(client, app); assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1217,6 +1242,8 @@ public class MockingOomAdjusterTests { doReturn(false).when(wpc).isHeavyWeightProcess(); assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1229,9 +1256,11 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(client, app); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1246,10 +1275,12 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(client, app); doReturn(false).when(wpc).isHeavyWeightProcess(); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } } @@ -1849,7 +1880,7 @@ public class MockingOomAdjusterTests { bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class)); bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class)); - updateOomAdj(app1, app2); + updateOomAdj(client1, client2, app1, app2); assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP); @@ -1899,6 +1930,8 @@ public class MockingOomAdjusterTests { s1.getConnections().clear(); s2.getConnections().clear(); + client1.mServices.removeAllConnections(); + client2.mServices.removeAllConnections(); client1.mState.setMaxAdj(UNKNOWN_ADJ); client2.mState.setMaxAdj(UNKNOWN_ADJ); client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); @@ -1909,7 +1942,7 @@ public class MockingOomAdjusterTests { bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class)); - updateOomAdj(app1, app2); + updateOomAdj(client1, client2, app1, app2); // VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE. assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ, @@ -1922,7 +1955,7 @@ public class MockingOomAdjusterTests { doReturn(client2).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE); + updateOomAdj(client2, app2); assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT); } @@ -1977,6 +2010,7 @@ public class MockingOomAdjusterTests { app.setPendingFinishAttach(true); app.mState.setHasForegroundActivities(false); + sService.mOomAdjuster.onProcessBeginLocked(app); sService.mOomAdjuster.setAttachingProcessStatesLSP(app); updateOomAdj(app); @@ -1991,7 +2025,9 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); app.setPendingFinishAttach(true); app.mState.setHasForegroundActivities(true); + doReturn(app).when(sService).getTopApp(); + sService.mOomAdjuster.onProcessBeginLocked(app); sService.mOomAdjuster.setAttachingProcessStatesLSP(app); updateOomAdj(app); @@ -2088,7 +2124,7 @@ public class MockingOomAdjusterTests { anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); doNothing().when(sService.mServices) .scheduleServiceTimeoutLocked(any(ProcessRecord.class)); - sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE); + updateOomAdj(client1, client2, app1, app2, app3); assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState()); assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState()); @@ -2426,6 +2462,8 @@ public class MockingOomAdjusterTests { lru.clear(); lru.add(app2); lru.add(app); + sService.mOomAdjuster.onProcessBeginLocked(app2); + sService.mOomAdjuster.onProcessBeginLocked(app); final ComponentName cn = ComponentName.unflattenFromString( MOCKAPP_PACKAGENAME + "/.TestService"); @@ -2528,7 +2566,7 @@ public class MockingOomAdjusterTests { doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); doReturn(app).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(app); assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS new file mode 100644 index 000000000000..008a53f6941f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS @@ -0,0 +1 @@ +include /services/companion/java/com/android/server/companion/OWNERS
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java new file mode 100644 index 000000000000..01159b1f901c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 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.companion.utils; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManagerInternal; +import android.content.pm.Signature; +import android.content.pm.SigningDetails; +import android.content.res.Resources; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.companion.PackageUtils; +import com.android.server.pm.pkg.AndroidPackage; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +public class PackageUtilsTest { + private static final String[] ALLOWED_PACKAGE_NAMES = new String[]{ + "allowed_app", + }; + private static final Signature[] ALLOWED_PACKAGE_SIGNATURES = new Signature[]{ + new Signature("001122"), + }; + private static final String[] DISALLOWED_PACKAGE_NAMES = new String[]{ + "disallowed_app", + }; + private static final Signature[] DISALLOWED_PACKAGE_SIGNATURES = new Signature[]{ + new Signature("778899"), + }; + + @Test + public void isAllowlisted_true() { + Context context = spy( + new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + final Resources res = spy(context.getResources()); + doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray( + com.android.internal.R.array.config_companionDevicePackages); + doReturn(android.util.PackageUtils.computeSignaturesSha256Digests( + ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray( + com.android.internal.R.array.config_companionDeviceCerts); + doReturn(res).when(context).getResources(); + PackageManagerInternal pm = mock(PackageManagerInternal.class); + AndroidPackage ap = mock(AndroidPackage.class); + SigningDetails sd = new SigningDetails( + ALLOWED_PACKAGE_SIGNATURES, + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, + null, + null); + doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]); + doReturn(sd).when(ap).getSigningDetails(); + assertTrue(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0])); + } + + @Test + public void isAllowlisted_package_disallowed() { + Context context = spy( + new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + final Resources res = spy(context.getResources()); + doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray( + com.android.internal.R.array.config_companionDevicePackages); + doReturn(android.util.PackageUtils.computeSignaturesSha256Digests( + ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray( + com.android.internal.R.array.config_companionDeviceCerts); + doReturn(res).when(context).getResources(); + PackageManagerInternal pm = mock(PackageManagerInternal.class); + AndroidPackage ap = mock(AndroidPackage.class); + SigningDetails sd = new SigningDetails( + ALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, + null, + null); + doReturn(ap).when(pm).getPackage(DISALLOWED_PACKAGE_NAMES[0]); + doReturn(sd).when(ap).getSigningDetails(); + assertFalse(PackageUtils.isPackageAllowlisted(context, pm, DISALLOWED_PACKAGE_NAMES[0])); + } + + @Test + public void isAllowlisted_signature_mismatch() { + Context context = spy( + new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + final Resources res = spy(context.getResources()); + doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray( + com.android.internal.R.array.config_companionDevicePackages); + doReturn(android.util.PackageUtils.computeSignaturesSha256Digests( + ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray( + com.android.internal.R.array.config_companionDeviceCerts); + doReturn(res).when(context).getResources(); + PackageManagerInternal pm = mock(PackageManagerInternal.class); + AndroidPackage ap = mock(AndroidPackage.class); + SigningDetails sd = new SigningDetails( + DISALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, + null, + null); + doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]); + doReturn(sd).when(ap).getSigningDetails(); + assertFalse(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0])); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt index 9f855c5ca83f..f834cb24f245 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt @@ -99,23 +99,45 @@ class AndroidPackageParsingValidationTest { @Test fun parseApplicationTag() { val tag = "application" - validateTagAttr(tag, "backupAgent", - R.styleable.AndroidManifestApplication_backupAgent, 1024) - validateTagAttrComponentName(tag, "backupAgent", - R.styleable.AndroidManifestApplication_backupAgent) - validateTagAttr(tag, "manageSpaceActivity", - R.styleable.AndroidManifestApplication_manageSpaceActivity, 1024) + validateTagAttr( + tag, + "backupAgent", + R.styleable.AndroidManifestApplication_backupAgent, + 1024 + ) + validateTagAttrComponentName( + tag, + "backupAgent", + R.styleable.AndroidManifestApplication_backupAgent + ) + validateTagAttr( + tag, + "manageSpaceActivity", + R.styleable.AndroidManifestApplication_manageSpaceActivity, + 1024 + ) validateTagAttr(tag, "name", R.styleable.AndroidManifestApplication_name, 1024) - validateTagAttrComponentName(tag, "name", - R.styleable.AndroidManifestApplication_name) + validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestApplication_name) validateTagAttr(tag, "permission", R.styleable.AndroidManifestApplication_permission, 1024) validateTagAttr(tag, "process", R.styleable.AndroidManifestApplication_process, 1024) - validateTagAttr(tag, "requiredAccountType", - R.styleable.AndroidManifestApplication_requiredAccountType, 1024) - validateTagAttr(tag, "restrictedAccountType", - R.styleable.AndroidManifestApplication_restrictedAccountType, 1024) - validateTagAttr(tag, "taskAffinity", - R.styleable.AndroidManifestApplication_taskAffinity, 1024) + validateTagAttr( + tag, + "requiredAccountType", + R.styleable.AndroidManifestApplication_requiredAccountType, + 1024 + ) + validateTagAttr( + tag, + "restrictedAccountType", + R.styleable.AndroidManifestApplication_restrictedAccountType, + 1024 + ) + validateTagAttr( + tag, + "taskAffinity", + R.styleable.AndroidManifestApplication_taskAffinity, + 1024 + ) validateTagCount("profileable", 100, tag) validateTagCount("uses-native-library", 100, tag) validateTagCount("receiver", 1000, tag) @@ -159,12 +181,23 @@ class AndroidPackageParsingValidationTest { fun parseActivityAliasTag() { val tag = "activity-alias" validateTagAttr(tag, "name", R.styleable.AndroidManifestActivityAlias_name, 1024) - validateTagAttr(tag, "permission", - R.styleable.AndroidManifestActivityAlias_permission, 1024) - validateTagAttr(tag, "targetActivity", - R.styleable.AndroidManifestActivityAlias_targetActivity, 1024) - validateTagAttrComponentName(tag, "targetActivity", - R.styleable.AndroidManifestActivityAlias_targetActivity) + validateTagAttr( + tag, + "permission", + R.styleable.AndroidManifestActivityAlias_permission, + 1024 + ) + validateTagAttr( + tag, + "targetActivity", + R.styleable.AndroidManifestActivityAlias_targetActivity, + 1024 + ) + validateTagAttrComponentName( + tag, + "targetActivity", + R.styleable.AndroidManifestActivityAlias_targetActivity + ) validateTagCount("meta-data", 1000, tag) validateTagCount("intent-filter", 20000, tag) } @@ -173,7 +206,6 @@ class AndroidPackageParsingValidationTest { fun parseUsesLibraryTag() { val tag = "uses-library" validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesLibrary_name, 1024) - validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestUsesLibrary_name) } @Test @@ -181,10 +213,17 @@ class AndroidPackageParsingValidationTest { val tag = "activity" validateTagAttr(tag, "name", R.styleable.AndroidManifestActivity_name, 1024) validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestActivity_name) - validateTagAttr(tag, "parentActivityName", - R.styleable.AndroidManifestActivity_parentActivityName, 1024) - validateTagAttrComponentName(tag, "parentActivityName", - R.styleable.AndroidManifestActivity_parentActivityName) + validateTagAttr( + tag, + "parentActivityName", + R.styleable.AndroidManifestActivity_parentActivityName, + 1024 + ) + validateTagAttrComponentName( + tag, + "parentActivityName", + R.styleable.AndroidManifestActivity_parentActivityName + ) validateTagAttr(tag, "permission", R.styleable.AndroidManifestActivity_permission, 1024) validateTagAttr(tag, "process", R.styleable.AndroidManifestActivity_process, 1024) validateTagAttr(tag, "taskAffinity", R.styleable.AndroidManifestActivity_taskAffinity, 1024) @@ -197,26 +236,49 @@ class AndroidPackageParsingValidationTest { fun parseOverlayTag() { val tag = "overlay" validateTagAttr(tag, "category", R.styleable.AndroidManifestResourceOverlay_category, 1024) - validateTagAttr(tag, "requiredSystemPropertyName", - R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName, 1024) - validateTagAttr(tag, "requiredSystemPropertyValue", - R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue, PROP_VALUE_MAX) - validateTagAttr(tag, "targetPackage", - R.styleable.AndroidManifestResourceOverlay_targetPackage, 256) - validateTagAttr(tag, "targetName", - R.styleable.AndroidManifestResourceOverlay_targetName, 1024) + validateTagAttr( + tag, + "requiredSystemPropertyName", + R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName, + 1024 + ) + validateTagAttr( + tag, + "requiredSystemPropertyValue", + R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue, + PROP_VALUE_MAX + ) + validateTagAttr( + tag, + "targetPackage", + R.styleable.AndroidManifestResourceOverlay_targetPackage, + 256 + ) + validateTagAttr( + tag, + "targetName", + R.styleable.AndroidManifestResourceOverlay_targetName, + 1024 + ) } @Test fun parseInstrumentationTag() { val tag = "instrumentation" validateTagAttr(tag, "name", R.styleable.AndroidManifestInstrumentation_name, 1024) - validateTagAttrComponentName(tag, "name", - R.styleable.AndroidManifestInstrumentation_name) - validateTagAttr(tag, "targetPackage", - R.styleable.AndroidManifestInstrumentation_targetPackage, 256) - validateTagAttr(tag, "targetProcesses", - R.styleable.AndroidManifestInstrumentation_targetProcesses, 1024) + validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestInstrumentation_name) + validateTagAttr( + tag, + "targetPackage", + R.styleable.AndroidManifestInstrumentation_targetPackage, + 256 + ) + validateTagAttr( + tag, + "targetProcesses", + R.styleable.AndroidManifestInstrumentation_targetProcesses, + 1024 + ) } @Test @@ -278,10 +340,18 @@ class AndroidPackageParsingValidationTest { validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestProvider_name) validateTagAttr(tag, "permission", R.styleable.AndroidManifestProvider_permission, 1024) validateTagAttr(tag, "process", R.styleable.AndroidManifestProvider_process, 1024) - validateTagAttr(tag, "readPermission", - R.styleable.AndroidManifestProvider_readPermission, 1024) - validateTagAttr(tag, "writePermission", - R.styleable.AndroidManifestProvider_writePermission, 1024) + validateTagAttr( + tag, + "readPermission", + R.styleable.AndroidManifestProvider_readPermission, + 1024 + ) + validateTagAttr( + tag, + "writePermission", + R.styleable.AndroidManifestProvider_writePermission, + 1024 + ) validateTagCount("grant-uri-permission", 100, tag) validateTagCount("path-permission", 100, tag) validateTagCount("meta-data", 1000, tag) @@ -292,26 +362,54 @@ class AndroidPackageParsingValidationTest { fun parseGrantUriPermissionTag() { val tag = "grant-uri-permission" validateTagAttr(tag, "path", R.styleable.AndroidManifestGrantUriPermission_path, 4000) - validateTagAttr(tag, "pathPrefix", - R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 4000) - validateTagAttr(tag, "pathPattern", - R.styleable.AndroidManifestGrantUriPermission_pathPattern, 4000) + validateTagAttr( + tag, + "pathPrefix", + R.styleable.AndroidManifestGrantUriPermission_pathPrefix, + 4000 + ) + validateTagAttr( + tag, + "pathPattern", + R.styleable.AndroidManifestGrantUriPermission_pathPattern, + 4000 + ) } @Test fun parsePathPermissionTag() { val tag = "path-permission" validateTagAttr(tag, "path", R.styleable.AndroidManifestPathPermission_path, 4000) - validateTagAttr(tag, "pathPrefix", - R.styleable.AndroidManifestPathPermission_pathPrefix, 4000) - validateTagAttr(tag, "pathPattern", - R.styleable.AndroidManifestPathPermission_pathPattern, 4000) - validateTagAttr(tag, "permission", - R.styleable.AndroidManifestPathPermission_permission, 1024) - validateTagAttr(tag, "readPermission", - R.styleable.AndroidManifestPathPermission_readPermission, 1024) - validateTagAttr(tag, "writePermission", - R.styleable.AndroidManifestPathPermission_writePermission, 1024) + validateTagAttr( + tag, + "pathPrefix", + R.styleable.AndroidManifestPathPermission_pathPrefix, + 4000 + ) + validateTagAttr( + tag, + "pathPattern", + R.styleable.AndroidManifestPathPermission_pathPattern, + 4000 + ) + validateTagAttr( + tag, + "permission", + R.styleable.AndroidManifestPathPermission_permission, + 1024 + ) + validateTagAttr( + tag, + "readPermission", + R.styleable.AndroidManifestPathPermission_readPermission, + 1024 + ) + validateTagAttr( + tag, + "writePermission", + R.styleable.AndroidManifestPathPermission_writePermission, + 1024 + ) } @Test @@ -350,8 +448,12 @@ class AndroidPackageParsingValidationTest { validateTagAttr(tag, "pathPattern", R.styleable.AndroidManifestData_pathPattern, 4000) validateTagAttr(tag, "pathPrefix", R.styleable.AndroidManifestData_pathPrefix, 4000) validateTagAttr(tag, "pathSuffix", R.styleable.AndroidManifestData_pathSuffix, 4000) - validateTagAttr(tag, "pathAdvancedPattern", - R.styleable.AndroidManifestData_pathAdvancedPattern, 4000) + validateTagAttr( + tag, + "pathAdvancedPattern", + R.styleable.AndroidManifestData_pathAdvancedPattern, + 4000 + ) validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512) } @@ -365,8 +467,12 @@ class AndroidPackageParsingValidationTest { fun parsePermissionTag() { val tag = "permission" validateTagAttr(tag, "name", R.styleable.AndroidManifestPermission_name, 1024) - validateTagAttr(tag, "permissionGroup", - R.styleable.AndroidManifestPermission_permissionGroup, 256) + validateTagAttr( + tag, + "permissionGroup", + R.styleable.AndroidManifestPermission_permissionGroup, + 256 + ) } @Test @@ -386,14 +492,18 @@ class AndroidPackageParsingValidationTest { try { validator.validateStrAttr(pullParser, attr, name) } catch (e: SecurityException) { - fail("Failed to parse attribute $attr in <$tag> as valid Java class name:" + - " ${e.message}") + fail( + "Failed to parse attribute $attr in <$tag> as valid Java class name:" + + " ${e.message}" + ) } try { validator.validateResStrAttr(pullParser, index, name) } catch (e: SecurityException) { - fail("Failed to parse attribute $attr in <$tag> as valid Java class name:" + - " ${e.message}") + fail( + "Failed to parse attribute $attr in <$tag> as valid Java class name:" + + " ${e.message}" + ) } } @@ -404,13 +514,17 @@ class AndroidPackageParsingValidationTest { val validator = Validator() pullParser.nextTag() validator.validate(pullParser) - val e1 = assertThrows("$name is not valid Java class name", - SecurityException::class.java) { + val e1 = assertThrows( + "$name is not valid Java class name", + SecurityException::class.java + ) { validator.validateStrAttr(pullParser, attr, name) } assertEquals(expectedAttrComponentNameErrorMsg(name), e1.message) - val e2 = assertThrows("$name is not valid Java class name", - SecurityException::class.java) { + val e2 = assertThrows( + "$name is not valid Java class name", + SecurityException::class.java + ) { validator.validateResStrAttr(pullParser, index, name) } assertEquals(expectedAttrComponentNameErrorMsg(name), e2.message) @@ -437,15 +551,19 @@ class AndroidPackageParsingValidationTest { try { validator.validateStrAttr(pullParser, name, value) } catch (e: SecurityException) { - fail("Failed to parse valid <$tag> attribute $name with max length of $maxLen:" + - " ${e.message}") + fail( + "Failed to parse valid <$tag> attribute $name with max length of $maxLen:" + + " ${e.message}" + ) } if (index != null) { try { validator.validateResStrAttr(pullParser, index, value) } catch (e: SecurityException) { - fail("Failed to parse valid <$tag> resource string attribute $name with max" + - " length of $maxLen: ${e.message}") + fail( + "Failed to parse valid <$tag> resource string attribute $name with max" + + " length of $maxLen: ${e.message}" + ) } } } @@ -485,8 +603,10 @@ class AndroidPackageParsingValidationTest { try { parseXmlStr(xml) } catch (e: SecurityException) { - fail("Failed to parse <$tag> with max count limit of $maxNum under" + - " <$parentTag>: ${e.message}") + fail( + "Failed to parse <$tag> with max count limit of $maxNum under" + + " <$parentTag>: ${e.message}" + ) } } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 54995fb5bd84..edc5df20231a 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -1022,9 +1022,12 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100) .compose(); CombinedVibration effect = CombinedVibration.createParallel(composed); - long vibrationId = startThreadAndDispatcher(effect); - + // We create the HalVibration here to obtain the vibration id and use it to mock the + // required response when calling triggerSyncedVibration. + HalVibration halVibration = createVibration(effect); + long vibrationId = halVibration.id; when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); + startThreadAndDispatcher(halVibration); assertTrue(waitUntil( () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty() @@ -1056,7 +1059,6 @@ public class VibrationThreadTest { mVibratorProviders.get(4).setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK); when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); - when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true); VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) @@ -1067,7 +1069,12 @@ public class VibrationThreadTest { .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1)) .addVibrator(4, composed) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + // We create the HalVibration here to obtain the vibration id and use it to mock the + // required response when calling triggerSyncedVibration. + HalVibration halVibration = createVibration(effect); + long vibrationId = halVibration.id; + when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); + startThreadAndDispatcher(halVibration); waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC @@ -1117,13 +1124,17 @@ public class VibrationThreadTest { mockVibrators(vibratorIds); mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK); when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); - when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false); CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(10, 100)) .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + // We create the HalVibration here to obtain the vibration id and use it to mock the + // required response when calling triggerSyncedVibration. + HalVibration halVibration = createVibration(effect); + long vibrationId = halVibration.id; + when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(false); + startThreadAndDispatcher(halVibration); waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC @@ -1492,7 +1503,6 @@ public class VibrationThreadTest { assertTrue(fakeVibrator.getAmplitudes().isEmpty()); } - @FlakyTest @Test public void vibrate_multipleVibrations_withCancel() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects( diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java index 1c6408bd6da7..56c3ec0ffe2d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java @@ -50,7 +50,6 @@ import android.platform.test.annotations.Presubmit; import android.util.Log; import android.view.IWindowManager; -import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import com.android.server.am.AssistDataRequester; @@ -154,7 +153,6 @@ public class AssistDataRequesterTest { .noteOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString(), any(), any()); } - @FlakyTest(bugId = 280107567) @Test public void testRequestData() throws Exception { setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, @@ -262,7 +260,6 @@ public class AssistDataRequesterTest { assertReceivedDataCount(0, 1, 0, 1); } - @FlakyTest(bugId = 280107567) @Test public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception { setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, @@ -274,7 +271,6 @@ public class AssistDataRequesterTest { assertReceivedDataCount(5, 5, 0, 0); } - @FlakyTest(bugId = 280107567) @Test public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception { setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, @@ -321,7 +317,8 @@ public class AssistDataRequesterTest { assertEquals("Expected " + numPendingScreenshots + " pending screenshots, got " + mDataRequester.getPendingScreenshotCount(), numPendingScreenshots, mDataRequester.getPendingScreenshotCount()); - assertFalse("Expected request NOT completed", mCallbacks.mRequestCompleted); + assertEquals("Expected request NOT completed, unless no pending data", + numPendingData == 0 && numPendingScreenshots == 0, mCallbacks.mRequestCompleted); mGate.countDown(); waitForIdle(mHandler); assertEquals("Expected " + numReceivedData + " data, received " @@ -380,14 +377,7 @@ public class AssistDataRequesterTest { @Override public void onAssistRequestCompleted() { - mHandler.post(() -> { - try { - mGate.await(10, TimeUnit.SECONDS); - mRequestCompleted = true; - } catch (InterruptedException e) { - Log.e(TAG, "Failed to wait", e); - } - }); + mRequestCompleted = true; } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java index 8bc4cedf6fce..db08eabac699 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; import android.view.InputChannel; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.Before; @@ -72,6 +73,7 @@ public class TaskPositioningControllerTests extends WindowTestsBase { doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor(); } + @FlakyTest(bugId = 291067614) @Test public void testStartAndFinishPositioning() { assertFalse(mTarget.isPositioningLocked()); |