Merge "Move logic for handling uid observers outside AMS - part2"

This commit is contained in:
Sudheer Shanka
2020-10-15 18:46:36 +00:00
committed by Android (Google) Code Review
8 changed files with 629 additions and 321 deletions

View File

@@ -16,7 +16,12 @@
package com.android.server.am;
import android.app.ActivityManager;
import android.os.UserHandle;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
/** Class for tracking active uids for running processes. */
final class ActiveUids {
@@ -71,4 +76,43 @@ final class ActiveUids {
int indexOfKey(int uid) {
return mActiveUids.indexOfKey(uid);
}
boolean dump(PrintWriter pw, String dumpPackage, int dumpAppId,
String header, boolean needSep) {
boolean printed = false;
for (int i = 0; i < mActiveUids.size(); i++) {
final UidRecord uidRec = mActiveUids.valueAt(i);
if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
continue;
}
if (!printed) {
printed = true;
if (needSep) {
pw.println();
}
pw.print(" "); pw.println(header);
}
pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid);
pw.print(": "); pw.println(uidRec);
pw.print(" curProcState="); pw.print(uidRec.mCurProcState);
pw.print(" curCapability=");
ActivityManager.printCapabilitiesFull(pw, uidRec.curCapability);
pw.println();
for (int j = uidRec.procRecords.size() - 1; j >= 0; j--) {
pw.print(" proc=");
pw.println(uidRec.procRecords.valueAt(j));
}
}
return printed;
}
void dumpProto(ProtoOutputStream proto, String dumpPackage, int dumpAppId, long fieldId) {
for (int i = 0; i < mActiveUids.size(); i++) {
UidRecord uidRec = mActiveUids.valueAt(i);
if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
continue;
}
uidRec.dumpDebug(proto, fieldId);
}
}
}

View File

@@ -1979,7 +1979,6 @@ public class ActivityManagerService extends IActivityManager.Stub
mConstants = hasHandlerThread
? new ActivityManagerConstants(mContext, this, mHandler) : null;
final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
mUidObserverController = new UidObserverController(this);
mPlatformCompat = null;
mProcessList = injector.getProcessList(this);
mProcessList.init(this, activeUids, mPlatformCompat);
@@ -1997,6 +1996,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mServices = hasHandlerThread ? new ActiveServices(this) : null;
mSystemThread = null;
mUiHandler = injector.getUiHandler(null /* service */);
mUidObserverController = new UidObserverController(mUiHandler);
mUserController = hasHandlerThread ? new UserController(this) : null;
mPendingIntentController = hasHandlerThread
? new PendingIntentController(handlerThread.getLooper(), mUserController,
@@ -2077,7 +2077,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mCpHelper = new ContentProviderHelper(this, true);
mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
mUidObserverController = new UidObserverController(this);
mUidObserverController = new UidObserverController(mUiHandler);
final File systemDir = SystemServiceManager.ensureSystemDir();
@@ -8816,37 +8816,6 @@ public class ActivityManagerService extends IActivityManager.Stub
return -1;
}
boolean dumpUids(PrintWriter pw, String dumpPackage, int dumpAppId, ActiveUids uids,
String header, boolean needSep) {
boolean printed = false;
for (int i=0; i<uids.size(); i++) {
UidRecord uidRec = uids.valueAt(i);
if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
continue;
}
if (!printed) {
printed = true;
if (needSep) {
pw.println();
}
pw.print(" ");
pw.println(header);
needSep = true;
}
pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid);
pw.print(": "); pw.println(uidRec);
pw.print(" curProcState="); pw.print(uidRec.mCurProcState);
pw.print(" curCapability=");
ActivityManager.printCapabilitiesFull(pw, uidRec.curCapability);
pw.println();
for (int j = uidRec.procRecords.size() - 1; j >= 0; j--) {
pw.print(" proc=");
pw.println(uidRec.procRecords.valueAt(j));
}
}
return printed;
}
void dumpBinderProxyInterfaceCounts(PrintWriter pw, String header) {
final BinderProxy.InterfaceCount[] proxyCounts = BinderProxy.getSortedInterfaceCounts(50);
@@ -9096,19 +9065,13 @@ public class ActivityManagerService extends IActivityManager.Stub
needSep = dumpProcessesToGc(pw, needSep, dumpPackage);
if (mProcessList.mActiveUids.size() > 0) {
if (dumpUids(pw, dumpPackage, dumpAppId, mProcessList.mActiveUids,
"UID states:", needSep)) {
needSep = true;
}
needSep |= mProcessList.mActiveUids.dump(pw, dumpPackage, dumpAppId,
"UID states:", needSep);
}
if (dumpAll) {
if (mUidObserverController.mValidateUids.size() > 0) {
if (dumpUids(pw, dumpPackage, dumpAppId, mUidObserverController.mValidateUids,
"UID validation:", needSep)) {
needSep = true;
}
}
needSep |= mUidObserverController.dumpValidateUids(pw,
dumpPackage, dumpAppId, "UID validation:", needSep);
}
if (needSep) {
@@ -9377,22 +9340,12 @@ public class ActivityManagerService extends IActivityManager.Stub
ActivityManagerServiceDumpProcessesProto.ACTIVE_INSTRUMENTATIONS);
}
int whichAppId = getAppId(dumpPackage);
for (int i = 0; i < mProcessList.mActiveUids.size(); i++) {
UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
continue;
}
uidRec.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
}
final int dumpAppId = getAppId(dumpPackage);
mProcessList.mActiveUids.dumpProto(proto, dumpPackage, dumpAppId,
ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
for (int i = 0; i < mUidObserverController.mValidateUids.size(); i++) {
UidRecord uidRec = mUidObserverController.mValidateUids.valueAt(i);
if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
continue;
}
uidRec.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.VALIDATE_UIDS);
}
mUidObserverController.dumpValidateUidsProto(proto, dumpPackage, dumpAppId,
ActivityManagerServiceDumpProcessesProto.VALIDATE_UIDS);
if (mProcessList.getLruSizeLocked() > 0) {
long lruToken = proto.start(ActivityManagerServiceDumpProcessesProto.LRU_PROCS);
@@ -14892,6 +14845,58 @@ public class ActivityManagerService extends IActivityManager.Stub
return false;
}
private boolean isEphemeralLocked(int uid) {
final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
return false;
}
return getPackageManagerInternalLocked().isPackageEphemeral(
UserHandle.getUserId(uid), packages[0]);
}
void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
uid = uidRec != null ? uidRec.uid : uid;
if (uid < 0) {
throw new IllegalArgumentException("No UidRecord or uid");
}
final int procState = uidRec != null
? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
final long procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
final int capability = uidRec != null ? uidRec.setCapability : 0;
final boolean ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
if (uidRec != null && !uidRec.idle && (change & UidRecord.CHANGE_GONE) != 0) {
// If this uid is going away, and we haven't yet reported it is gone,
// then do so now.
change |= UidRecord.CHANGE_IDLE;
}
final int enqueuedChange = mUidObserverController.enqueueUidChange(
uid, change, procState, procStateSeq, capability, ephemeral);
if (uidRec != null) {
uidRec.lastReportedChange = enqueuedChange;
uidRec.updateLastDispatchedProcStateSeq(enqueuedChange);
}
// Directly update the power manager, since we sit on top of it and it is critical
// it be kept in sync (so wake locks will be held as soon as appropriate).
if (mLocalPowerManager != null) {
// TODO: dispatch cached/uncached changes here, so we don't need to report
// all proc state changes.
if ((enqueuedChange & UidRecord.CHANGE_ACTIVE) != 0) {
mLocalPowerManager.uidActive(uid);
}
if ((enqueuedChange & UidRecord.CHANGE_IDLE) != 0) {
mLocalPowerManager.uidIdle(uid);
}
if ((enqueuedChange & UidRecord.CHANGE_GONE) != 0) {
mLocalPowerManager.uidGone(uid);
} else {
mLocalPowerManager.updateUidProcState(uid, procState);
}
}
}
final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
synchronized (mProcessStats.mLock) {
if (proc.thread != null && proc.baseProcessTracker != null) {
@@ -15157,7 +15162,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("this")
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
mUidObserverController.enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
}
/**
@@ -16707,7 +16712,7 @@ public class ActivityManagerService extends IActivityManager.Stub
+ totalTime + ". Uid: " + callingUid + " procStateSeq: "
+ procStateSeq + " UidRec: " + record
+ " validateUidRec: "
+ mUidObserverController.mValidateUids.get(callingUid));
+ mUidObserverController.getValidateUidRecord(callingUid));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();

View File

@@ -1147,7 +1147,7 @@ public final class OomAdjuster {
uidRec.setWhitelist = uidRec.curWhitelist;
uidRec.setIdle = uidRec.idle;
mService.mAtmInternal.onUidProcStateChanged(uidRec.uid, uidRec.setProcState);
mService.mUidObserverController.enqueueUidChangeLocked(uidRec, -1, uidChange);
mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState(),
uidRec.curCapability);
if (uidRec.foregroundServices) {

View File

@@ -2948,7 +2948,7 @@ public final class ProcessList {
// No more processes using this uid, tell clients it is gone.
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"No more processes in " + uidRecord);
mService.mUidObserverController.enqueueUidChangeLocked(uidRecord, -1,
mService.enqueueUidChangeLocked(uidRecord, -1,
UidRecord.CHANGE_GONE);
EventLogTags.writeAmUidStopped(uid);
mActiveUids.remove(uid);

View File

@@ -23,11 +23,13 @@ import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
import android.app.ActivityManager;
import android.app.ActivityManagerProto;
import android.app.IUidObserver;
import android.os.Handler;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
@@ -40,158 +42,150 @@ import java.io.PrintWriter;
import java.util.ArrayList;
public class UidObserverController {
private final ActivityManagerService mService;
final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
UidRecord.ChangeItem[] mActiveUidChanges = new UidRecord.ChangeItem[5];
final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
/** Total # of UID change events dispatched, shown in dumpsys. */
int mUidChangeDispatchCount;
/** If a UID observer takes more than this long, send a WTF. */
private static final int SLOW_UID_OBSERVER_THRESHOLD_MS = 20;
private final Handler mHandler;
private final Object mLock = new Object();
@GuardedBy("mLock")
final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
@GuardedBy("mLock")
private final SparseArray<ChangeRecord> mPendingUidChanges = new SparseArray<>();
@GuardedBy("mLock")
private final ArrayList<ChangeRecord> mAvailUidChanges = new ArrayList<>();
private ChangeRecord[] mActiveUidChanges = new ChangeRecord[5];
/** Total # of UID change events dispatched, shown in dumpsys. */
@GuardedBy("mLock")
private int mUidChangeDispatchCount;
private final Runnable mDispatchRunnable = this::dispatchUidsChanged;
/**
* This is for verifying the UID report flow.
*/
static final boolean VALIDATE_UID_STATES = true;
final ActiveUids mValidateUids;
private static final boolean VALIDATE_UID_STATES = true;
private final ActiveUids mValidateUids;
UidObserverController(ActivityManagerService service) {
mService = service;
mValidateUids = new ActiveUids(mService, false /* postChangesToAtm */);
UidObserverController(Handler handler) {
mHandler = handler;
mValidateUids = new ActiveUids(null /* service */, false /* postChangesToAtm */);
}
@GuardedBy("mService")
void register(IUidObserver observer, int which, int cutpoint, String callingPackage,
int callingUid) {
mUidObservers.register(observer, new UidObserverRegistration(callingUid,
callingPackage, which, cutpoint));
synchronized (mLock) {
mUidObservers.register(observer, new UidObserverRegistration(callingUid,
callingPackage, which, cutpoint));
}
}
@GuardedBy("mService")
void unregister(IUidObserver observer) {
mUidObservers.unregister(observer);
synchronized (mLock) {
mUidObservers.unregister(observer);
}
}
@GuardedBy("mService")
final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
final UidRecord.ChangeItem pendingChange;
if (uidRec == null || uidRec.pendingChange == null) {
int enqueueUidChange(int uid, int change, int procState, long procStateSeq,
int capability, boolean ephemeral) {
synchronized (mLock) {
if (mPendingUidChanges.size() == 0) {
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "*** Enqueueing dispatch uid changed!");
}
mService.mUiHandler.post(this::dispatchUidsChanged);
mHandler.post(mDispatchRunnable);
}
final int size = mAvailUidChanges.size();
if (size > 0) {
pendingChange = mAvailUidChanges.remove(size - 1);
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "Retrieving available item: " + pendingChange);
}
} else {
pendingChange = new UidRecord.ChangeItem();
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "Allocating new item: " + pendingChange);
}
}
if (uidRec != null) {
uidRec.pendingChange = pendingChange;
if ((change & UidRecord.CHANGE_GONE) != 0 && !uidRec.idle) {
// If this uid is going away, and we haven't yet reported it is gone,
// then do so now.
change |= UidRecord.CHANGE_IDLE;
}
} else if (uid < 0) {
throw new IllegalArgumentException("No UidRecord or uid");
}
pendingChange.uidRecord = uidRec;
pendingChange.uid = uidRec != null ? uidRec.uid : uid;
mPendingUidChanges.add(pendingChange);
} else {
pendingChange = uidRec.pendingChange;
// If there is no change in idle or active state, then keep whatever was pending.
if ((change & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
change |= (pendingChange.change & (UidRecord.CHANGE_IDLE
| UidRecord.CHANGE_ACTIVE));
}
// If there is no change in cached or uncached state, then keep whatever was pending.
if ((change & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
change |= (pendingChange.change & (UidRecord.CHANGE_CACHED
| UidRecord.CHANGE_UNCACHED));
}
// If this is a report of the UID being gone, then we shouldn't keep any previous
// report of it being active or cached. (That is, a gone uid is never active,
// and never cached.)
if ((change & UidRecord.CHANGE_GONE) != 0) {
change &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
if (!uidRec.idle) {
// If this uid is going away, and we haven't yet reported it is gone,
// then do so now.
change |= UidRecord.CHANGE_IDLE;
}
}
}
pendingChange.change = change;
pendingChange.processState = uidRec != null
? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
pendingChange.capability = uidRec != null ? uidRec.setCapability : 0;
pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
if (uidRec != null) {
uidRec.lastReportedChange = change;
uidRec.updateLastDispatchedProcStateSeq(change);
}
// Directly update the power manager, since we sit on top of it and it is critical
// it be kept in sync (so wake locks will be held as soon as appropriate).
if (mService.mLocalPowerManager != null) {
// TO DO: dispatch cached/uncached changes here, so we don't need to report
// all proc state changes.
if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
mService.mLocalPowerManager.uidActive(pendingChange.uid);
}
if ((change & UidRecord.CHANGE_IDLE) != 0) {
mService.mLocalPowerManager.uidIdle(pendingChange.uid);
}
if ((change & UidRecord.CHANGE_GONE) != 0) {
mService.mLocalPowerManager.uidGone(pendingChange.uid);
ChangeRecord changeRecord = mPendingUidChanges.get(uid);
if (changeRecord == null) {
changeRecord = getOrCreateChangeRecordLocked();
mPendingUidChanges.put(uid, changeRecord);
} else {
mService.mLocalPowerManager.updateUidProcState(pendingChange.uid,
pendingChange.processState);
change = mergeWithPendingChange(change, changeRecord.change);
}
changeRecord.uid = uid;
changeRecord.change = change;
changeRecord.procState = procState;
changeRecord.procStateSeq = procStateSeq;
changeRecord.capability = capability;
changeRecord.ephemeral = ephemeral;
return changeRecord.change;
}
}
SparseArray<ChangeRecord> getPendingUidChangesForTest() {
return mPendingUidChanges;
}
ActiveUids getValidateUidsForTest() {
return mValidateUids;
}
@VisibleForTesting
static int mergeWithPendingChange(int currentChange, int pendingChange) {
// If there is no change in idle or active state, then keep whatever was pending.
if ((currentChange & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
currentChange |= (pendingChange & (UidRecord.CHANGE_IDLE
| UidRecord.CHANGE_ACTIVE));
}
// If there is no change in cached or uncached state, then keep whatever was pending.
if ((currentChange & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
currentChange |= (pendingChange & (UidRecord.CHANGE_CACHED
| UidRecord.CHANGE_UNCACHED));
}
// If this is a report of the UID being gone, then we shouldn't keep any previous
// report of it being active or cached. (That is, a gone uid is never active,
// and never cached.)
if ((currentChange & UidRecord.CHANGE_GONE) != 0) {
currentChange &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
}
return currentChange;
}
@GuardedBy("mLock")
private ChangeRecord getOrCreateChangeRecordLocked() {
final ChangeRecord changeRecord;
final int size = mAvailUidChanges.size();
if (size > 0) {
changeRecord = mAvailUidChanges.remove(size - 1);
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "Retrieving available item: " + changeRecord);
}
} else {
changeRecord = new ChangeRecord();
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "Allocating new item: " + changeRecord);
}
}
return changeRecord;
}
@VisibleForTesting
void dispatchUidsChanged() {
int numUidChanges;
synchronized (mService) {
final int numUidChanges;
synchronized (mLock) {
numUidChanges = mPendingUidChanges.size();
if (mActiveUidChanges.length < numUidChanges) {
mActiveUidChanges = new UidRecord.ChangeItem[numUidChanges];
mActiveUidChanges = new ChangeRecord[numUidChanges];
}
for (int i = 0; i < numUidChanges; i++) {
final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
mActiveUidChanges[i] = change;
if (change.uidRecord != null) {
change.uidRecord.pendingChange = null;
change.uidRecord = null;
}
mActiveUidChanges[i] = mPendingUidChanges.valueAt(i);
}
mPendingUidChanges.clear();
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "*** Delivering " + numUidChanges + " uid changes");
}
mUidChangeDispatchCount += numUidChanges;
}
mUidChangeDispatchCount += numUidChanges;
int i = mUidObservers.beginBroadcast();
while (i > 0) {
i--;
while (i-- > 0) {
dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i),
(UidObserverRegistration) mUidObservers.getBroadcastCookie(i), numUidChanges);
}
@@ -199,7 +193,7 @@ public class UidObserverController {
if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
for (int j = 0; j < numUidChanges; ++j) {
final UidRecord.ChangeItem item = mActiveUidChanges[j];
final ChangeRecord item = mActiveUidChanges[j];
if ((item.change & UidRecord.CHANGE_GONE) != 0) {
mValidateUids.remove(item.uid);
} else {
@@ -213,14 +207,14 @@ public class UidObserverController {
} else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
validateUid.idle = false;
}
validateUid.setCurProcState(validateUid.setProcState = item.processState);
validateUid.setCurProcState(validateUid.setProcState = item.procState);
validateUid.curCapability = validateUid.setCapability = item.capability;
validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
}
}
}
synchronized (mService) {
synchronized (mLock) {
for (int j = 0; j < numUidChanges; j++) {
mAvailUidChanges.add(mActiveUidChanges[j]);
}
@@ -234,7 +228,7 @@ public class UidObserverController {
}
try {
for (int j = 0; j < changesSize; j++) {
UidRecord.ChangeItem item = mActiveUidChanges[j];
final ChangeRecord item = mActiveUidChanges[j];
final int change = item.change;
if (change == UidRecord.CHANGE_PROCSTATE
&& (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
@@ -285,7 +279,7 @@ public class UidObserverController {
if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "UID CHANGED uid=" + item.uid
+ ": " + item.processState + ": " + item.capability);
+ ": " + item.procState + ": " + item.capability);
}
boolean doReport = true;
if (reg.mCutpoint >= ActivityManager.MIN_PROCESS_STATE) {
@@ -293,17 +287,17 @@ public class UidObserverController {
ActivityManager.PROCESS_STATE_UNKNOWN);
if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) {
final boolean lastAboveCut = lastState <= reg.mCutpoint;
final boolean newAboveCut = item.processState <= reg.mCutpoint;
final boolean newAboveCut = item.procState <= reg.mCutpoint;
doReport = lastAboveCut != newAboveCut;
} else {
doReport = item.processState != PROCESS_STATE_NONEXISTENT;
doReport = item.procState != PROCESS_STATE_NONEXISTENT;
}
}
if (doReport) {
if (reg.mLastProcStates != null) {
reg.mLastProcStates.put(item.uid, item.processState);
reg.mLastProcStates.put(item.uid, item.procState);
}
observer.onUidStateChanged(item.uid, item.processState,
observer.onUidStateChanged(item.uid, item.procState,
item.procStateSeq, item.capability);
}
}
@@ -320,94 +314,82 @@ public class UidObserverController {
}
}
private boolean isEphemeralLocked(int uid) {
final String[] packages = mService.mContext.getPackageManager().getPackagesForUid(uid);
if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
return false;
}
return mService.getPackageManagerInternalLocked().isPackageEphemeral(
UserHandle.getUserId(uid), packages[0]);
UidRecord getValidateUidRecord(int uid) {
return mValidateUids.get(uid);
}
@GuardedBy("mService")
void dump(PrintWriter pw, String dumpPackage) {
final int count = mUidObservers.getRegisteredCallbackCount();
boolean printed = false;
for (int i = 0; i < count; i++) {
final UidObserverRegistration reg = (UidObserverRegistration)
mUidObservers.getRegisteredCallbackCookie(i);
if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
if (!printed) {
pw.println(" mUidObservers:");
printed = true;
}
pw.print(" "); UserHandle.formatUid(pw, reg.mUid);
pw.print(" "); pw.print(reg.mPkg);
final IUidObserver observer = mUidObservers.getRegisteredCallbackItem(i);
pw.print(" "); pw.print(observer.getClass().getTypeName()); pw.print(":");
if ((reg.mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) {
pw.print(" IDLE");
}
if ((reg.mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
pw.print(" ACT");
}
if ((reg.mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) {
pw.print(" GONE");
}
if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
pw.print(" STATE");
pw.print(" (cut="); pw.print(reg.mCutpoint);
pw.print(")");
}
pw.println();
if (reg.mLastProcStates != null) {
final int size = reg.mLastProcStates.size();
for (int j = 0; j < size; j++) {
pw.print(" Last ");
UserHandle.formatUid(pw, reg.mLastProcStates.keyAt(j));
pw.print(": "); pw.println(reg.mLastProcStates.valueAt(j));
synchronized (mLock) {
final int count = mUidObservers.getRegisteredCallbackCount();
boolean printed = false;
for (int i = 0; i < count; i++) {
final UidObserverRegistration reg = (UidObserverRegistration)
mUidObservers.getRegisteredCallbackCookie(i);
if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
if (!printed) {
pw.println(" mUidObservers:");
printed = true;
}
reg.dump(pw, mUidObservers.getRegisteredCallbackItem(i));
}
}
}
pw.println();
pw.print(" mUidChangeDispatchCount=");
pw.print(mUidChangeDispatchCount);
pw.println();
pw.println(" Slow UID dispatches:");
final int size = mUidObservers.beginBroadcast();
for (int i = 0; i < size; i++) {
UidObserverRegistration r =
(UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
pw.print(" ");
pw.print(mUidObservers.getBroadcastItem(i).getClass().getTypeName());
pw.print(": ");
pw.print(r.mSlowDispatchCount);
pw.print(" / Max ");
pw.print(r.mMaxDispatchTime);
pw.println("ms");
pw.println();
pw.print(" mUidChangeDispatchCount=");
pw.print(mUidChangeDispatchCount);
pw.println();
pw.println(" Slow UID dispatches:");
for (int i = 0; i < count; i++) {
final UidObserverRegistration reg = (UidObserverRegistration)
mUidObservers.getRegisteredCallbackCookie(i);
pw.print(" ");
pw.print(mUidObservers.getRegisteredCallbackItem(i).getClass().getTypeName());
pw.print(": ");
pw.print(reg.mSlowDispatchCount);
pw.print(" / Max ");
pw.print(reg.mMaxDispatchTime);
pw.println("ms");
}
}
mUidObservers.finishBroadcast();
}
@GuardedBy("mService")
void dumpDebug(ProtoOutputStream proto, String dumpPackage) {
final int count = mUidObservers.getRegisteredCallbackCount();
for (int i = 0; i < count; i++) {
final UidObserverRegistration reg = (UidObserverRegistration)
mUidObservers.getRegisteredCallbackCookie(i);
if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
synchronized (mLock) {
final int count = mUidObservers.getRegisteredCallbackCount();
for (int i = 0; i < count; i++) {
final UidObserverRegistration reg = (UidObserverRegistration)
mUidObservers.getRegisteredCallbackCookie(i);
if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
}
}
}
}
boolean dumpValidateUids(PrintWriter pw, String dumpPackage, int dumpAppId,
String header, boolean needSep) {
return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
}
void dumpValidateUidsProto(ProtoOutputStream proto, String dumpPackage,
int dumpAppId, long fieldId) {
mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
}
static final class ChangeRecord {
public int uid;
public int change;
public int procState;
public int capability;
public boolean ephemeral;
public long procStateSeq;
}
private static final class UidObserverRegistration {
final int mUid;
final String mPkg;
final int mWhich;
final int mCutpoint;
private final int mUid;
private final String mPkg;
private final int mWhich;
private final int mCutpoint;
/**
* Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
@@ -439,10 +421,42 @@ public class UidObserverController {
this.mPkg = pkg;
this.mWhich = which;
this.mCutpoint = cutpoint;
if (cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
mLastProcStates = new SparseIntArray();
} else {
mLastProcStates = null;
mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
? new SparseIntArray() : null;
}
void dump(PrintWriter pw, IUidObserver observer) {
pw.print(" ");
UserHandle.formatUid(pw, mUid);
pw.print(" ");
pw.print(mPkg);
pw.print(" ");
pw.print(observer.getClass().getTypeName());
pw.print(":");
if ((mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) {
pw.print(" IDLE");
}
if ((mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
pw.print(" ACT");
}
if ((mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) {
pw.print(" GONE");
}
if ((mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
pw.print(" STATE");
pw.print(" (cut=");
pw.print(mCutpoint);
pw.print(")");
}
pw.println();
if (mLastProcStates != null) {
final int size = mLastProcStates.size();
for (int j = 0; j < size; j++) {
pw.print(" Last ");
UserHandle.formatUid(pw, mLastProcStates.keyAt(j));
pw.print(": ");
pw.println(mLastProcStates.valueAt(j));
}
}
}

View File

@@ -107,17 +107,6 @@ public final class UidRecord {
UidRecordProto.CHANGE_UNCACHED,
};
static final class ChangeItem {
UidRecord uidRecord;
int uid;
int change;
int processState;
int capability;
boolean ephemeral;
long procStateSeq;
}
ChangeItem pendingChange;
int lastReportedChange;
public UidRecord(int _uid) {

View File

@@ -68,6 +68,7 @@ import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.SparseArray;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
@@ -76,6 +77,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.am.UidObserverController.ChangeRecord;
import com.android.server.appop.AppOpsService;
import com.android.server.wm.ActivityTaskManagerService;
@@ -539,15 +541,15 @@ public class ActivityManagerServiceTest {
ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
ActivityManager.PROCESS_STATE_TOP
};
final Map<Integer, UidRecord.ChangeItem> changeItems = new HashMap<>();
final Map<Integer, ChangeRecord> changeItems = new HashMap<>();
for (int i = 0; i < changesForPendingUidRecords.length; ++i) {
final UidRecord.ChangeItem pendingChange = new UidRecord.ChangeItem();
final ChangeRecord pendingChange = new ChangeRecord();
pendingChange.change = changesForPendingUidRecords[i];
pendingChange.uid = i;
pendingChange.processState = procStatesForPendingUidRecords[i];
pendingChange.procState = procStatesForPendingUidRecords[i];
pendingChange.procStateSeq = i;
changeItems.put(changesForPendingUidRecords[i], pendingChange);
mAms.mUidObserverController.mPendingUidChanges.add(pendingChange);
addPendingUidChange(pendingChange);
}
mAms.mUidObserverController.dispatchUidsChanged();
@@ -602,7 +604,7 @@ public class ActivityManagerServiceTest {
verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems,
(observer, changeItem) -> {
verify(observer).onUidStateChanged(changeItem.uid,
changeItem.processState, changeItem.procStateSeq,
changeItem.procState, changeItem.procStateSeq,
ActivityManager.PROCESS_CAPABILITY_NONE);
});
}
@@ -612,14 +614,14 @@ public class ActivityManagerServiceTest {
}
private interface ObserverChangesVerifier {
void verify(IUidObserver observer, UidRecord.ChangeItem changeItem) throws RemoteException;
void verify(IUidObserver observer, ChangeRecord changeItem) throws RemoteException;
}
private void verifyObserverReceivedChanges(IUidObserver observer, int[] changesToVerify,
Map<Integer, UidRecord.ChangeItem> changeItems, ObserverChangesVerifier verifier)
Map<Integer, ChangeRecord> changeItems, ObserverChangesVerifier verifier)
throws RemoteException {
for (int change : changesToVerify) {
final UidRecord.ChangeItem changeItem = changeItems.get(change);
final ChangeRecord changeItem = changeItems.get(change);
verifier.verify(observer, changeItem);
}
}
@@ -641,59 +643,59 @@ public class ActivityManagerServiceTest {
// So, resetting the mock here.
Mockito.reset(observer);
final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem();
final ChangeRecord changeItem = new ChangeRecord();
changeItem.uid = TEST_UID;
changeItem.change = UidRecord.CHANGE_PROCSTATE;
changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
changeItem.procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
changeItem.procStateSeq = 111;
mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
addPendingUidChange(changeItem);
mAms.mUidObserverController.dispatchUidsChanged();
// First process state message is always delivered regardless of whether the process state
// change is above or below the cutpoint (PROCESS_STATE_SERVICE).
verify(observer).onUidStateChanged(TEST_UID,
changeItem.processState, changeItem.procStateSeq,
changeItem.procState, changeItem.procStateSeq,
ActivityManager.PROCESS_CAPABILITY_NONE);
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
changeItem.procState = ActivityManager.PROCESS_STATE_RECEIVER;
addPendingUidChange(changeItem);
mAms.mUidObserverController.dispatchUidsChanged();
// Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is also below cutpoint, so no callback will be invoked.
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
changeItem.procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
addPendingUidChange(changeItem);
mAms.mUidObserverController.dispatchUidsChanged();
// Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is above cutpoint, so callback will be invoked with the
// current process state change.
verify(observer).onUidStateChanged(TEST_UID,
changeItem.processState, changeItem.procStateSeq,
changeItem.procState, changeItem.procStateSeq,
ActivityManager.PROCESS_CAPABILITY_NONE);
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
changeItem.procState = ActivityManager.PROCESS_STATE_TOP;
addPendingUidChange(changeItem);
mAms.mUidObserverController.dispatchUidsChanged();
// Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is also above cutpoint, so no callback will be invoked.
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
changeItem.procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
addPendingUidChange(changeItem);
mAms.mUidObserverController.dispatchUidsChanged();
// Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is below cutpoint, so callback will be invoked with the
// current process state change.
verify(observer).onUidStateChanged(TEST_UID,
changeItem.processState, changeItem.procStateSeq,
changeItem.procState, changeItem.procStateSeq,
ActivityManager.PROCESS_CAPABILITY_NONE);
verifyNoMoreInteractions(observer);
}
/**
* This test verifies that {@link ActivityManagerService#mValidateUids} which is a
* This test verifies that {@link UidObserverController#getValidateUidsForTest()} which is a
* part of dumpsys is correctly updated.
*/
@Test
@@ -707,45 +709,45 @@ public class ActivityManagerServiceTest {
ActivityManager.PROCESS_STATE_SERVICE,
ActivityManager.PROCESS_STATE_RECEIVER
};
final ArrayList<UidRecord.ChangeItem> pendingItemsForUids =
final ArrayList<ChangeRecord> pendingItemsForUids =
new ArrayList<>(changesForPendingItems.length);
for (int i = 0; i < changesForPendingItems.length; ++i) {
final UidRecord.ChangeItem item = new UidRecord.ChangeItem();
final ChangeRecord item = new ChangeRecord();
item.uid = i;
item.change = changesForPendingItems[i];
item.processState = procStatesForPendingItems[i];
item.procState = procStatesForPendingItems[i];
pendingItemsForUids.add(i, item);
}
// Verify that when there no observers listening to uid state changes, then there will
// be no changes to validateUids.
mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
addPendingUidChanges(pendingItemsForUids);
mAms.mUidObserverController.dispatchUidsChanged();
assertEquals("No observers registered, so validateUids should be empty",
0, mAms.mUidObserverController.mValidateUids.size());
0, mAms.mUidObserverController.getValidateUidsForTest().size());
final IUidObserver observer = mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
mAms.registerUidObserver(observer, 0, 0, null);
// Verify that when observers are registered, then validateUids is correctly updated.
mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
addPendingUidChanges(pendingItemsForUids);
mAms.mUidObserverController.dispatchUidsChanged();
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
final ChangeRecord item = pendingItemsForUids.get(i);
final UidRecord validateUidRecord =
mAms.mUidObserverController.mValidateUids.get(item.uid);
mAms.mUidObserverController.getValidateUidsForTest().get(item.uid);
if ((item.change & UidRecord.CHANGE_GONE) != 0) {
assertNull("validateUidRecord should be null since the change is either "
+ "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
} else {
assertNotNull("validateUidRecord should not be null since the change is neither "
+ "CHANGE_GONE nor CHANGE_GONE_IDLE", validateUidRecord);
assertEquals("processState: " + item.processState + " curProcState: "
assertEquals("processState: " + item.procState + " curProcState: "
+ validateUidRecord.getCurProcState() + " should have been equal",
item.processState, validateUidRecord.getCurProcState());
assertEquals("processState: " + item.processState + " setProcState: "
item.procState, validateUidRecord.getCurProcState());
assertEquals("processState: " + item.procState + " setProcState: "
+ validateUidRecord.getCurProcState() + " should have been equal",
item.processState, validateUidRecord.setProcState);
item.procState, validateUidRecord.setProcState);
if (item.change == UidRecord.CHANGE_IDLE) {
assertTrue("UidRecord.idle should be updated to true for CHANGE_IDLE",
validateUidRecord.idle);
@@ -759,19 +761,19 @@ public class ActivityManagerServiceTest {
// Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
// will be removed from validateUids.
assertNotEquals("validateUids should not be empty", 0,
mAms.mUidObserverController.mValidateUids.size());
mAms.mUidObserverController.getValidateUidsForTest().size());
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
final ChangeRecord item = pendingItemsForUids.get(i);
// Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
// distribution for this assignment.
item.change = (i % 2) == 0 ? (UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE)
: UidRecord.CHANGE_GONE;
}
mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
addPendingUidChanges(pendingItemsForUids);
mAms.mUidObserverController.dispatchUidsChanged();
assertEquals("validateUids should be empty, size="
+ mAms.mUidObserverController.mValidateUids.size(),
0, mAms.mUidObserverController.mValidateUids.size());
+ mAms.mUidObserverController.getValidateUidsForTest().size(),
0, mAms.mUidObserverController.getValidateUidsForTest().size());
}
@Test
@@ -784,8 +786,6 @@ public class ActivityManagerServiceTest {
// Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as
// expected.
final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem();
uidRecord.pendingChange = changeItem;
uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
}
@@ -793,7 +793,7 @@ public class ActivityManagerServiceTest {
@Test
public void testEnqueueUidChangeLocked_nullUidRecord() {
// Use "null" uidRecord to make sure there is no crash.
mAms.mUidObserverController.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
}
private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
@@ -802,7 +802,7 @@ public class ActivityManagerServiceTest {
final int changeToDispatch = UID_RECORD_CHANGES[i];
// Reset lastProcStateSeqDispatchToObservers after every test.
uidRecord.lastDispatchedProcStateSeq = 0;
mAms.mUidObserverController.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
// Verify there is no effect on curProcStateSeq.
assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) {
@@ -833,16 +833,16 @@ public class ActivityManagerServiceTest {
// Reset the current state
mHandler.reset();
uidRecord.pendingChange = null;
mAms.mUidObserverController.mPendingUidChanges.clear();
clearPendingUidChanges();
mAms.mUidObserverController.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
// Verify that UidRecord.pendingChange is updated correctly.
assertNotNull(uidRecord.pendingChange);
assertEquals(TEST_UID, uidRecord.pendingChange.uid);
assertEquals(expectedProcState, uidRecord.pendingChange.processState);
assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
// Verify that pendingChange is updated correctly.
final ChangeRecord pendingChange = getPendingChange(uidRecord.uid);
assertNotNull(pendingChange);
assertEquals(TEST_UID, pendingChange.uid);
assertEquals(expectedProcState, pendingChange.procState);
assertEquals(TEST_PROC_STATE_SEQ1, pendingChange.procStateSeq);
// TODO: Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
}
@@ -923,6 +923,29 @@ public class ActivityManagerServiceTest {
mAms.mProcessList.mActiveUids.clear();
}
private ChangeRecord getPendingChange(int uid) {
final SparseArray<ChangeRecord> pendingChanges =
mAms.mUidObserverController.getPendingUidChangesForTest();
return pendingChanges.get(uid);
}
private void addPendingUidChange(ChangeRecord record) {
mAms.mUidObserverController.getPendingUidChangesForTest().put(record.uid, record);
}
private void addPendingUidChanges(ArrayList<ChangeRecord> changes) {
final SparseArray<ChangeRecord> pendingChanges =
mAms.mUidObserverController.getPendingUidChangesForTest();
for (int i = 0; i < changes.size(); ++i) {
final ChangeRecord record = changes.get(i);
pendingChanges.put(record.uid, record);
}
}
private void clearPendingUidChanges() {
mAms.mUidObserverController.getPendingUidChangesForTest().clear();
}
private static class TestHandler extends Handler {
private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec

View File

@@ -0,0 +1,233 @@
/*
* Copyright (C) 2020 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_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
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_IMPORTANT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
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 junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.IUidObserver;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.DebugUtils;
import android.util.Pair;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
import com.android.server.am.UidObserverController.ChangeRecord;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
@SmallTest
public class UidObserverControllerTest {
private static final int TEST_UID1 = 1111;
private static final int TEST_UID2 = 2222;
private static final int TEST_UID3 = 3333;
private static final String TEST_PKG1 = "com.example1";
private static final String TEST_PKG2 = "com.example2";
private static final String TEST_PKG3 = "com.example3";
private UidObserverController mUidObserverController;
@Before
public void setUp() {
mUidObserverController = new UidObserverController(Mockito.mock(Handler.class));
}
@Test
public void testEnqueueUidChange() {
int change = mUidObserverController.enqueueUidChange(TEST_UID1, UidRecord.CHANGE_ACTIVE,
PROCESS_STATE_FOREGROUND_SERVICE, PROCESS_CAPABILITY_ALL, 0, false);
assertEquals("expected=ACTIVE,actual=" + changeToStr(change),
UidRecord.CHANGE_ACTIVE, change);
assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
PROCESS_CAPABILITY_ALL, 0, false);
assertNull(getPendingChange(TEST_UID2));
change = mUidObserverController.enqueueUidChange(TEST_UID2, UidRecord.CHANGE_CACHED,
PROCESS_STATE_CACHED_RECENT, PROCESS_CAPABILITY_NONE, 99, true);
assertEquals("expected=ACTIVE,actual=" + changeToStr(change),
UidRecord.CHANGE_CACHED, change);
assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
PROCESS_CAPABILITY_ALL, 0, false);
assertPendingChange(TEST_UID2, UidRecord.CHANGE_CACHED, PROCESS_STATE_CACHED_RECENT,
PROCESS_CAPABILITY_NONE, 99, true);
change = mUidObserverController.enqueueUidChange(TEST_UID1, UidRecord.CHANGE_UNCACHED,
PROCESS_STATE_TOP, PROCESS_CAPABILITY_ALL, 0, false);
assertEquals("expected=ACTIVE|UNCACHED,actual=" + changeToStr(change),
UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED, change);
assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED,
PROCESS_STATE_TOP, PROCESS_CAPABILITY_ALL, 0, false);
assertPendingChange(TEST_UID2, UidRecord.CHANGE_CACHED, PROCESS_STATE_CACHED_RECENT,
PROCESS_CAPABILITY_NONE, 99, true);
}
@Test
public void testMergeWithPendingChange() {
final SparseArray<Pair<Integer, Integer>> changesToVerify = new SparseArray<>();
changesToVerify.put(UidRecord.CHANGE_ACTIVE,
Pair.create(UidRecord.CHANGE_ACTIVE, UidRecord.CHANGE_IDLE));
changesToVerify.put(UidRecord.CHANGE_IDLE,
Pair.create(UidRecord.CHANGE_IDLE, UidRecord.CHANGE_ACTIVE));
changesToVerify.put(UidRecord.CHANGE_CACHED,
Pair.create(UidRecord.CHANGE_CACHED, UidRecord.CHANGE_UNCACHED));
changesToVerify.put(UidRecord.CHANGE_UNCACHED,
Pair.create(UidRecord.CHANGE_UNCACHED, UidRecord.CHANGE_CACHED));
changesToVerify.put(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED,
Pair.create(UidRecord.CHANGE_ACTIVE, UidRecord.CHANGE_UNCACHED));
changesToVerify.put(UidRecord.CHANGE_IDLE | UidRecord.CHANGE_CACHED,
Pair.create(UidRecord.CHANGE_IDLE, UidRecord.CHANGE_CACHED));
changesToVerify.put(UidRecord.CHANGE_GONE,
Pair.create(UidRecord.CHANGE_GONE, UidRecord.CHANGE_ACTIVE));
changesToVerify.put(UidRecord.CHANGE_GONE,
Pair.create(UidRecord.CHANGE_GONE, UidRecord.CHANGE_CACHED));
for (int i = 0; i < changesToVerify.size(); ++i) {
final int expectedChange = changesToVerify.keyAt(i);
final int currentChange = changesToVerify.valueAt(i).first;
final int pendingChange = changesToVerify.valueAt(i).second;
assertEquals("current=" + changeToStr(currentChange) + ", pending="
+ changeToStr(pendingChange) + "exp=" + changeToStr(expectedChange),
expectedChange, UidObserverController.mergeWithPendingChange(
currentChange, pendingChange));
}
}
@Test
public void testDispatchUidsChanged() throws RemoteException {
addPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_PROCSTATE,
PROCESS_STATE_TOP, 0, PROCESS_CAPABILITY_ALL, false);
final IUidObserver observer1 = Mockito.mock(IUidObserver.Stub.class);
registerObserver(observer1,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_ACTIVE,
PROCESS_STATE_IMPORTANT_FOREGROUND, TEST_PKG2, TEST_UID2);
final IUidObserver observer2 = Mockito.mock(IUidObserver.Stub.class);
registerObserver(observer2, ActivityManager.UID_OBSERVER_PROCSTATE,
PROCESS_STATE_SERVICE, TEST_PKG3, TEST_UID3);
mUidObserverController.dispatchUidsChanged();
verify(observer1).onUidStateChanged(TEST_UID1, PROCESS_STATE_TOP,
0, PROCESS_CAPABILITY_ALL);
verify(observer1).onUidActive(TEST_UID1);
verifyNoMoreInteractions(observer1);
verify(observer2).onUidStateChanged(TEST_UID1, PROCESS_STATE_TOP,
0, PROCESS_CAPABILITY_ALL);
verifyNoMoreInteractions(observer2);
addPendingChange(TEST_UID1, UidRecord.CHANGE_PROCSTATE, PROCESS_STATE_IMPORTANT_BACKGROUND,
99, PROCESS_CAPABILITY_FOREGROUND_LOCATION, false);
mUidObserverController.dispatchUidsChanged();
verify(observer1).onUidStateChanged(TEST_UID1, PROCESS_STATE_IMPORTANT_BACKGROUND,
99, PROCESS_CAPABILITY_FOREGROUND_LOCATION);
verifyNoMoreInteractions(observer1);
verifyNoMoreInteractions(observer2);
addPendingChange(TEST_UID1, UidRecord.CHANGE_PROCSTATE, PROCESS_STATE_RECEIVER,
111, PROCESS_CAPABILITY_NONE, false);
mUidObserverController.dispatchUidsChanged();
verify(observer2).onUidStateChanged(TEST_UID1, PROCESS_STATE_RECEIVER,
111, PROCESS_CAPABILITY_NONE);
verifyNoMoreInteractions(observer1);
verifyNoMoreInteractions(observer2);
unregisterObserver(observer1);
addPendingChange(TEST_UID1, UidRecord.CHANGE_PROCSTATE, PROCESS_STATE_TOP,
112, PROCESS_CAPABILITY_ALL, false);
mUidObserverController.dispatchUidsChanged();
verify(observer2).onUidStateChanged(TEST_UID1, PROCESS_STATE_TOP,
112, PROCESS_CAPABILITY_ALL);
verifyNoMoreInteractions(observer1);
verifyNoMoreInteractions(observer2);
unregisterObserver(observer2);
addPendingChange(TEST_UID1, UidRecord.CHANGE_PROCSTATE, PROCESS_STATE_CACHED_RECENT,
112, PROCESS_CAPABILITY_NONE, false);
mUidObserverController.dispatchUidsChanged();
verifyNoMoreInteractions(observer1);
verifyNoMoreInteractions(observer2);
}
private void registerObserver(IUidObserver observer, int which, int cutpoint,
String callingPackage, int callingUid) {
when(observer.asBinder()).thenReturn((IBinder) observer);
mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid);
Mockito.reset(observer);
}
private void unregisterObserver(IUidObserver observer) {
when(observer.asBinder()).thenReturn((IBinder) observer);
mUidObserverController.unregister(observer);
Mockito.reset(observer);
}
private void addPendingChange(int uid, int change, int procState, long procStateSeq,
int capability, boolean ephemeral) {
final ChangeRecord record = new ChangeRecord();
record.uid = uid;
record.change = change;
record.procState = procState;
record.procStateSeq = procStateSeq;
record.capability = capability;
record.ephemeral = ephemeral;
mUidObserverController.getPendingUidChangesForTest().put(uid, record);
}
private void assertPendingChange(int uid, int change, int procState, long procStateSeq,
int capability, boolean ephemeral) {
final ChangeRecord record = getPendingChange(uid);
assertNotNull(record);
assertEquals(change, record.change);
assertEquals(procState, record.procState);
assertEquals(procStateSeq, record.procStateSeq);
assertEquals(capability, record.capability);
assertEquals(ephemeral, record.ephemeral);
}
private ChangeRecord getPendingChange(int uid) {
return mUidObserverController.getPendingUidChangesForTest().get(uid);
}
private static String changeToStr(int change) {
return DebugUtils.flagsToString(UidRecord.class, "CHANGE_", change);
}
}