diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index e080a80e1c372..17a0d62c27b36 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -247,6 +247,10 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSendBroadcast(pw); case "compact": return runCompact(pw); + case "freeze": + return runFreeze(pw); + case "unfreeze": + return runUnfreeze(pw); case "instrument": getOutPrintWriter().println("Error: must be invoked through 'am instrument'."); return -1; @@ -1074,20 +1078,10 @@ final class ActivityManagerShellCommand extends ShellCommand { boolean isFullCompact = op.equals("full"); boolean isSomeCompact = op.equals("some"); if (isFullCompact || isSomeCompact) { - String processName = getNextArgRequired(); - synchronized (mInternal.mProcLock) { - // Default to current user - int userId = mInterface.getCurrentUserId(); - String userOpt = getNextOption(); - if (userOpt != null && "--user".equals(userOpt)) { - int inputUserId = UserHandle.parseUserArg(getNextArgRequired()); - if (inputUserId != UserHandle.USER_CURRENT) { - userId = inputUserId; - } - } - final int uid = - mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId); - app = mInternal.getProcessRecordLocked(processName, uid); + app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; } pw.println("Process record found pid: " + app.mPid); if (isFullCompact) { @@ -1143,6 +1137,93 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + @NeverCompile + int runFreeze(PrintWriter pw) throws RemoteException { + String freezerOpt = getNextOption(); + boolean isSticky = false; + if (freezerOpt != null) { + isSticky = freezerOpt.equals("--sticky"); + } + ProcessRecord app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; + } + pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky); + synchronized (mInternal) { + synchronized (mInternal.mProcLock) { + app.mOptRecord.setFreezeSticky(isSticky); + mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(app, 0, true); + } + } + return 0; + } + + @NeverCompile + int runUnfreeze(PrintWriter pw) throws RemoteException { + String freezerOpt = getNextOption(); + boolean isSticky = false; + if (freezerOpt != null) { + isSticky = freezerOpt.equals("--sticky"); + } + ProcessRecord app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; + } + pw.println("Unfreezing pid: " + app.mPid); + synchronized (mInternal) { + synchronized (mInternal.mProcLock) { + synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) { + app.mOptRecord.setFreezeSticky(isSticky); + mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0, + false); + } + } + } + return 0; + } + + /** + * Parses from the shell the process name and user id if provided and provides the corresponding + * {@link ProcessRecord)} If no user is provided, it will fallback to current user. + * Example usage: {@code --user current} or {@code } + * @return process record of process, null if none found. + * @throws RemoteException + */ + @NeverCompile + ProcessRecord getProcessFromShell() throws RemoteException { + ProcessRecord app; + String processName = getNextArgRequired(); + synchronized (mInternal.mProcLock) { + // Default to current user + int userId = getUserIdFromShellOrFallback(); + final int uid = + mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId); + app = mInternal.getProcessRecordLocked(processName, uid); + } + return app; + } + + /** + * @return User id from command line provided in the form of + * {@code --user } and if the argument is not found it will fallback + * to current user. + * @throws RemoteException + */ + @NeverCompile + int getUserIdFromShellOrFallback() throws RemoteException { + int userId = mInterface.getCurrentUserId(); + String userOpt = getNextOption(); + if (userOpt != null && "--user".equals(userOpt)) { + int inputUserId = UserHandle.parseUserArg(getNextArgRequired()); + if (inputUserId != UserHandle.USER_CURRENT) { + userId = inputUserId; + } + } + return userId; + } + int runDumpHeap(PrintWriter pw) throws RemoteException { final PrintWriter err = getErrPrintWriter(); boolean managed = true; @@ -4061,6 +4142,14 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Perform a native compaction for process with ."); pw.println(" some: execute file compaction."); pw.println(" full: execute anon + file compaction."); + pw.println(" freeze [--sticky] [--user ]"); + pw.println(" Freeze a process."); + pw.println(" --sticky: persists the frozen state for the process lifetime or"); + pw.println(" until an unfreeze is triggered via shell"); + pw.println(" unfreeze [--sticky] [--user ]"); + pw.println(" Unfreeze a process."); + pw.println(" --sticky: persists the unfrozen state for the process lifetime or"); + pw.println(" until a freeze is triggered via shell"); pw.println(" instrument [-r] [-e ] [-p ] [-w]"); pw.println(" [--user | current]"); pw.println(" [--no-hidden-api-checks [--no-test-api-access]]"); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 8ad76cb668bf6..1426cfd652866 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -342,7 +342,7 @@ public final class CachedAppOptimizer { private final ActivityManagerGlobalLock mProcLock; - private final Object mFreezerLock = new Object(); + public final Object mFreezerLock = new Object(); private final OnPropertiesChangedListener mOnFlagsChangedListener = new OnPropertiesChangedListener() { @@ -763,8 +763,9 @@ public final class CachedAppOptimizer { pw.println(" Apps frozen: " + size); for (int i = 0; i < size; i++) { ProcessRecord app = mFrozenProcesses.valueAt(i); - pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime() - + ": " + app.getPid() + " " + app.processName); + pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime() + ": " + app.getPid() + + " " + app.processName + + (app.mOptRecord.isFreezeSticky() ? " (sticky)" : "")); } if (!mPendingCompactionProcesses.isEmpty()) { @@ -1283,12 +1284,26 @@ public final class CachedAppOptimizer { @GuardedBy({"mAm", "mProcLock"}) void freezeAppAsyncLSP(ProcessRecord app) { + freezeAppAsyncInternalLSP(app, mFreezerDebounceTimeout, false); + } + + @GuardedBy({"mAm", "mProcLock"}) + void freezeAppAsyncInternalLSP(ProcessRecord app, long delayMillis, boolean force) { final ProcessCachedOptimizerRecord opt = app.mOptRecord; if (opt.isPendingFreeze()) { // Skip redundant DO_FREEZE message return; } + if (opt.isFreezeSticky() && !force) { + if (DEBUG_FREEZER) { + Slog.d(TAG_AM, + "Skip freezing because unfrozen state is sticky pid=" + app.getPid() + " " + + app.processName); + } + return; + } + if (mAm.mConstants.USE_MODERN_TRIM && app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) { final IApplicationThread thread = app.getThread(); @@ -1301,9 +1316,8 @@ public final class CachedAppOptimizer { } } mFreezeHandler.sendMessageDelayed( - mFreezeHandler.obtainMessage( - SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), - mFreezerDebounceTimeout); + mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), + delayMillis); opt.setPendingFreeze(true); if (DEBUG_FREEZER) { Slog.d(TAG_AM, "Async freezing " + app.getPid() + " " + app.processName); @@ -1311,9 +1325,19 @@ public final class CachedAppOptimizer { } @GuardedBy({"mAm", "mProcLock", "mFreezerLock"}) - void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason) { + void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) { final int pid = app.getPid(); final ProcessCachedOptimizerRecord opt = app.mOptRecord; + boolean sticky = opt.isFreezeSticky(); + if (sticky && !force) { + // Sticky freezes will not change their state unless forced out of it. + if (DEBUG_FREEZER) { + Slog.d(TAG_AM, + "Skip unfreezing because frozen state is sticky pid=" + pid + " " + + app.processName); + } + return; + } if (opt.isPendingFreeze()) { // Remove pending DO_FREEZE message mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app); @@ -1406,7 +1430,7 @@ public final class CachedAppOptimizer { @GuardedBy({"mAm", "mProcLock"}) void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) { synchronized (mFreezerLock) { - unfreezeAppInternalLSP(app, reason); + unfreezeAppInternalLSP(app, reason, false); } } @@ -2080,15 +2104,6 @@ public final class CachedAppOptimizer { synchronized (mProcLock) { pid = proc.getPid(); - if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ - || opt.shouldNotFreeze()) { - if (DEBUG_FREEZER) { - Slog.d(TAG_AM, "Skipping freeze for process " + pid - + " " + name + " curAdj = " + proc.mState.getCurAdj() - + ", shouldNotFreeze = " + opt.shouldNotFreeze()); - } - return; - } if (mFreezerOverride) { opt.setFreezerOverride(true); diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index f2331072ce51b..e8c8f6dd5462e 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -74,6 +74,15 @@ final class ProcessCachedOptimizerRecord { @GuardedBy("mProcLock") private boolean mFrozen; + /** + * If set to true it will make the (un)freeze decision sticky which means that the freezer + * decision will remain the same unless a freeze is forced via {@link #mForceFreezeOps}. + * This property is usually set to true when external user wants to maintain a (un)frozen state + * after being applied. + */ + @GuardedBy("mProcLock") + private boolean mFreezeSticky; + /** * Set to false after the process has been frozen. * Set to true after we have collected PSS for the frozen process. @@ -195,6 +204,15 @@ final class ProcessCachedOptimizerRecord { void setFrozen(boolean frozen) { mFrozen = frozen; } + @GuardedBy("mProcLock") + void setFreezeSticky(boolean sticky) { + mFreezeSticky = sticky; + } + + @GuardedBy("mProcLock") + boolean isFreezeSticky() { + return mFreezeSticky; + } boolean skipPSSCollectionBecauseFrozen() { boolean collected = mHasCollectedFrozenPSS;