Merge "Update `OWNERS` files under `frameworks/base`." into main
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 75cfba0..121f348 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -1037,7 +1037,7 @@
optional int32 uid = 1;
repeated .android.app.ApplicationExitInfoProto app_exit_info = 2;
- repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3;
+ repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3 [deprecated=true];
}
repeated User users = 2;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 796dc77..30b1c6f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15909,6 +15909,7 @@
String[] excludedPackages, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
+
synchronized(this) {
intent = verifyBroadcastLocked(intent);
@@ -15922,6 +15923,12 @@
// Permission regimes around sender-supplied broadcast options.
enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
+ final ComponentName cn = intent.getComponent();
+
+ Trace.traceBegin(
+ Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
+
final long origId = Binder.clearCallingIdentity();
try {
return broadcastIntentLocked(callerApp,
@@ -15932,6 +15939,7 @@
callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
} finally {
Binder.restoreCallingIdentity(origId);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
}
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 666e560..47b65eb 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -88,6 +88,7 @@
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.zip.GZIPOutputStream;
@@ -104,9 +105,9 @@
private static final long APP_EXIT_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30);
/** These are actions that the forEach* should take after each iteration */
- private static final int FOREACH_ACTION_NONE = 0;
- private static final int FOREACH_ACTION_REMOVE_ITEM = 1;
- private static final int FOREACH_ACTION_STOP_ITERATION = 2;
+ @VisibleForTesting static final int FOREACH_ACTION_NONE = 0;
+ @VisibleForTesting static final int FOREACH_ACTION_REMOVE_ITEM = 1;
+ @VisibleForTesting static final int FOREACH_ACTION_STOP_ITERATION = 2;
private static final int APP_EXIT_RAW_INFO_POOL_SIZE = 8;
@@ -125,7 +126,7 @@
private static final String APP_TRACE_FILE_SUFFIX = ".gz";
- private final Object mLock = new Object();
+ @VisibleForTesting final Object mLock = new Object();
/**
* Initialized in {@link #init} and read-only after that.
@@ -410,6 +411,23 @@
}
/**
+ * Certain types of crashes should not be updated. This could end up deleting valuable
+ * information, for example, if a test application crashes and then the `am instrument`
+ * finishes, then the crash whould be replaced with a `reason == USER_REQUESTED`
+ * ApplicationExitInfo from ActivityManager, and the original crash would be lost.
+ */
+ private boolean preventExitInfoUpdate(final ApplicationExitInfo exitInfo) {
+ switch (exitInfo.getReason()) {
+ case ApplicationExitInfo.REASON_ANR:
+ case ApplicationExitInfo.REASON_CRASH:
+ case ApplicationExitInfo.REASON_CRASH_NATIVE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
* Make note when ActivityManagerService decides to kill an application process.
*/
@VisibleForTesting
@@ -418,10 +436,10 @@
ApplicationExitInfo info = getExitInfoLocked(
raw.getPackageName(), raw.getPackageUid(), raw.getPid());
- if (info == null) {
+ if (info == null || preventExitInfoUpdate(info)) {
info = addExitInfoLocked(raw);
} else {
- // always override the existing info since we are now more informational.
+ // Override the existing info since we have more information.
info.setReason(raw.getReason());
info.setSubReason(raw.getSubReason());
info.setStatus(0);
@@ -431,24 +449,8 @@
scheduleLogToStatsdLocked(info, true);
}
- /**
- * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't
- * being killed but the crash should still be added to AppExitInfo. Also, because we're not
- * crashing, don't log out to statsd.
- */
- @VisibleForTesting
- @GuardedBy("mLock")
- void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) {
- addExitInfoLocked(raw, /* recoverable */ true);
- }
-
@GuardedBy("mLock")
private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
- return addExitInfoLocked(raw, /* recoverable */ false);
- }
-
- @GuardedBy("mLock")
- private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) {
if (!mAppExitInfoLoaded.get()) {
Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
return null;
@@ -464,13 +466,13 @@
}
}
for (int i = 0; i < packages.length; i++) {
- addExitInfoInnerLocked(packages[i], uid, info, recoverable);
+ addExitInfoInnerLocked(packages[i], uid, info);
}
// SDK sandbox exits are stored under both real and package UID
if (Process.isSdkSandboxUid(uid)) {
for (int i = 0; i < packages.length; i++) {
- addExitInfoInnerLocked(packages[i], raw.getPackageUid(), info, recoverable);
+ addExitInfoInnerLocked(packages[i], raw.getPackageUid(), info);
}
}
@@ -526,72 +528,77 @@
if (k != null) {
uid = k;
}
- ArrayList<ApplicationExitInfo> tlist = mTmpInfoList;
- tlist.clear();
final int targetUid = uid;
+ // Launder the modification bit through a `final` array, as Java doesn't allow you to mutate
+ // a captured boolean inside of a lambda.
+ final boolean[] isModified = {false};
forEachPackageLocked((packageName, records) -> {
AppExitInfoContainer container = records.get(targetUid);
if (container == null) {
return FOREACH_ACTION_NONE;
}
- tlist.clear();
- container.getExitInfoLocked(pid, 1, tlist);
- if (tlist.size() == 0) {
+ mTmpInfoList.clear();
+ container.getExitInfosLocked(pid, /* maxNum */ 0, mTmpInfoList);
+ if (mTmpInfoList.size() == 0) {
return FOREACH_ACTION_NONE;
}
- ApplicationExitInfo info = tlist.get(0);
- if (info.getRealUid() != targetUid) {
- tlist.clear();
- return FOREACH_ACTION_NONE;
- }
- // Okay found it, update its reason.
- updateExistingExitInfoRecordLocked(info, status, reason);
- return FOREACH_ACTION_STOP_ITERATION;
+ for (int i = 0, size = mTmpInfoList.size(); i < size; i++) {
+ ApplicationExitInfo info = mTmpInfoList.get(i);
+ if (info.getRealUid() != targetUid) {
+ continue;
+ }
+ // We only update the most recent `ApplicationExitInfo` for this pid, which will
+ // always be the first one we se as `getExitInfosLocked()` returns them sorted
+ // by most-recent-first.
+ isModified[0] = true;
+ updateExistingExitInfoRecordLocked(info, status, reason);
+ return FOREACH_ACTION_STOP_ITERATION;
+ }
+ return FOREACH_ACTION_NONE;
});
- return tlist.size() > 0;
+ mTmpInfoList.clear();
+ return isModified[0];
}
/**
* Get the exit info with matching package name, filterUid and filterPid (if > 0)
*/
@VisibleForTesting
- void getExitInfo(final String packageName, final int filterUid,
- final int filterPid, final int maxNum, final ArrayList<ApplicationExitInfo> results) {
+ void getExitInfo(final String packageName, final int filterUid, final int filterPid,
+ final int maxNum, final List<ApplicationExitInfo> results) {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- boolean emptyPackageName = TextUtils.isEmpty(packageName);
- if (!emptyPackageName) {
- // fast path
+ if (!TextUtils.isEmpty(packageName)) {
+ // Fast path - just a single package.
AppExitInfoContainer container = mData.get(packageName, filterUid);
if (container != null) {
- container.getExitInfoLocked(filterPid, maxNum, results);
+ container.getExitInfosLocked(filterPid, maxNum, results);
}
- } else {
- // slow path
- final ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
- list.clear();
- // get all packages
- forEachPackageLocked((name, records) -> {
- AppExitInfoContainer container = records.get(filterUid);
- if (container != null) {
- mTmpInfoList.clear();
- list.addAll(container.toListLocked(mTmpInfoList, filterPid));
- }
- return AppExitInfoTracker.FOREACH_ACTION_NONE;
- });
+ return;
+ }
- Collections.sort(list,
- (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
- int size = list.size();
- if (maxNum > 0) {
- size = Math.min(size, maxNum);
+ // Slow path - get all the packages.
+ forEachPackageLocked((name, records) -> {
+ AppExitInfoContainer container = records.get(filterUid);
+ if (container != null) {
+ container.getExitInfosLocked(filterPid, /* maxNum */ 0, results);
}
- for (int i = 0; i < size; i++) {
- results.add(list.get(i));
- }
- list.clear();
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ });
+
+ // And while the results for each package are sorted, we should
+ // sort over and trim the quantity of global results as well.
+ Collections.sort(
+ results, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
+ if (maxNum <= 0) {
+ return;
+ }
+
+ int elementsToRemove = results.size() - maxNum;
+ for (int i = 0; i < elementsToRemove; i++) {
+ results.removeLast();
}
}
} finally {
@@ -606,12 +613,10 @@
@GuardedBy("mLock")
private ApplicationExitInfo getExitInfoLocked(final String packageName,
final int filterUid, final int filterPid) {
- ArrayList<ApplicationExitInfo> list = mTmpInfoList;
- list.clear();
- getExitInfo(packageName, filterUid, filterPid, 1, list);
-
- ApplicationExitInfo info = list.size() > 0 ? list.get(0) : null;
- list.clear();
+ mTmpInfoList.clear();
+ getExitInfo(packageName, filterUid, filterPid, 1, mTmpInfoList);
+ ApplicationExitInfo info = mTmpInfoList.size() > 0 ? mTmpInfoList.getFirst() : null;
+ mTmpInfoList.clear();
return info;
}
@@ -878,8 +883,7 @@
}
@GuardedBy("mLock")
- private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info,
- boolean recoverable) {
+ private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) {
AppExitInfoContainer container = mData.get(packageName, uid);
if (container == null) {
container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
@@ -893,11 +897,7 @@
}
mData.put(packageName, uid, container);
}
- if (recoverable) {
- container.addRecoverableCrashLocked(info);
- } else {
- container.addExitInfoLocked(info);
- }
+ container.addExitInfoLocked(info);
}
@GuardedBy("mLock")
@@ -1205,7 +1205,7 @@
forEachPackageLocked((name, records) -> {
for (int i = records.size() - 1; i >= 0; i--) {
final AppExitInfoContainer container = records.valueAt(i);
- container.forEachRecordLocked((pid, info) -> {
+ container.forEachRecordLocked((info) -> {
final File traceFile = info.getTraceFile();
if (traceFile != null) {
allFiles.remove(traceFile.getName());
@@ -1322,90 +1322,72 @@
* A container class of {@link android.app.ApplicationExitInfo}
*/
final class AppExitInfoContainer {
- private SparseArray<ApplicationExitInfo> mInfos; // index is a pid
- private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid
+ private ArrayList<ApplicationExitInfo> mExitInfos;
private int mMaxCapacity;
private int mUid; // Application uid, not isolated uid.
AppExitInfoContainer(final int maxCapacity) {
- mInfos = new SparseArray<ApplicationExitInfo>();
- mRecoverableCrashes = new SparseArray<ApplicationExitInfo>();
+ mExitInfos = new ArrayList<ApplicationExitInfo>();
mMaxCapacity = maxCapacity;
}
+ @VisibleForTesting
@GuardedBy("mLock")
- void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid,
- final int maxNum, ArrayList<ApplicationExitInfo> results) {
- if (filterPid > 0) {
- ApplicationExitInfo r = map.get(filterPid);
- if (r != null) {
- results.add(r);
+ void getExitInfosLocked(
+ final int filterPid, final int maxNum, List<ApplicationExitInfo> results) {
+ if (mExitInfos.size() == 0) {
+ return;
+ }
+
+ // Most of the callers might only be interested with the most recent
+ // ApplicationExitInfo, and so we can special case an O(n) walk.
+ if (maxNum == 1) {
+ ApplicationExitInfo result = null;
+ for (int i = 0, size = mExitInfos.size(); i < size; i++) {
+ ApplicationExitInfo info = mExitInfos.get(i);
+ if (filterPid > 0 && info.getPid() != filterPid) {
+ continue;
+ }
+
+ if (result == null || result.getTimestamp() < info.getTimestamp()) {
+ result = info;
+ }
}
+ if (result != null) {
+ results.add(result);
+ }
+ return;
+ }
+
+ mTmpInfoList2.clear();
+ if (filterPid <= 0) {
+ mTmpInfoList2.addAll(mExitInfos);
} else {
- final int numRep = map.size();
- if (maxNum <= 0 || numRep <= maxNum) {
- // Return all records.
- for (int i = 0; i < numRep; i++) {
- results.add(map.valueAt(i));
- }
- Collections.sort(results,
- (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
- } else {
- if (maxNum == 1) {
- // Most of the caller might be only interested with the most recent one
- ApplicationExitInfo r = map.valueAt(0);
- for (int i = 1; i < numRep; i++) {
- ApplicationExitInfo t = map.valueAt(i);
- if (r.getTimestamp() < t.getTimestamp()) {
- r = t;
- }
- }
- results.add(r);
- } else {
- // Huh, need to sort it out then.
- ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
- list.clear();
- for (int i = 0; i < numRep; i++) {
- list.add(map.valueAt(i));
- }
- Collections.sort(list,
- (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
- for (int i = 0; i < maxNum; i++) {
- results.add(list.get(i));
- }
- list.clear();
+ for (int i = 0, size = mExitInfos.size(); i < size; i++) {
+ ApplicationExitInfo info = mExitInfos.get(i);
+ if (info.getPid() == filterPid) {
+ mTmpInfoList2.add(info);
}
}
}
- }
- @GuardedBy("mLock")
- void getExitInfoLocked(final int filterPid, final int maxNum,
- ArrayList<ApplicationExitInfo> results) {
- getInfosLocked(mInfos, filterPid, maxNum, results);
- }
-
- @GuardedBy("mLock")
- void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) {
- int size;
- if ((size = map.size()) >= mMaxCapacity) {
- int oldestIndex = -1;
- long oldestTimeStamp = Long.MAX_VALUE;
- for (int i = 0; i < size; i++) {
- ApplicationExitInfo r = map.valueAt(i);
- if (r.getTimestamp() < oldestTimeStamp) {
- oldestTimeStamp = r.getTimestamp();
- oldestIndex = i;
- }
- }
- if (oldestIndex >= 0) {
- final File traceFile = map.valueAt(oldestIndex).getTraceFile();
- if (traceFile != null) {
- traceFile.delete();
- }
- map.removeAt(oldestIndex);
- }
+ Collections.sort(
+ mTmpInfoList2, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
+ if (maxNum <= 0) {
+ results.addAll(mTmpInfoList2);
+ return;
}
+
+ int elementsToRemove = mTmpInfoList2.size() - maxNum;
+ for (int i = 0; i < elementsToRemove; i++) {
+ mTmpInfoList2.removeLast();
+ }
+ results.addAll(mTmpInfoList2);
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void addExitInfoLocked(ApplicationExitInfo info) {
// Claim the state information if there is any
int uid = info.getPackageUid();
// SDK sandbox app states and app traces are stored under real UID
@@ -1420,24 +1402,39 @@
if (info.getTraceFile() == null) {
info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid));
}
-
info.setAppTraceRetriever(mAppTraceRetriever);
- map.append(pid, info);
+
+ mExitInfos.add(info);
+ if (mExitInfos.size() <= mMaxCapacity) {
+ return;
+ }
+
+ ApplicationExitInfo oldest = null;
+ for (int i = 0, size = mExitInfos.size(); i < size; i++) {
+ ApplicationExitInfo info2 = mExitInfos.get(i);
+ if (oldest == null || info2.getTimestamp() < oldest.getTimestamp()) {
+ oldest = info2;
+ }
+ }
+ File traceFile = oldest.getTraceFile();
+ if (traceFile != null) {
+ traceFile.delete();
+ }
+ mExitInfos.remove(oldest);
}
@GuardedBy("mLock")
- void addExitInfoLocked(ApplicationExitInfo info) {
- addInfoLocked(mInfos, info);
- }
-
- @GuardedBy("mLock")
- void addRecoverableCrashLocked(ApplicationExitInfo info) {
- addInfoLocked(mRecoverableCrashes, info);
+ ApplicationExitInfo getLastExitInfoForPid(final int pid) {
+ mTmpInfoList.clear();
+ getExitInfosLocked(pid, /* maxNum */ 1, mTmpInfoList);
+ ApplicationExitInfo info = mTmpInfoList.size() == 0 ? null : mTmpInfoList.getFirst();
+ mTmpInfoList.clear();
+ return info;
}
@GuardedBy("mLock")
boolean appendTraceIfNecessaryLocked(final int pid, final File traceFile) {
- final ApplicationExitInfo r = mInfos.get(pid);
+ final ApplicationExitInfo r = getLastExitInfoForPid(pid);
if (r != null) {
r.setTraceFile(traceFile);
r.setAppTraceRetriever(mAppTraceRetriever);
@@ -1447,49 +1444,36 @@
}
@GuardedBy("mLock")
- void destroyLocked(SparseArray<ApplicationExitInfo> map) {
- for (int i = map.size() - 1; i >= 0; i--) {
- ApplicationExitInfo ai = map.valueAt(i);
- final File traceFile = ai.getTraceFile();
+ void destroyLocked() {
+ for (int i = 0, size = mExitInfos.size(); i < size; i++) {
+ ApplicationExitInfo info = mExitInfos.get(i);
+ final File traceFile = info.getTraceFile();
if (traceFile != null) {
traceFile.delete();
}
- ai.setTraceFile(null);
- ai.setAppTraceRetriever(null);
+ info.setTraceFile(null);
+ info.setAppTraceRetriever(null);
}
}
+ /**
+ * Go through each record in an *unspecified* order, execute `callback()` on each element,
+ * and potentially do some action (stopping iteration, removing the element, etc.) based on
+ * the return value of the callback.
+ */
@GuardedBy("mLock")
- void destroyLocked() {
- destroyLocked(mInfos);
- destroyLocked(mRecoverableCrashes);
- }
-
- @GuardedBy("mLock")
- void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) {
+ void forEachRecordLocked(final Function<ApplicationExitInfo, Integer> callback) {
if (callback == null) return;
- for (int i = mInfos.size() - 1; i >= 0; i--) {
- switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
+ for (int i = mExitInfos.size() - 1; i >= 0; i--) {
+ ApplicationExitInfo info = mExitInfos.get(i);
+ switch (callback.apply(info)) {
case FOREACH_ACTION_STOP_ITERATION: return;
case FOREACH_ACTION_REMOVE_ITEM:
- final File traceFile = mInfos.valueAt(i).getTraceFile();
- if (traceFile != null) {
+ File traceFile;
+ if ((traceFile = info.getTraceFile()) != null) {
traceFile.delete();
}
- mInfos.removeAt(i);
- break;
- }
- }
- for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
- switch (callback.apply(
- mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) {
- case FOREACH_ACTION_STOP_ITERATION: return;
- case FOREACH_ACTION_REMOVE_ITEM:
- final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile();
- if (traceFile != null) {
- traceFile.delete();
- }
- mRecoverableCrashes.removeAt(i);
+ mExitInfos.remove(info);
break;
}
}
@@ -1497,30 +1481,20 @@
@GuardedBy("mLock")
void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
- ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>();
- for (int i = mInfos.size() - 1; i >= 0; i--) {
- list.add(mInfos.valueAt(i));
+ mTmpInfoList.clear();
+ getExitInfosLocked(/* filterPid */ 0, /* maxNum */ 0, mTmpInfoList);
+ for (int i = 0, size = mTmpInfoList.size(); i < size; i++) {
+ mTmpInfoList.get(i).dump(pw, prefix + " ", "#" + i, sdf);
}
- for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
- list.add(mRecoverableCrashes.valueAt(i));
- }
- Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
- int size = list.size();
- for (int i = 0; i < size; i++) {
- list.get(i).dump(pw, prefix + " ", "#" + i, sdf);
- }
+ mTmpInfoList.clear();
}
@GuardedBy("mLock")
void writeToProto(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(AppsExitInfoProto.Package.User.UID, mUid);
- for (int i = 0; i < mInfos.size(); i++) {
- mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
- }
- for (int i = 0; i < mRecoverableCrashes.size(); i++) {
- mRecoverableCrashes.valueAt(i).writeToProto(
- proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+ for (int i = 0, size = mExitInfos.size(); i < size; i++) {
+ mExitInfos.get(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
}
proto.end(token);
}
@@ -1539,14 +1513,7 @@
case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: {
ApplicationExitInfo info = new ApplicationExitInfo();
info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
- mInfos.put(info.getPid(), info);
- break;
- }
- case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: {
- ApplicationExitInfo info = new ApplicationExitInfo();
- info.readFromProto(
- proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
- mRecoverableCrashes.put(info.getPid(), info);
+ mExitInfos.add(info);
break;
}
}
@@ -1554,24 +1521,6 @@
proto.end(token);
return mUid;
}
-
- @GuardedBy("mLock")
- List<ApplicationExitInfo> toListLocked(List<ApplicationExitInfo> list, int filterPid) {
- if (list == null) {
- list = new ArrayList<ApplicationExitInfo>();
- }
- for (int i = mInfos.size() - 1; i >= 0; i--) {
- if (filterPid == 0 || filterPid == mInfos.keyAt(i)) {
- list.add(mInfos.valueAt(i));
- }
- }
- for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
- if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) {
- list.add(mRecoverableCrashes.valueAt(i));
- }
- }
- return list;
- }
}
/**
@@ -1750,7 +1699,11 @@
case MSG_APP_RECOVERABLE_CRASH: {
ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
synchronized (mLock) {
- handleNoteAppRecoverableCrashLocked(raw);
+ // Unlike MSG_APP_KILL, this is a recoverable crash, and
+ // so we want to bypass the statsd app-kill logging.
+ // Hence, call `addExitInfoLocked()` directly instead of
+ // `handleNoteAppKillLocked()`.
+ addExitInfoLocked(raw);
}
recycleRawRecord(raw);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index e15942b..adcbf5c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -84,7 +84,11 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
import java.util.Random;
+import java.util.function.Function;
import java.util.zip.GZIPInputStream;
/**
@@ -940,6 +944,228 @@
}
}
+ private ApplicationExitInfo createExitInfo(int i) {
+ ApplicationExitInfo info = new ApplicationExitInfo();
+ info.setPid(i);
+ info.setTimestamp(1000 + i);
+ info.setPackageUid(2000);
+ return info;
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private ArrayList<ApplicationExitInfo> getExitInfosHelper(
+ AppExitInfoTracker.AppExitInfoContainer container, int filterPid, int maxNum) {
+ ArrayList<ApplicationExitInfo> infos = new ArrayList<ApplicationExitInfo>();
+ container.getExitInfosLocked(filterPid, maxNum, infos);
+ return infos;
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void checkAreHelper(AppExitInfoTracker.AppExitInfoContainer container, int filterPid,
+ int maxNum, List<Integer> expected, Function<ApplicationExitInfo, Integer> func) {
+ ArrayList<Integer> values = new ArrayList<Integer>();
+ getExitInfosHelper(container, filterPid, maxNum)
+ .forEach((exitInfo) -> values.add(func.apply(exitInfo)));
+ assertEquals(values, expected);
+
+ HashMap<Integer, Integer> expectedMultiset = new HashMap<Integer, Integer>();
+ expected.forEach(
+ (elem) -> expectedMultiset.put(elem, expectedMultiset.getOrDefault(elem, 0) + 1));
+ // `maxNum` isn't a parameter supported by `forEachRecordLocked()s`, but we can emulate it
+ // by stopping iteration when we've seen enough elements.
+ int[] numElementsToObserveWrapped = {maxNum};
+ container.forEachRecordLocked((exitInfo) -> {
+ // Same thing as above, `filterPid` isn't a parameter supported out of the box for
+ // `forEachRecordLocked()`, but we emulate it here.
+ if (filterPid > 0 && filterPid != exitInfo.getPid()) {
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ }
+
+ Integer key = func.apply(exitInfo);
+ assertTrue(expectedMultiset.toString(), expectedMultiset.containsKey(key));
+ Integer references = expectedMultiset.get(key);
+ if (references == 1) {
+ expectedMultiset.remove(key);
+ } else {
+ expectedMultiset.put(key, references - 1);
+ }
+ if (--numElementsToObserveWrapped[0] == 0) {
+ return AppExitInfoTracker.FOREACH_ACTION_STOP_ITERATION;
+ }
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ });
+ assertEquals(expectedMultiset.size(), 0);
+ }
+
+ private void checkPidsAre(AppExitInfoTracker.AppExitInfoContainer container, int filterPid,
+ int maxNum, List<Integer> expectedPids) {
+ checkAreHelper(container, filterPid, maxNum, expectedPids, (exitInfo) -> exitInfo.getPid());
+ }
+
+ private void checkPidsAre(
+ AppExitInfoTracker.AppExitInfoContainer container, List<Integer> expectedPids) {
+ checkPidsAre(container, 0, 0, expectedPids);
+ }
+
+ private void checkTimestampsAre(AppExitInfoTracker.AppExitInfoContainer container,
+ int filterPid, int maxNum, List<Integer> expectedTimestamps) {
+ checkAreHelper(container, filterPid, maxNum, expectedTimestamps,
+ (exitInfo) -> (int) exitInfo.getTimestamp());
+ }
+
+ private void checkTimestampsAre(
+ AppExitInfoTracker.AppExitInfoContainer container, List<Integer> expectedTimestamps) {
+ checkTimestampsAre(container, 0, 0, expectedTimestamps);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private AppExitInfoTracker.AppExitInfoContainer createBasicContainer() {
+ AppExitInfoTracker.AppExitInfoContainer container =
+ mAppExitInfoTracker.new AppExitInfoContainer(3);
+ container.addExitInfoLocked(createExitInfo(10));
+ container.addExitInfoLocked(createExitInfo(30));
+ container.addExitInfoLocked(createExitInfo(20));
+ return container;
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testContainerGetExitInfosIsSortedNewestFirst() throws Exception {
+ AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
+ checkPidsAre(container, Arrays.asList(30, 20, 10));
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testContainerRemovesOldestReports() throws Exception {
+ AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
+ container.addExitInfoLocked(createExitInfo(40));
+ checkPidsAre(container, Arrays.asList(40, 30, 20));
+
+ container.addExitInfoLocked(createExitInfo(50));
+ checkPidsAre(container, Arrays.asList(50, 40, 30));
+
+ container.addExitInfoLocked(createExitInfo(45));
+ checkPidsAre(container, Arrays.asList(50, 45, 40));
+
+ // Adding an older report shouldn't remove the newer ones.
+ container.addExitInfoLocked(createExitInfo(15));
+ checkPidsAre(container, Arrays.asList(50, 45, 40));
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testContainerFilterByPid() throws Exception {
+ AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
+ assertEquals(1, getExitInfosHelper(container, 30, 0).size());
+ assertEquals(30, getExitInfosHelper(container, 0, 0).get(0).getPid());
+
+ assertEquals(1, getExitInfosHelper(container, 30, 0).size());
+ assertEquals(20, getExitInfosHelper(container, 20, 0).get(0).getPid());
+
+ assertEquals(1, getExitInfosHelper(container, 10, 0).size());
+ assertEquals(10, getExitInfosHelper(container, 10, 0).get(0).getPid());
+
+ assertEquals(0, getExitInfosHelper(container, 1337, 0).size());
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testContainerLimitQuantityOfResults() throws Exception {
+ AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
+ checkPidsAre(container, /* filterPid */ 30, /* maxNum */ 1, Arrays.asList(30));
+ checkPidsAre(container, /* filterPid */ 30, /* maxNum */ 1000, Arrays.asList(30));
+
+ checkPidsAre(container, /* filterPid */ 20, /* maxNum */ 1, Arrays.asList(20));
+ checkPidsAre(container, /* filterPid */ 20, /* maxNum */ 1000, Arrays.asList(20));
+
+ checkPidsAre(container, /* filterPid */ 10, /* maxNum */ 1, Arrays.asList(10));
+ checkPidsAre(container, /* filterPid */ 10, /* maxNum */ 1000, Arrays.asList(10));
+
+ checkPidsAre(container, /* filterPid */ 1337, /* maxNum */ 1, Arrays.asList());
+ checkPidsAre(container, /* filterPid */ 1337, /* maxNum */ 1000, Arrays.asList());
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testContainerLastExitInfoForPid() throws Exception {
+ AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
+ assertEquals(30, container.getLastExitInfoForPid(30).getPid());
+ assertEquals(20, container.getLastExitInfoForPid(20).getPid());
+ assertEquals(10, container.getLastExitInfoForPid(10).getPid());
+ assertEquals(null, container.getLastExitInfoForPid(1337));
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testContainerCanHoldMultipleFromSamePid() throws Exception {
+ AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
+ ApplicationExitInfo info = createExitInfo(100);
+ ApplicationExitInfo info2 = createExitInfo(100);
+ ApplicationExitInfo info3 = createExitInfo(100);
+ info2.setTimestamp(1337);
+ info3.setTimestamp(31337);
+
+ container.addExitInfoLocked(info);
+ assertEquals(1100, container.getLastExitInfoForPid(100).getTimestamp());
+ container.addExitInfoLocked(info2);
+ assertEquals(1337, container.getLastExitInfoForPid(100).getTimestamp());
+ container.addExitInfoLocked(info3);
+ assertEquals(31337, container.getLastExitInfoForPid(100).getTimestamp());
+
+ checkPidsAre(container, Arrays.asList(100, 100, 100));
+ checkTimestampsAre(container, Arrays.asList(31337, 1337, 1100));
+
+ checkPidsAre(container, /* filterPid */ 100, /* maxNum */ 0, Arrays.asList(100, 100, 100));
+ checkTimestampsAre(
+ container, /* filterPid */ 100, /* maxNum */ 0, Arrays.asList(31337, 1337, 1100));
+
+ checkPidsAre(container, /* filterPid */ 100, /* maxNum */ 2, Arrays.asList(100, 100));
+ checkTimestampsAre(
+ container, /* filterPid */ 100, /* maxNum */ 2, Arrays.asList(31337, 1337));
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testContainerIteration() throws Exception {
+ AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
+ checkPidsAre(container, Arrays.asList(30, 20, 10));
+
+ // Unfortunately relying on order for this test, which is implemented as "last inserted" ->
+ // "first inserted". Note that this is insertion order, not timestamp. Thus, it's 20 -> 30
+ // -> 10, as defined by `createBasicContainer()`.
+ List<Integer> elements = Arrays.asList(20, 30, 10);
+ for (int i = 0, size = elements.size(); i < size; i++) {
+ ArrayList<Integer> processedEntries = new ArrayList<Integer>();
+ final int finalIndex = i;
+ container.forEachRecordLocked((exitInfo) -> {
+ processedEntries.add(new Integer(exitInfo.getPid()));
+ if (exitInfo.getPid() == elements.get(finalIndex)) {
+ return AppExitInfoTracker.FOREACH_ACTION_STOP_ITERATION;
+ }
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ });
+ assertEquals(processedEntries, elements.subList(0, i + 1));
+ }
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testContainerIterationRemove() throws Exception {
+ for (int pidToRemove : Arrays.asList(30, 20, 10)) {
+ AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
+ container.forEachRecordLocked((exitInfo) -> {
+ if (exitInfo.getPid() == pidToRemove) {
+ return AppExitInfoTracker.FOREACH_ACTION_REMOVE_ITEM;
+ }
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ });
+ ArrayList<Integer> pidsRemaining = new ArrayList<Integer>(Arrays.asList(30, 20, 10));
+ pidsRemaining.remove(new Integer(pidToRemove));
+ checkPidsAre(container, pidsRemaining);
+ }
+ }
+
private static int makeExitStatus(int exitCode) {
return (exitCode << 8) & 0xff00;
}