summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt31
-rwxr-xr-xapi/system-current.txt4
-rw-r--r--core/java/android/app/ActivityManager.java45
-rw-r--r--core/java/android/app/ApplicationExitInfo.aidl19
-rw-r--r--core/java/android/app/ApplicationExitInfo.java901
-rw-r--r--core/java/android/app/IActivityManager.aidl27
-rw-r--r--core/proto/android/app/appexitinfo.proto185
-rw-r--r--core/proto/android/server/activitymanagerservice.proto21
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java131
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java32
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java73
-rw-r--r--services/core/java/com/android/server/am/AppExitInfoTracker.java1302
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java20
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java126
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java25
-rw-r--r--services/core/java/com/android/server/wm/ActivityStackSupervisor.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessListener.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java954
22 files changed, 3815 insertions, 108 deletions
diff --git a/api/current.txt b/api/current.txt
index 1210d627c99e..5906308afced 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3977,6 +3977,7 @@ package android.app {
method public android.util.Size getAppTaskThumbnailSize();
method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks();
method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo();
+ method @Nullable public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int);
method public int getLargeMemoryClass();
method public int getLauncherLargeIconDensity();
method public int getLauncherLargeIconSize();
@@ -3994,6 +3995,7 @@ package android.app {
method public boolean isActivityStartAllowedOnDisplay(@NonNull android.content.Context, int, @NonNull android.content.Intent);
method public boolean isBackgroundRestricted();
method @Deprecated public boolean isInLockTaskMode();
+ method public static boolean isLowMemoryKillReportSupported();
method public boolean isLowRamDevice();
method @Deprecated public static boolean isRunningInTestHarness();
method public static boolean isRunningInUserTestHarness();
@@ -4507,6 +4509,35 @@ package android.app {
field public String serviceDetails;
}
+ public final class ApplicationExitInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getDefiningUid();
+ method @Nullable public String getDescription();
+ method public int getImportance();
+ method public int getPackageUid();
+ method public int getPid();
+ method @NonNull public String getProcessName();
+ method public int getPss();
+ method public int getRealUid();
+ method public int getReason();
+ method public int getRss();
+ method public int getStatus();
+ method public long getTimestamp();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationExitInfo> CREATOR;
+ field public static final int REASON_ANR = 6; // 0x6
+ field public static final int REASON_CRASH = 4; // 0x4
+ field public static final int REASON_CRASH_NATIVE = 5; // 0x5
+ field public static final int REASON_EXCESSIVE_RESOURCE_USAGE = 9; // 0x9
+ field public static final int REASON_EXIT_SELF = 1; // 0x1
+ field public static final int REASON_INITIALIZATION_FAILURE = 7; // 0x7
+ field public static final int REASON_LOW_MEMORY = 3; // 0x3
+ field public static final int REASON_OTHER = 10; // 0xa
+ field public static final int REASON_PERMISSION_CHANGE = 8; // 0x8
+ field public static final int REASON_SIGNALED = 2; // 0x2
+ field public static final int REASON_UNKNOWN = 0; // 0x0
+ }
+
public final class AsyncNotedAppOp implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getFeatureId();
diff --git a/api/system-current.txt b/api/system-current.txt
index c2f6b2feafb9..2e8aee3d3bc1 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -567,6 +567,10 @@ package android.app {
field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
}
+ public final class ApplicationExitInfo implements android.os.Parcelable {
+ method @NonNull public android.os.UserHandle getUserHandle();
+ }
+
public class BroadcastOptions {
method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission("android.permission.START_ACTIVITIES_FROM_BACKGROUND") public void setBackgroundActivityStartsAllowed(boolean);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e1bd6aeb6f48..8753b986f2df 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import android.Manifest;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -3480,6 +3481,50 @@ public class ActivityManager {
}
/**
+ * Return a list of {@link ApplicationExitInfo} records containing the reasons for the most
+ * recent app deaths.
+ *
+ * <p class="note"> Note: System stores this historical information in a ring buffer and only
+ * the most recent records will be returned. </p>
+ *
+ * <p class="note"> Note: In the case that this application was bound to an external service
+ * with flag {@link android.content.Context#BIND_EXTERNAL_SERVICE}, the process of that external
+ * service will be included in this package's exit info. </p>
+ *
+ * @param packageName Optional, a null value means match all packages belonging to the
+ * caller's UID. If this package belongs to another UID, you must hold
+ * {@link android.Manifest.permission#DUMP} in order to retrieve it.
+ * @param pid A process ID that used to belong to this package but died later; a value
+ * of 0 means to ignore this parameter and return all matching records.
+ * @param maxNum The maximum number of results to be returned; a value of 0
+ * means to ignore this parameter and return all matching records
+ *
+ * @return a list of {@link ApplicationExitInfo} records matching the criteria, sorted in
+ * the order from most recent to least recent.
+ */
+ @Nullable
+ public List<ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String packageName,
+ @IntRange(from = 0) int pid, @IntRange(from = 0) int maxNum) {
+ try {
+ ParceledListSlice<ApplicationExitInfo> r = getService().getHistoricalProcessExitReasons(
+ packageName, pid, maxNum, mContext.getUserId());
+ return r == null ? null : r.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /*
+ * @return Whether or not the low memory kill will be reported in
+ * {@link #getHistoricalProcessExitReasons}.
+ *
+ * @see {@link ApplicationExitInfo#REASON_LOW_MEMORY}
+ */
+ public static boolean isLowMemoryKillReportSupported() {
+ return SystemProperties.getBoolean("persist.sys.lmk.reportkills", false);
+ }
+
+ /**
* Return the importance of a given package name, based on the processes that are
* currently running. The return value is one of the importance constants defined
* in {@link RunningAppProcessInfo}, giving you the highest importance of all the
diff --git a/core/java/android/app/ApplicationExitInfo.aidl b/core/java/android/app/ApplicationExitInfo.aidl
new file mode 100644
index 000000000000..fb7d8f605c96
--- /dev/null
+++ b/core/java/android/app/ApplicationExitInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 android.app;
+
+parcelable ApplicationExitInfo;
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
new file mode 100644
index 000000000000..4bf5f07e86be
--- /dev/null
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -0,0 +1,901 @@
+/*
+ * Copyright (C) 2019 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 android.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.ActivityManager.RunningAppProcessInfo.Importance;
+import android.icu.text.SimpleDateFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.DebugUtils;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Describes the information of an application process's death.
+ *
+ * <p>
+ * Application process could die for many reasons, for example {@link #REASON_LOW_MEMORY}
+ * when it was killed by the ystem because it was running low on memory. Reason
+ * of the death can be retrieved via {@link #getReason}. Besides the reason, there are a few other
+ * auxiliary APIs like {@link #getStatus} and {@link #getImportance} to help the caller with
+ * additional diagnostic information.
+ * </p>
+ *
+ */
+public final class ApplicationExitInfo implements Parcelable {
+
+ /**
+ * Application process died due to unknown reason.
+ */
+ public static final int REASON_UNKNOWN = 0;
+
+ /**
+ * Application process exit normally by itself, for example,
+ * via {@link java.lang.System#exit}; {@link #getStatus} will specify the exit code.
+ *
+ * <p>Applications should normally not do this, as the system has a better knowledge
+ * in terms of process management.</p>
+ */
+ public static final int REASON_EXIT_SELF = 1;
+
+ /**
+ * Application process died due to the result of an OS signal; for example,
+ * {@link android.system.OsConstants#SIGKILL}; {@link #getStatus} will specify the signal
+ * number.
+ */
+ public static final int REASON_SIGNALED = 2;
+
+ /**
+ * Application process was killed by the system low memory killer, meaning the system was
+ * under memory pressure at the time of kill.
+ *
+ * <p class="note">
+ * Not all devices support reporting {@link #REASON_LOW_MEMORY}; on a device with no such
+ * support, when a process is killed due to memory pressure, the {@link #getReason} will return
+ * {@link #REASON_SIGNALED} and {@link #getStatus} will return
+ * the value {@link android.system.OsConstants#SIGKILL}.
+ *
+ * Application should use {@link ActivityManager#isLowMemoryKillReportSupported} to check
+ * if the device supports reporting {@link #REASON_LOW_MEMORY} or not.
+ * </p>
+ */
+ public static final int REASON_LOW_MEMORY = 3;
+
+ /**
+ * Application process died because of an unhandled exception in Java code.
+ */
+ public static final int REASON_CRASH = 4;
+
+ /**
+ * Application process died because of a native code crash.
+ */
+ public static final int REASON_CRASH_NATIVE = 5;
+
+ /**
+ * Application process was killed due to being unresponsive (ANR).
+ */
+ public static final int REASON_ANR = 6;
+
+ /**
+ * Application process was killed because of initialization failure,
+ * for example, it took too long to attach to the system during the start,
+ * or there was an error during initialization.
+ */
+ public static final int REASON_INITIALIZATION_FAILURE = 7;
+
+ /**
+ * Application process was killed due to a runtime permission change.
+ */
+ public static final int REASON_PERMISSION_CHANGE = 8;
+
+ /**
+ * Application process was killed by the system due to excessive resource usage.
+ */
+ public static final int REASON_EXCESSIVE_RESOURCE_USAGE = 9;
+
+ /**
+ * Application process was killed by the system for various other reasons,
+ * for example, the application package got disabled by the user;
+ * {@link #getDescription} will specify the cause given by the system.
+ */
+ public static final int REASON_OTHER = 10;
+
+ /**
+ * Application process kills subreason is unknown.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_UNKNOWN = 0;
+
+ /**
+ * Application process was killed because user quit it on the "wait for debugger" dialog;
+ * this would be set when the reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_WAIT_FOR_DEBUGGER = 1;
+
+ /**
+ * Application process was killed by the activity manager because there were too many cached
+ * processes; this would be set only when the reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_TOO_MANY_CACHED = 2;
+
+ /**
+ * Application process was killed by the activity manager because there were too many empty
+ * processes; this would be set only when the reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_TOO_MANY_EMPTY = 3;
+
+ /**
+ * Application process was killed by the activity manager because there were too many cached
+ * processes and this process had been in empty state for a long time;
+ * this would be set only when the reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_TRIM_EMPTY = 4;
+
+ /**
+ * Application process was killed by the activity manager because system was on memory pressure
+ * and this process took large amount of cached memory;
+ * this would be set only when the reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_LARGE_CACHED = 5;
+
+ /**
+ * Application process was killed by the activity manager because the system was on low memory
+ * pressure for a significant amount of time since last idle;
+ * this would be set only when the reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_MEMORY_PRESSURE = 6;
+
+ /**
+ * Application process was killed by the activity manager due to excessive CPU usage;
+ * this would be set only when the reason is {@link #REASON_EXCESSIVE_RESOURCE_USAGE}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_EXCESSIVE_CPU = 7;
+
+ /**
+ * @see {@link #getPid}
+ */
+ private int mPid;
+
+ /**
+ * @see {@link #getRealUid}
+ */
+ private int mRealUid;
+
+ /**
+ * @see {@link #getPackageUid}
+ */
+ private int mPackageUid;
+
+ /**
+ * @see {@link #getDefiningUid}
+ */
+ private int mDefiningUid;
+
+ /**
+ * @see {@link #getProcessName}
+ */
+ private String mProcessName;
+
+ /**
+ * @see {@link #getReason}
+ */
+ private @Reason int mReason;
+
+ /**
+ * @see {@link #getStatus}
+ */
+ private int mStatus;
+
+ /**
+ * @see {@link #getImportance}
+ */
+ private @Importance int mImportance;
+
+ /**
+ * @see {@link #getPss}
+ */
+ private int mPss;
+
+ /**
+ * @see {@link #getRss}
+ */
+ private int mRss;
+
+ /**
+ * @see {@link #getTimestamp}
+ */
+ private long mTimestamp;
+
+ /**
+ * @see {@link #getDescription}
+ */
+ private @Nullable String mDescription;
+
+ /**
+ * @see {@link #getSubReason}
+ */
+ private @SubReason int mSubReason;
+
+ /**
+ * @see {@link #getConnectionGroup}
+ */
+ private int mConnectionGroup;
+
+ /**
+ * @see {@link #getPackageName}
+ */
+ private String mPackageName;
+
+ /**
+ * @see {@link #getPackageList}
+ */
+ private String[] mPackageList;
+
+ /** @hide */
+ @IntDef(prefix = { "REASON_" }, value = {
+ REASON_UNKNOWN,
+ REASON_EXIT_SELF,
+ REASON_SIGNALED,
+ REASON_LOW_MEMORY,
+ REASON_CRASH,
+ REASON_CRASH_NATIVE,
+ REASON_ANR,
+ REASON_INITIALIZATION_FAILURE,
+ REASON_PERMISSION_CHANGE,
+ REASON_EXCESSIVE_RESOURCE_USAGE,
+ REASON_OTHER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Reason {}
+
+ /** @hide */
+ @IntDef(prefix = { "SUBREASON_" }, value = {
+ SUBREASON_UNKNOWN,
+ SUBREASON_WAIT_FOR_DEBUGGER,
+ SUBREASON_TOO_MANY_CACHED,
+ SUBREASON_TOO_MANY_EMPTY,
+ SUBREASON_TRIM_EMPTY,
+ SUBREASON_LARGE_CACHED,
+ SUBREASON_MEMORY_PRESSURE,
+ SUBREASON_EXCESSIVE_CPU,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SubReason {}
+
+ /**
+ * The process id of the process that died.
+ */
+ public int getPid() {
+ return mPid;
+ }
+
+ /**
+ * The kernel user identifier of the process, most of the time the system uses this
+ * to do access control checks. It's typically the uid of the package where the component is
+ * running from, except the case of isolated process, where this field identifies the kernel
+ * user identifier that this process is actually running with, while the {@link #getPackageUid}
+ * identifies the kernel user identifier that is assigned at the package installation time.
+ */
+ public int getRealUid() {
+ return mRealUid;
+ }
+
+ /**
+ * Similar to {@link #getRealUid}, it's the kernel user identifier that is assigned at the
+ * package installation time.
+ */
+ public int getPackageUid() {
+ return mPackageUid;
+ }
+
+ /**
+ * Return the defining kernel user identifier, maybe different from {@link #getRealUid} and
+ * {@link #getPackageUid}, if an external service was bound with the flag
+ * {@link android.content.Context#BIND_EXTERNAL_SERVICE} - in this case, this field here
+ * will be the kernel user identifier of the external service provider.
+ */
+ public int getDefiningUid() {
+ return mDefiningUid;
+ }
+
+ /**
+ * The actual process name it was running with.
+ */
+ public @NonNull String getProcessName() {
+ return mProcessName;
+ }
+
+ /**
+ * The reason code of the process's death.
+ */
+ public @Reason int getReason() {
+ return mReason;
+ }
+
+ /*
+ * The exit status argument of exit() if the application calls it, or the signal
+ * number if the application is signaled.
+ */
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * The importance of the process that it used to have before the death.
+ */
+ public @Importance int getImportance() {
+ return mImportance;
+ }
+
+ /*
+ * Last proportional set size of the memory that the process had used in kB.
+ *
+ * <p class="note">Note: This is the value from last sampling on the process,
+ * it's NOT the exact memory information prior to its death; and it'll be zero
+ * if the process died before system had a chance to take the sample. </p>
+ */
+ public int getPss() {
+ return mPss;
+ }
+
+ /**
+ * Last resident set size of the memory that the process had used in kB.
+ *
+ * <p class="note">Note: This is the value from last sampling on the process,
+ * it's NOT the exact memory information prior to its death; and it'll be zero
+ * if the process died before system had a chance to take the sample. </p>
+ */
+ public int getRss() {
+ return mRss;
+ }
+
+ /**
+ * The timestamp of the process's death, in milliseconds since the epoch.
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * The human readable description of the process's death, given by the system; could be null.
+ */
+ public @Nullable String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Return the user id of the record on a multi-user system.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull UserHandle getUserHandle() {
+ return UserHandle.of(UserHandle.getUserId(mRealUid));
+ }
+
+ /**
+ * A subtype reason in conjunction with {@link #mReason}.
+ *
+ * For internal use only.
+ *
+ * @hide
+ */
+ public @SubReason int getSubReason() {
+ return mSubReason;
+ }
+
+ /**
+ * The connection group this process belongs to, if there is any.
+ * @see {@link android.content.Context#updateServiceGroup}.
+ *
+ * For internal use only.
+ *
+ * @hide
+ */
+ public int getConnectionGroup() {
+ return mConnectionGroup;
+ }
+
+ /**
+ * Name of first package running in this process;
+ *
+ * For system internal use only, will not retain across processes.
+ *
+ * @hide
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * List of packages running in this process;
+ *
+ * For system internal use only, will not retain across processes.
+ *
+ * @hide
+ */
+ public String[] getPackageList() {
+ return mPackageList;
+ }
+
+ /**
+ * @see {@link #getPid}
+ *
+ * @hide
+ */
+ public void setPid(final int pid) {
+ mPid = pid;
+ }
+
+ /**
+ * @see {@link #getRealUid}
+ *
+ * @hide
+ */
+ public void setRealUid(final int uid) {
+ mRealUid = uid;
+ }
+
+ /**
+ * @see {@link #getPackageUid}
+ *
+ * @hide
+ */
+ public void setPackageUid(final int uid) {
+ mPackageUid = uid;
+ }
+
+ /**
+ * @see {@link #getDefiningUid}
+ *
+ * @hide
+ */
+ public void setDefiningUid(final int uid) {
+ mDefiningUid = uid;
+ }
+
+ /**
+ * @see {@link #getProcessName}
+ *
+ * @hide
+ */
+ public void setProcessName(final String processName) {
+ mProcessName = processName;
+ }
+
+ /**
+ * @see {@link #getReason}
+ *
+ * @hide
+ */
+ public void setReason(final @Reason int reason) {
+ mReason = reason;
+ }
+
+ /**
+ * @see {@link #getStatus}
+ *
+ * @hide
+ */
+ public void setStatus(final int status) {
+ mStatus = status;
+ }
+
+ /**
+ * @see {@link #getImportance}
+ *
+ * @hide
+ */
+ public void setImportance(final @Importance int importance) {
+ mImportance = importance;
+ }
+
+ /**
+ * @see {@link #getPss}
+ *
+ * @hide
+ */
+ public void setPss(final int pss) {
+ mPss = pss;
+ }
+
+ /**
+ * @see {@link #getRss}
+ *
+ * @hide
+ */
+ public void setRss(final int rss) {
+ mRss = rss;
+ }
+
+ /**
+ * @see {@link #getTimestamp}
+ *
+ * @hide
+ */
+ public void setTimestamp(final long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * @see {@link #getDescription}
+ *
+ * @hide
+ */
+ public void setDescription(final String description) {
+ mDescription = description;
+ }
+
+ /**
+ * @see {@link #getSubReason}
+ *
+ * @hide
+ */
+ public void setSubReason(final @SubReason int subReason) {
+ mSubReason = subReason;
+ }
+
+ /**
+ * @see {@link #getConnectionGroup}
+ *
+ * @hide
+ */
+ public void setConnectionGroup(final int connectionGroup) {
+ mConnectionGroup = connectionGroup;
+ }
+
+ /**
+ * @see {@link #getPackageName}
+ *
+ * @hide
+ */
+ public void setPackageName(final String packageName) {
+ mPackageName = packageName;
+ }
+
+ /**
+ * @see {@link #getPackageList}
+ *
+ * @hide
+ */
+ public void setPackageList(final String[] packageList) {
+ mPackageList = packageList;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mPid);
+ dest.writeInt(mRealUid);
+ dest.writeInt(mPackageUid);
+ dest.writeInt(mDefiningUid);
+ dest.writeString(mProcessName);
+ dest.writeInt(mConnectionGroup);
+ dest.writeInt(mReason);
+ dest.writeInt(mSubReason);
+ dest.writeInt(mStatus);
+ dest.writeInt(mImportance);
+ dest.writeInt(mPss);
+ dest.writeInt(mRss);
+ dest.writeLong(mTimestamp);
+ dest.writeString(mDescription);
+ }
+
+ /** @hide */
+ public ApplicationExitInfo() {
+ }
+
+ /** @hide */
+ public ApplicationExitInfo(ApplicationExitInfo other) {
+ mPid = other.mPid;
+ mRealUid = other.mRealUid;
+ mPackageUid = other.mPackageUid;
+ mDefiningUid = other.mDefiningUid;
+ mProcessName = other.mProcessName;
+ mConnectionGroup = other.mConnectionGroup;
+ mReason = other.mReason;
+ mStatus = other.mStatus;
+ mSubReason = other.mSubReason;
+ mImportance = other.mImportance;
+ mPss = other.mPss;
+ mRss = other.mRss;
+ mTimestamp = other.mTimestamp;
+ mDescription = other.mDescription;
+ }
+
+ private ApplicationExitInfo(@NonNull Parcel in) {
+ mPid = in.readInt();
+ mRealUid = in.readInt();
+ mPackageUid = in.readInt();
+ mDefiningUid = in.readInt();
+ mProcessName = in.readString();
+ mConnectionGroup = in.readInt();
+ mReason = in.readInt();
+ mSubReason = in.readInt();
+ mStatus = in.readInt();
+ mImportance = in.readInt();
+ mPss = in.readInt();
+ mRss = in.readInt();
+ mTimestamp = in.readLong();
+ mDescription = in.readString();
+ }
+
+ public @NonNull static final Creator<ApplicationExitInfo> CREATOR =
+ new Creator<ApplicationExitInfo>() {
+ @Override
+ public ApplicationExitInfo createFromParcel(Parcel in) {
+ return new ApplicationExitInfo(in);
+ }
+
+ @Override
+ public ApplicationExitInfo[] newArray(int size) {
+ return new ApplicationExitInfo[size];
+ }
+ };
+
+ /** @hide */
+ public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix,
+ @NonNull SimpleDateFormat sdf) {
+ pw.println(prefix + "ApplicationExitInfo " + seqSuffix + ":");
+ pw.println(prefix + " timestamp=" + sdf.format(new Date(mTimestamp)));
+ pw.println(prefix + " pid=" + mPid);
+ pw.println(prefix + " realUid=" + mRealUid);
+ pw.println(prefix + " packageUid=" + mPackageUid);
+ pw.println(prefix + " definingUid=" + mDefiningUid);
+ pw.println(prefix + " user=" + UserHandle.getUserId(mPackageUid));
+ pw.println(prefix + " process=" + mProcessName);
+ pw.println(prefix + " reason=" + mReason + " (" + reasonCodeToString(mReason) + ")");
+ pw.println(prefix + " status=" + mStatus);
+ pw.println(prefix + " importance=" + mImportance);
+ pw.print(prefix + " pss="); DebugUtils.printSizeValue(pw, mPss << 10); pw.println();
+ pw.print(prefix + " rss="); DebugUtils.printSizeValue(pw, mRss << 10); pw.println();
+ pw.println(prefix + " description=" + mDescription);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ApplicationExitInfo(timestamp=");
+ sb.append(new SimpleDateFormat().format(new Date(mTimestamp)));
+ sb.append(" pid=").append(mPid);
+ sb.append(" realUid=").append(mRealUid);
+ sb.append(" packageUid=").append(mPackageUid);
+ sb.append(" definingUid=").append(mDefiningUid);
+ sb.append(" user=").append(UserHandle.getUserId(mPackageUid));
+ sb.append(" process=").append(mProcessName);
+ sb.append(" reason=").append(mReason).append(" (")
+ .append(reasonCodeToString(mReason)).append(")");
+ sb.append(" status=").append(mStatus);
+ sb.append(" importance=").append(mImportance);
+ sb.append(" pss="); DebugUtils.sizeValueToString(mPss << 10, sb);
+ sb.append(" rss="); DebugUtils.sizeValueToString(mRss << 10, sb);
+ sb.append(" description=").append(mDescription);
+ return sb.toString();
+ }
+
+ private static String reasonCodeToString(@Reason int reason) {
+ switch (reason) {
+ case REASON_EXIT_SELF:
+ return "EXIT_SELF";
+ case REASON_SIGNALED:
+ return "SIGNALED";
+ case REASON_LOW_MEMORY:
+ return "LOW_MEMORY";
+ case REASON_CRASH:
+ return "APP CRASH(EXCEPTION)";
+ case REASON_CRASH_NATIVE:
+ return "APP CRASH(NATIVE)";
+ case REASON_ANR:
+ return "ANR";
+ case REASON_INITIALIZATION_FAILURE:
+ return "INITIALIZATION FAILURE";
+ case REASON_PERMISSION_CHANGE:
+ return "PERMISSION CHANGE";
+ case REASON_EXCESSIVE_RESOURCE_USAGE:
+ return "EXCESSIVE RESOURCE USAGE";
+ case REASON_OTHER:
+ return "OTHER KILLS BY SYSTEM";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /** @hide */
+ public static String subreasonToString(@SubReason int subreason) {
+ switch (subreason) {
+ case SUBREASON_WAIT_FOR_DEBUGGER:
+ return "WAIT FOR DEBUGGER";
+ case SUBREASON_TOO_MANY_CACHED:
+ return "TOO MANY CACHED PROCS";
+ case SUBREASON_TOO_MANY_EMPTY:
+ return "TOO MANY EMPTY PROCS";
+ case SUBREASON_TRIM_EMPTY:
+ return "TRIM EMPTY";
+ case SUBREASON_LARGE_CACHED:
+ return "LARGE CACHED";
+ case SUBREASON_MEMORY_PRESSURE:
+ return "MEMORY PRESSURE";
+ case SUBREASON_EXCESSIVE_CPU:
+ return "EXCESSIVE CPU USAGE";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.app.ApplicationExitInfoProto}
+ *
+ * @param proto Stream to write the ApplicationExitInfo object to.
+ * @param fieldId Field Id of the ApplicationExitInfo as defined in the parent message
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(ApplicationExitInfoProto.PID, mPid);
+ proto.write(ApplicationExitInfoProto.REAL_UID, mRealUid);
+ proto.write(ApplicationExitInfoProto.PACKAGE_UID, mPackageUid);
+ proto.write(ApplicationExitInfoProto.DEFINING_UID, mDefiningUid);
+ proto.write(ApplicationExitInfoProto.PROCESS_NAME, mProcessName);
+ proto.write(ApplicationExitInfoProto.CONNECTION_GROUP, mConnectionGroup);
+ proto.write(ApplicationExitInfoProto.REASON, mReason);
+ proto.write(ApplicationExitInfoProto.SUB_REASON, mSubReason);
+ proto.write(ApplicationExitInfoProto.STATUS, mStatus);
+ proto.write(ApplicationExitInfoProto.IMPORTANCE, mImportance);
+ proto.write(ApplicationExitInfoProto.PSS, mPss);
+ proto.write(ApplicationExitInfoProto.RSS, mRss);
+ proto.write(ApplicationExitInfoProto.TIMESTAMP, mTimestamp);
+ proto.write(ApplicationExitInfoProto.DESCRIPTION, mDescription);
+ proto.end(token);
+ }
+
+ /**
+ * Read from a protocol buffer input stream.
+ * Protocol buffer message definition at {@link android.app.ApplicationExitInfoProto}
+ *
+ * @param proto Stream to read the ApplicationExitInfo object from.
+ * @param fieldId Field Id of the ApplicationExitInfo as defined in the parent message
+ * @hide
+ */
+ public void readFromProto(ProtoInputStream proto, long fieldId)
+ throws IOException, WireTypeMismatchException {
+ final long token = proto.start(fieldId);
+ while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (proto.getFieldNumber()) {
+ case (int) ApplicationExitInfoProto.PID:
+ mPid = proto.readInt(ApplicationExitInfoProto.PID);
+ break;
+ case (int) ApplicationExitInfoProto.REAL_UID:
+ mRealUid = proto.readInt(ApplicationExitInfoProto.REAL_UID);
+ break;
+ case (int) ApplicationExitInfoProto.PACKAGE_UID:
+ mPackageUid = proto.readInt(ApplicationExitInfoProto.PACKAGE_UID);
+ break;
+ case (int) ApplicationExitInfoProto.DEFINING_UID:
+ mDefiningUid = proto.readInt(ApplicationExitInfoProto.DEFINING_UID);
+ break;
+ case (int) ApplicationExitInfoProto.PROCESS_NAME:
+ mProcessName = proto.readString(ApplicationExitInfoProto.PROCESS_NAME);
+ break;
+ case (int) ApplicationExitInfoProto.CONNECTION_GROUP:
+ mConnectionGroup = proto.readInt(ApplicationExitInfoProto.CONNECTION_GROUP);
+ break;
+ case (int) ApplicationExitInfoProto.REASON:
+ mReason = proto.readInt(ApplicationExitInfoProto.REASON);
+ break;
+ case (int) ApplicationExitInfoProto.SUB_REASON:
+ mSubReason = proto.readInt(ApplicationExitInfoProto.SUB_REASON);
+ break;
+ case (int) ApplicationExitInfoProto.STATUS:
+ mStatus = proto.readInt(ApplicationExitInfoProto.STATUS);
+ break;
+ case (int) ApplicationExitInfoProto.IMPORTANCE:
+ mImportance = proto.readInt(ApplicationExitInfoProto.IMPORTANCE);
+ break;
+ case (int) ApplicationExitInfoProto.PSS:
+ mPss = proto.readInt(ApplicationExitInfoProto.PSS);
+ break;
+ case (int) ApplicationExitInfoProto.RSS:
+ mRss = proto.readInt(ApplicationExitInfoProto.RSS);
+ break;
+ case (int) ApplicationExitInfoProto.TIMESTAMP:
+ mTimestamp = proto.readLong(ApplicationExitInfoProto.TIMESTAMP);
+ break;
+ case (int) ApplicationExitInfoProto.DESCRIPTION:
+ mDescription = proto.readString(ApplicationExitInfoProto.DESCRIPTION);
+ break;
+ }
+ }
+ proto.end(token);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || !(other instanceof ApplicationExitInfo)) {
+ return false;
+ }
+ ApplicationExitInfo o = (ApplicationExitInfo) other;
+ return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid
+ && mDefiningUid == o.mDefiningUid
+ && mConnectionGroup == o.mConnectionGroup && mReason == o.mReason
+ && mSubReason == o.mSubReason && mImportance == o.mImportance
+ && mStatus == o.mStatus && mTimestamp == o.mTimestamp
+ && mPss == o.mPss && mRss == o.mRss
+ && TextUtils.equals(mProcessName, o.mProcessName)
+ && TextUtils.equals(mDescription, o.mDescription);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mPid;
+ result = 31 * result + mRealUid;
+ result = 31 * result + mPackageUid;
+ result = 31 * result + mDefiningUid;
+ result = 31 * result + mConnectionGroup;
+ result = 31 * result + mReason;
+ result = 31 * result + mSubReason;
+ result = 31 * result + mImportance;
+ result = 31 * result + mStatus;
+ result = 31 * result + mPss;
+ result = 31 * result + mRss;
+ result = 31 * result + Long.hashCode(mTimestamp);
+ result = 31 * result + Objects.hashCode(mProcessName);
+ result = 31 * result + Objects.hashCode(mDescription);
+ return result;
+ }
+}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 580382e0a4aa..126cc5a6f5c4 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -18,6 +18,7 @@ package android.app;
import android.app.ActivityManager;
import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
import android.app.ContentProviderHolder;
import android.app.GrantedUriPermission;
import android.app.IApplicationThread;
@@ -604,4 +605,30 @@ interface IActivityManager {
* Method for the app to tell system that it's wedged and would like to trigger an ANR.
*/
void appNotResponding(String reason);
+
+ /**
+ * Return a list of {@link ApplicationExitInfo} records.
+ *
+ * <p class="note"> Note: System stores these historical information in a ring buffer, older
+ * records would be overwritten by newer records. </p>
+ *
+ * <p class="note"> Note: In the case that this application bound to an external service with
+ * flag {@link android.content.Context#BIND_EXTERNAL_SERVICE}, the process of that external
+ * service will be included in this package's exit info. </p>
+ *
+ * @param packageName Optional, an empty value means match all packages belonging to the
+ * caller's UID. If this package belongs to another UID, you must hold
+ * {@link android.Manifest.permission#DUMP} in order to retrieve it.
+ * @param pid Optional, it could be a process ID that used to belong to this package but
+ * died later; A value of 0 means to ignore this parameter and return all
+ * matching records.
+ * @param maxNum Optional, the maximum number of results should be returned; A value of 0
+ * means to ignore this parameter and return all matching records
+ * @param userId The userId in the multi-user environment.
+ *
+ * @return a list of {@link ApplicationExitInfo} records with the matching criteria, sorted in
+ * the order from most recent to least recent.
+ */
+ ParceledListSlice<ApplicationExitInfo> getHistoricalProcessExitReasons(String packageName,
+ int pid, int maxNum, int userId);
}
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
new file mode 100644
index 000000000000..e23f150fab25
--- /dev/null
+++ b/core/proto/android/app/appexitinfo.proto
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.app;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+/**
+ * An android.app.ApplicationExitInfo object.
+ */
+message ApplicationExitInfoProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 pid = 1;
+ optional int32 real_uid = 2;
+ optional int32 package_uid = 3;
+ optional int32 defining_uid = 4;
+ optional string process_name = 5;
+ optional int32 connection_group = 6;
+
+ enum ReasonCode {
+ /**
+ * Application process died due to unknown reason.
+ */
+ REASON_UNKNOWN = 0;
+
+ /**
+ * Application process exit normally by itself, for example,
+ * via {@link android.os.Process#exit}; {@link #status} will specify the exit code.
+ *
+ * <p>Applications should normally not do this, as the system has a better knowledge
+ * in terms of process management.</p>
+ */
+ REASON_EXIT_SELF = 1;
+
+ /**
+ * Application process died due to the result of an OS signal; for example,
+ * {@link android.os.Process#SIGNAL_KILL}; {@link #status} will specify the signum.
+ */
+ REASON_SIGNALED = 2;
+
+ /**
+ * Application process was killed by the system low memory killer, meaning the system was
+ * under memory pressure at the time of kill.
+ */
+ REASON_LOW_MEMORY = 3;
+
+ /**
+ * Application process died because of an unhandled exception in Java code.
+ */
+ REASON_CRASH = 4;
+
+ /**
+ * Application process died because it's crashed due to a native code crash.
+ */
+ REASON_CRASH_NATIVE = 5;
+
+ /**
+ * Application process was killed due to being unresponsive (ANR).
+ */
+ REASON_ANR = 6;
+
+ /**
+ * Application process was killed because it took too long to attach to the system
+ * during the start.
+ */
+ REASON_INITIALIZATION_FAILURE = 7;
+
+ /**
+ * Application process was killed because of initialization failure,
+ * for example, it took too long to attach to the system during the start,
+ * or there was an error during initialization.
+ */
+ REASON_PERMISSION_CHANGE = 8;
+
+ /**
+ * Application process was killed by the activity manager due to excessive resource usage.
+ */
+ REASON_EXCESSIVE_RESOURCE_USAGE = 9;
+
+ /**
+ * Application process was killed by the system for various other reasons,
+ * for example, the application package got disabled by the user;
+ * {@link #description} will specify the cause given by the system.
+ */
+ REASON_OTHER = 10;
+
+ }
+ optional ReasonCode reason = 7;
+
+ enum SubReason {
+ /**
+ * Application process kills subReason is unknown.
+ */
+ SUBREASON_UNKNOWN = 0;
+
+ /**
+ * Application process was killed because user quit it on the "wait for debugger" dialog.
+ */
+ SUBREASON_WAIT_FOR_DEBUGGER = 1;
+
+ /**
+ * Application process was killed by the activity manager because there were too many cached
+ * processes.
+ */
+ SUBREASON_TOO_MANY_CACHED = 2;
+
+ /**
+ * Application process was killed by the activity manager because there were too many empty
+ * processes.
+ */
+ SUBREASON_TOO_MANY_EMPTY = 3;
+
+ /**
+ * Application process was killed by the activity manager because there were too many cached
+ * processes and this process had been in empty state for a long time.
+ */
+ SUBREASON_TRIM_EMPTY = 4;
+
+ /**
+ * Application process was killed by the activity manager because system was on
+ * memory pressure and this process took large amount of cached memory.
+ */
+ SUBREASON_LARGE_CACHED = 5;
+
+ /**
+ * Application process was killed by the activity manager because the system was on
+ * low memory pressure for a significant amount of time since last idle.
+ */
+ SUBREASON_MEMORY_PRESSURE = 6;
+
+ /**
+ * Application process was killed by the activity manager due to excessive CPU usage.
+ */
+ SUBREASON_EXCESSIVE_CPU = 7;
+ }
+
+ optional SubReason sub_reason = 8;
+
+ optional int32 status = 9;
+
+ enum Importance {
+ option allow_alias = true;
+ /**
+ * Keep sync with the definitions in
+ * {@link android.app.ActivityManager.RunningAppProcessInfo}
+ */
+ IMPORTANCE_FOREGROUND = 100;
+ IMPORTANCE_FOREGROUND_SERVICE = 125;
+ IMPORTANCE_TOP_SLEEPING_PRE_28 = 150;
+ IMPORTANCE_VISIBLE = 200;
+ IMPORTANCE_PERCEPTIBLE_PRE_26 = 130;
+ IMPORTANCE_PERCEPTIBLE = 230;
+ IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170;
+ IMPORTANCE_SERVICE = 300;
+ IMPORTANCE_TOP_SLEEPING = 325;
+ IMPORTANCE_CANT_SAVE_STATE = 350;
+ IMPORTANCE_CACHED = 400;
+ IMPORTANCE_BACKGROUND = 400;
+ IMPORTANCE_EMPTY = 500;
+ IMPORTANCE_GONE = 1000;
+ }
+
+ optional Importance importance = 10;
+ optional int32 pss = 11;
+ optional int32 rss = 12;
+ optional int64 timestamp = 13;
+ optional string description = 14;
+}
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index c79f314ffaf2..126d44529edc 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -19,6 +19,7 @@ syntax = "proto2";
package com.android.server.am;
import "frameworks/base/core/proto/android/app/activitymanager.proto";
+import "frameworks/base/core/proto/android/app/appexitinfo.proto";
import "frameworks/base/core/proto/android/app/enums.proto";
import "frameworks/base/core/proto/android/app/notification.proto";
import "frameworks/base/core/proto/android/app/profilerinfo.proto";
@@ -1082,3 +1083,23 @@ message AppTimeTrackerProto {
optional .android.util.Duration started_time = 4;
optional string started_package = 5;
}
+
+// sync with com.android.server.am.am.ProcessList.java
+message AppsExitInfoProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 last_update_timestamp = 1;
+ message Package {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string package_name = 1;
+ message User {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 uid = 1;
+ repeated .android.app.ApplicationExitInfoProto app_exit_info = 2;
+ }
+ repeated User users = 2;
+ }
+ repeated Package packages = 2;
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2585197d85ec..625eb63dab8f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4303,4 +4303,7 @@
<!-- Package name of the required service extension package. -->
<string name="config_servicesExtensionPackage" translatable="false">android.ext.services</string>
+
+ <!-- Retention policy: number of records to kept for the historical exit info per package. -->
+ <integer name="config_app_exit_info_history_list_size">16</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 379d0aa3da3d..fe7ebe145717 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3820,4 +3820,7 @@
<java-symbol type="string" name="capability_title_canTakeScreenshot" />
<java-symbol type="string" name="config_servicesExtensionPackage" />
+
+ <!-- For app process exit info tracking -->
+ <java-symbol type="integer" name="config_app_exit_info_history_list_size" />
</resources>
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index e2a036aec172..3ffa5dea4d89 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -36,6 +36,7 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.ApplicationExitInfo;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.Notification;
@@ -2730,7 +2731,7 @@ public final class ActiveServices {
created = true;
} catch (DeadObjectException e) {
Slog.w(TAG, "Application dead when creating service " + r);
- mAm.appDiedLocked(app);
+ mAm.appDiedLocked(app, "Died when creating service");
throw e;
} finally {
if (!created) {
@@ -3649,7 +3650,8 @@ public final class ActiveServices {
&& proc.pid != 0 && proc.pid != ActivityManagerService.MY_PID
&& proc.setProcState >= ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
proc.kill("bound to service " + sr.shortInstanceName
- + " in dying proc " + (app != null ? app.processName : "??"), true);
+ + " in dying proc " + (app != null ? app.processName : "??"),
+ ApplicationExitInfo.REASON_OTHER, true);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 883e7c6799dc..b8090dbcf809 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -155,6 +155,7 @@ import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
import android.app.ApplicationThreadConstants;
import android.app.BroadcastOptions;
import android.app.ContentProviderHolder;
@@ -684,7 +685,7 @@ public class ActivityManagerService extends IActivityManager.Stub
/**
* Process management.
*/
- final ProcessList mProcessList = new ProcessList();
+ final ProcessList mProcessList;
/**
* Tracking long-term execution of processes to look for abuse and other
@@ -1534,7 +1535,7 @@ public class ActivityManagerService extends IActivityManager.Stub
TAG, "Death received in " + this
+ " for thread " + mAppThread.asBinder());
synchronized(ActivityManagerService.this) {
- appDiedLocked(mApp, mPid, mAppThread, true);
+ appDiedLocked(mApp, mPid, mAppThread, true, null);
}
}
}
@@ -2382,6 +2383,7 @@ public class ActivityManagerService extends IActivityManager.Stub
? new ActivityManagerConstants(mContext, this, mHandler) : null;
final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
mPlatformCompat = null;
+ mProcessList = injector.getProcessList(this);
mProcessList.init(this, activeUids, mPlatformCompat);
mLowMemDetector = null;
mOomAdjuster = hasHandlerThread
@@ -2436,6 +2438,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */);
mPlatformCompat = (PlatformCompat) ServiceManager.getService(
Context.PLATFORM_COMPAT_SERVICE);
+ mProcessList = mInjector.getProcessList(this);
mProcessList.init(this, activeUids, mPlatformCompat);
mLowMemDetector = new LowMemDetector(this);
mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
@@ -3680,13 +3683,13 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@GuardedBy("this")
- final void appDiedLocked(ProcessRecord app) {
- appDiedLocked(app, app.pid, app.thread, false);
+ final void appDiedLocked(ProcessRecord app, String reason) {
+ appDiedLocked(app, app.pid, app.thread, false, reason);
}
@GuardedBy("this")
final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,
- boolean fromBinderDied) {
+ boolean fromBinderDied, String reason) {
// First check if this ProcessRecord is actually active for the pid.
synchronized (mPidsSelfLocked) {
ProcessRecord curProc = mPidsSelfLocked.get(pid);
@@ -3704,6 +3707,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!app.killed) {
if (!fromBinderDied) {
killProcessQuiet(pid);
+ mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
}
ProcessList.killProcessGroup(app.uid, pid);
app.killed = true;
@@ -4727,7 +4732,7 @@ public class ActivityManagerService extends IActivityManager.Stub
cleanupAppInLaunchingProvidersLocked(app, true);
// Take care of any services that are waiting for the process.
mServices.processStartTimedOutLocked(app);
- app.kill("start timeout", true);
+ app.kill("start timeout", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
if (app.isolated) {
mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
}
@@ -4814,6 +4819,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (pid > 0 && pid != MY_PID) {
killProcessQuiet(pid);
//TODO: killProcessGroup(app.info.uid, pid);
+ mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNKNOWN, "attach failed");
} else {
try {
thread.scheduleExit();
@@ -5153,7 +5160,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (badApp) {
- app.kill("error during init", true);
+ app.kill("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
handleAppDiedLocked(app, false, true);
return false;
}
@@ -7544,7 +7551,7 @@ public class ActivityManagerService extends IActivityManager.Stub
proc.info.uid);
final long ident = Binder.clearCallingIdentity();
try {
- appDiedLocked(proc);
+ appDiedLocked(proc, "unstable content provider");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -8856,7 +8863,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
int adj = proc.setAdj;
if (adj >= worstType && !proc.killedByAm) {
- proc.kill(reason, true);
+ proc.kill(reason, ApplicationExitInfo.REASON_OTHER, true);
killed = true;
}
}
@@ -8904,7 +8911,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final int adj = proc.setAdj;
if (adj > belowAdj && !proc.killedByAm) {
- proc.kill(reason, true);
+ proc.kill(reason, ApplicationExitInfo.REASON_PERMISSION_CHANGE, true);
killed = true;
}
}
@@ -9048,7 +9055,10 @@ public class ActivityManagerService extends IActivityManager.Stub
TimeUtils.formatDuration(lowRamSinceLastIdle, sb);
Slog.wtfQuiet(TAG, sb.toString());
proc.kill("idle maint (pss " + proc.lastPss
- + " from " + proc.initialIdlePss + ")", true);
+ + " from " + proc.initialIdlePss + ")",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_MEMORY_PRESSURE,
+ true);
}
}
} else if (proc.setProcState < ActivityManager.PROCESS_STATE_HOME
@@ -9145,6 +9155,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Make sure we have the current profile info, since it is needed for security checks.
mUserController.onSystemReady();
mAppOpsService.systemReady();
+ mProcessList.onSystemReady();
mSystemReady = true;
t.traceEnd();
}
@@ -9972,6 +9983,67 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public ParceledListSlice<ApplicationExitInfo> getHistoricalProcessExitReasons(
+ String packageName, int pid, int maxNum, int userId) {
+ enforceNotIsolatedCaller("getHistoricalProcessExitReasons");
+
+ // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+ if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+ throw new IllegalArgumentException("Unsupported userId");
+ }
+
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getCallingUserId();
+ mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL,
+ "getHistoricalProcessExitReasons", null);
+
+ final ArrayList<ApplicationExitInfo> results = new ArrayList<ApplicationExitInfo>();
+ if (!TextUtils.isEmpty(packageName)) {
+ final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
+ "getHistoricalProcessExitReasons");
+ if (uid != Process.INVALID_UID) {
+ mProcessList.mAppExitInfoTracker.getExitInfo(
+ packageName, uid, pid, maxNum, results);
+ }
+ } else {
+ // If no package name is given, use the caller's uid as the filter uid.
+ mProcessList.mAppExitInfoTracker.getExitInfo(
+ packageName, callingUid, pid, maxNum, results);
+ }
+
+ return new ParceledListSlice<ApplicationExitInfo>(results);
+ }
+
+ /**
+ * Check if the calling process has the permission to dump given package,
+ * throw SecurityException if it doesn't have the permission.
+ *
+ * @return The UID of the given package, or {@link android.os.Process#INVALID_UID}
+ * if the package is not found.
+ */
+ private int enforceDumpPermissionForPackage(String packageName, int userId, int callingUid,
+ String function) {
+ long identity = Binder.clearCallingIdentity();
+ int uid = Process.INVALID_UID;
+ try {
+ uid = mPackageManagerInt.getPackageUid(packageName,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ if (uid == Process.INVALID_UID) {
+ return Process.INVALID_UID;
+ }
+ if (UserHandle.getAppId(uid) != UserHandle.getAppId(callingUid)) {
+ // Requires the DUMP permission if the target package doesn't belong
+ // to the caller.
+ enforceCallingPermission(android.Manifest.permission.DUMP, function);
+ }
+ return uid;
+ }
+
+ @Override
public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outState) {
if (outState == null) {
throw new IllegalArgumentException("outState is null");
@@ -10150,6 +10222,12 @@ public class ActivityManagerService extends IActivityManager.Stub
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
+ mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------"
+ + "------------");
+ }
dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
pw.println();
if (dumpAll) {
@@ -10448,6 +10526,12 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized (this) {
dumpUsersLocked(pw);
}
+ } else if ("exit-info".equals(cmd)) {
+ if (opti < args.length) {
+ dumpPackage = args[opti];
+ opti++;
+ }
+ mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
} else {
// Dumping a single activity?
if (!mAtmInternal.dumpActivity(fd, pw, cmd, args, opti, dumpAll,
@@ -14061,7 +14145,9 @@ public class ActivityManagerService extends IActivityManager.Stub
capp.kill("depends on provider "
+ cpr.name.flattenToShortString()
+ " in dying proc " + (proc != null ? proc.processName : "??")
- + " (adj " + (proc != null ? proc.setAdj : "??") + ")", true);
+ + " (adj " + (proc != null ? proc.setAdj : "??") + ")",
+ ApplicationExitInfo.REASON_OTHER,
+ true);
}
} else if (capp.thread != null && conn.provider.provider != null) {
try {
@@ -14146,6 +14232,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Take care of any launching providers waiting for this process.
if (cleanupAppInLaunchingProvidersLocked(app, false)) {
+ mProcessList.noteProcessDiedLocked(app);
restart = true;
}
@@ -14242,6 +14329,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mProcessesOnHold.remove(app);
mAtmInternal.onCleanUpApplicationRecord(app.getWindowProcessController());
+ mProcessList.noteProcessDiedLocked(app);
if (restart && !app.isolated) {
// We have components that still need to be running in the
@@ -16968,7 +17056,10 @@ public class ActivityManagerService extends IActivityManager.Stub
uptimeSince, cputimeUsed);
}
app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
- + " dur=" + checkDur + " limit=" + cpuLimit, true);
+ + " dur=" + checkDur + " limit=" + cpuLimit,
+ ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+ ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
+ true);
app.baseProcessTracker.reportExcessiveCpu(app.pkgList.mPkgList);
for (int ipkg = app.pkgList.size() - 1; ipkg >= 0; ipkg--) {
ProcessStats.ProcessStateHolder holder = app.pkgList.valueAt(ipkg);
@@ -17602,7 +17693,10 @@ public class ActivityManagerService extends IActivityManager.Stub
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
- app.kill("empty", false);
+ app.kill("empty",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
+ false);
} else if (app.thread != null) {
try {
app.thread.scheduleExit();
@@ -18432,7 +18526,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final ProcessRecord pr = (ProcessRecord) wpc.mOwner;
if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
&& pr.curReceivers.isEmpty()) {
- pr.kill("remove task", true);
+ pr.kill("remove task", ApplicationExitInfo.REASON_OTHER, true);
} else {
// We delay killing processes that are not in the background or running a
// receiver.
@@ -19237,6 +19331,13 @@ public class ActivityManagerService extends IActivityManager.Stub
return false;
}
+ /**
+ * Return the process list instance
+ */
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return new ProcessList();
+ }
+
private boolean ensureHasNetworkManagementInternal() {
if (mNmi == null) {
mNmi = LocalServices.getService(NetworkManagementInternal.class);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d7ad1c252401..e7d6eb7bb46b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -91,7 +91,6 @@ import android.view.Display;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.util.HexDump;
import com.android.internal.util.MemInfoReader;
-import com.android.internal.util.Preconditions;
import com.android.server.compat.PlatformCompat;
import java.io.BufferedReader;
@@ -217,6 +216,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runSetWatchHeap(pw);
case "clear-watch-heap":
return runClearWatchHeap(pw);
+ case "clear-exit-info":
+ return runClearExitInfo(pw);
case "bug-report":
return runBugReport(pw);
case "force-stop":
@@ -1019,6 +1020,30 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
+ int runClearExitInfo(PrintWriter pw) throws RemoteException {
+ mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
+ "runClearExitInfo()");
+ String opt;
+ int userId = UserHandle.USER_CURRENT;
+ String packageName = null;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ packageName = opt;
+ }
+ }
+ if (userId == UserHandle.USER_CURRENT) {
+ UserInfo user = mInterface.getCurrentUser();
+ if (user == null) {
+ return -1;
+ }
+ userId = user.id;
+ }
+ mInternal.mProcessList.mAppExitInfoTracker.clearHistoryProcessExitInfo(packageName, userId);
+ return 0;
+ }
+
int runBugReport(PrintWriter pw) throws RemoteException {
String opt;
boolean fullBugreport = true;
@@ -1145,7 +1170,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
static final int RESULT_ANR_DIALOG = 0;
static final int RESULT_ANR_KILL = 1;
- static final int RESULT_ANR_WAIT = 1;
+ static final int RESULT_ANR_WAIT = 2;
int mResult;
@@ -3008,6 +3033,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" s[ervices] [COMP_SPEC ...]: service state");
pw.println(" allowed-associations: current package association restrictions");
pw.println(" as[sociations]: tracked app associations");
+ pw.println(" exit-info [PACKAGE_NAME]: historical process exit information");
pw.println(" lmk: stats on low memory killer");
pw.println(" lru: raw LRU process list");
pw.println(" binder-proxies: stats on binder objects and IPCs");
@@ -3142,6 +3168,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" above <HEAP-LIMIT> then a heap dump is collected for the user to report.");
pw.println(" clear-watch-heap");
pw.println(" Clear the previously set-watch-heap.");
+ pw.println(" clear-exit-info [--user <USER_ID> | all | current] [package]");
+ pw.println(" Clear the process exit-info for given package");
pw.println(" bug-report [--progress | --telephony]");
pw.println(" Request bug report generation; will launch a notification");
pw.println(" when done to select where it should be delivered. Options are:");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 8071f52037e6..145f91bdb0b3 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -29,6 +29,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_N
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -315,11 +316,23 @@ class AppErrors {
}
void killAppAtUserRequestLocked(ProcessRecord app) {
- app.getDialogController().clearAllErrorDialogs();
- killAppImmediateLocked(app, "user-terminated", "user request after error");
+ ProcessRecord.ErrorDialogController controller =
+ app.getDialogController();
+
+ int reasonCode = ApplicationExitInfo.REASON_ANR;
+ int subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+ if (controller.hasDebugWaitingDialog()) {
+ reasonCode = ApplicationExitInfo.REASON_OTHER;
+ subReason = ApplicationExitInfo.SUBREASON_WAIT_FOR_DEBUGGER;
+ }
+
+ controller.clearAllErrorDialogs();
+ killAppImmediateLocked(app, reasonCode, subReason,
+ "user-terminated", "user request after error");
}
- private void killAppImmediateLocked(ProcessRecord app, String reason, String killReason) {
+ private void killAppImmediateLocked(ProcessRecord app, int reasonCode, int subReason,
+ String reason, String killReason) {
app.setCrashing(false);
app.crashingReport = null;
app.setNotResponding(false);
@@ -327,7 +340,7 @@ class AppErrors {
if (app.pid > 0 && app.pid != MY_PID) {
handleAppCrashLocked(app, reason,
null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
- app.kill(killReason, true);
+ app.kill(killReason, reasonCode, subReason, true);
}
}
@@ -381,7 +394,9 @@ class AppErrors {
mService.mHandler.postDelayed(
() -> {
synchronized (mService) {
- killAppImmediateLocked(p, "forced", "killed for invalid state");
+ killAppImmediateLocked(p, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "forced", "killed for invalid state");
}
},
5000L);
@@ -422,6 +437,13 @@ class AppErrors {
if (r != null) {
mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ mService.mProcessList.noteAppKill(r, (crashInfo != null
+ && "Native crash".equals(crashInfo.exceptionClassName))
+ ? ApplicationExitInfo.REASON_CRASH_NATIVE
+ : ApplicationExitInfo.REASON_CRASH,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "crash");
}
final int relaunchReason = r != null
@@ -488,7 +510,8 @@ class AppErrors {
stopReportingCrashesLocked(r);
}
if (res == AppErrorDialog.RESTART) {
- mService.mProcessList.removeProcessLocked(r, false, true, "crash");
+ mService.mProcessList.removeProcessLocked(r, false, true, "crash",
+ ApplicationExitInfo.REASON_CRASH);
if (taskId != INVALID_TASK_ID) {
try {
mService.startActivityFromRecents(taskId,
@@ -506,7 +529,8 @@ class AppErrors {
// Kill it with fire!
mService.mAtmInternal.onHandleAppCrash(r.getWindowProcessController());
if (!r.isPersistent()) {
- mService.mProcessList.removeProcessLocked(r, false, false, "crash");
+ mService.mProcessList.removeProcessLocked(r, false, false, "crash",
+ ApplicationExitInfo.REASON_CRASH);
mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
}
} finally {
@@ -549,22 +573,26 @@ class AppErrors {
return mService.mAtmInternal.handleAppCrashInActivityController(
name, pid, shortMsg, longMsg, timeMillis, crashInfo.stackTrace, () -> {
- if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"))
- && "Native crash".equals(crashInfo.exceptionClassName)) {
- Slog.w(TAG, "Skip killing native crashed app " + name
- + "(" + pid + ") during testing");
- } else {
- Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request");
- if (r != null) {
- if (!makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, null)) {
- r.kill("crash", true);
- }
+ if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"))
+ && "Native crash".equals(crashInfo.exceptionClassName)) {
+ Slog.w(TAG, "Skip killing native crashed app " + name
+ + "(" + pid + ") during testing");
} else {
- // Huh.
- Process.killProcess(pid);
- ProcessList.killProcessGroup(uid, pid);
+ Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request");
+ if (r != null) {
+ if (!makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, null)) {
+ r.kill("crash", ApplicationExitInfo.REASON_CRASH, true);
+ }
+ } else {
+ // Huh.
+ Process.killProcess(pid);
+ ProcessList.killProcessGroup(uid, pid);
+ mService.mProcessList.noteAppKill(pid, uid,
+ ApplicationExitInfo.REASON_CRASH,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "crash");
+ }
}
- }
});
}
@@ -719,7 +747,8 @@ class AppErrors {
// Don't let services in this process be restarted and potentially
// annoy the user repeatedly. Unless it is persistent, since those
// processes run critical code.
- mService.mProcessList.removeProcessLocked(app, false, tryAgain, "crash");
+ mService.mProcessList.removeProcessLocked(app, false, tryAgain, "crash",
+ ApplicationExitInfo.REASON_CRASH);
mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
if (!showBackground) {
return false;
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
new file mode 100644
index 000000000000..6e135d65f127
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -0,0 +1,1302 @@
+/*
+ * Copyright (C) 2019 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.RunningAppProcessInfo.procStateToImportance;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationExitInfo.Reason;
+import android.app.ApplicationExitInfo.SubReason;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.SimpleDateFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.system.OsConstants;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Pair;
+import android.util.Pools.SynchronizedPool;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ProcessMap;
+import com.android.server.IoThread;
+import com.android.server.SystemServiceManager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+/**
+ * A class to manage all the {@link android.app.ApplicationExitInfo} records.
+ */
+public final class AppExitInfoTracker {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "AppExitInfoTracker" : TAG_AM;
+
+ /**
+ * Interval of persisting the app exit info to persistent storage.
+ */
+ private static final long APP_EXIT_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30);
+
+ /** These are actions that the forEachPackage should take after each iteration */
+ private static final int FOREACH_ACTION_NONE = 0;
+ private static final int FOREACH_ACTION_REMOVE_PACKAGE = 1;
+ private static final int FOREACH_ACTION_STOP_ITERATION = 2;
+
+ private static final int APP_EXIT_RAW_INFO_POOL_SIZE = 8;
+
+ @VisibleForTesting
+ static final String APP_EXIT_INFO_FILE = "procexitinfo";
+
+ private final Object mLock = new Object();
+
+ /**
+ * Initialized in {@link #init} and read-only after that.
+ */
+ private ActivityManagerService mService;
+
+ /**
+ * Initialized in {@link #init} and read-only after that.
+ */
+ private KillHandler mKillHandler;
+
+ /**
+ * The task to persist app process exit info
+ */
+ @GuardedBy("mLock")
+ private Runnable mAppExitInfoPersistTask = null;
+
+ /**
+ * Last time(in ms) since epoch that the app exit info was persisted into persistent storage.
+ */
+ @GuardedBy("mLock")
+ private long mLastAppExitInfoPersistTimestamp = 0L;
+
+ /**
+ * Retention policy: keep up to X historical exit info per package.
+ *
+ * Initialized in {@link #init} and read-only after that.
+ * Not lock is needed.
+ */
+ private int mAppExitInfoHistoryListSize;
+
+ /*
+ * PackageName/uid -> [pid/info, ...] holder, the uid here is the package uid.
+ */
+ @GuardedBy("mLock")
+ private final ProcessMap<AppExitInfoContainer> mData;
+
+ /** A pool of raw {@link android.app.ApplicationExitInfo} records. */
+ @GuardedBy("mService")
+ private final SynchronizedPool<ApplicationExitInfo> mRawRecordsPool;
+
+ /**
+ * Wheather or not we've loaded the historical app process exit info from
+ * persistent storage.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ boolean mAppExitInfoLoaded = false;
+
+ /**
+ * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}.
+ */
+ @GuardedBy("mLock")
+ final ArrayList<ApplicationExitInfo> mTmpInfoList = new ArrayList<ApplicationExitInfo>();
+
+ /**
+ * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}.
+ */
+ @GuardedBy("mLock")
+ final ArrayList<ApplicationExitInfo> mTmpInfoList2 = new ArrayList<ApplicationExitInfo>();
+
+ /**
+ * The path to the historical proc exit info file, persisted in the storage.
+ */
+ @VisibleForTesting
+ File mProcExitInfoFile;
+
+ /**
+ * Mapping between the isolated UID to its application uid.
+ */
+ final IsolatedUidRecords mIsolatedUidRecords =
+ new IsolatedUidRecords();
+
+ /**
+ * Bookkeeping app process exit info from Zygote.
+ */
+ final AppExitInfoExternalSource mAppExitInfoSourceZygote =
+ new AppExitInfoExternalSource("zygote", null);
+
+ /**
+ * Bookkeeping low memory kills info from lmkd.
+ */
+ final AppExitInfoExternalSource mAppExitInfoSourceLmkd =
+ new AppExitInfoExternalSource("lmkd", ApplicationExitInfo.REASON_LOW_MEMORY);
+
+ AppExitInfoTracker() {
+ mData = new ProcessMap<AppExitInfoContainer>();
+ mRawRecordsPool = new SynchronizedPool<ApplicationExitInfo>(APP_EXIT_RAW_INFO_POOL_SIZE);
+ }
+
+ void init(ActivityManagerService service, Looper looper) {
+ mService = service;
+ mKillHandler = new KillHandler(looper);
+ mProcExitInfoFile = new File(SystemServiceManager.ensureSystemDir(), APP_EXIT_INFO_FILE);
+
+ mAppExitInfoHistoryListSize = service.mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_app_exit_info_history_list_size);
+ }
+
+ void onSystemReady() {
+ // Read the sysprop set by lmkd and set this to persist so app could read it.
+ SystemProperties.set("persist.sys.lmk.reportkills",
+ Boolean.toString(SystemProperties.getBoolean("sys.lmk.reportkills", false)));
+ registerForUserRemoval();
+ registerForPackageRemoval();
+ IoThread.getHandler().post(this::loadExistingProcessExitInfo);
+ }
+
+ @GuardedBy("mService")
+ void scheduleNoteProcessDiedLocked(final ProcessRecord app) {
+ if (app == null || app.info == null) {
+ return;
+ }
+
+ synchronized (mLock) {
+ if (!mAppExitInfoLoaded) {
+ return;
+ }
+ }
+ // The current thread is holding the global lock, let's extract the info from it
+ // and schedule the info note task in the kill handler.
+ mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecordLocked(app))
+ .sendToTarget();
+ }
+
+ void scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason,
+ final @SubReason int subReason, final String msg) {
+ synchronized (mLock) {
+ if (!mAppExitInfoLoaded) {
+ return;
+ }
+ }
+ synchronized (mService) {
+ if (app == null || app.info == null) {
+ return;
+ }
+
+ ApplicationExitInfo raw = obtainRawRecordLocked(app);
+ raw.setReason(reason);
+ raw.setSubReason(subReason);
+ raw.setDescription(msg);
+ mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
+ }
+ }
+
+ void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
+ final @SubReason int subReason, final String msg) {
+ synchronized (mLock) {
+ if (!mAppExitInfoLoaded) {
+ return;
+ }
+ }
+ synchronized (mService) {
+ ProcessRecord app;
+ synchronized (mService.mPidsSelfLocked) {
+ app = mService.mPidsSelfLocked.get(pid);
+ }
+ if (app == null) {
+ if (DEBUG_PROCESSES) {
+ Slog.w(TAG, "Skipping saving the kill reason for pid " + pid
+ + "(uid=" + uid + ") since its process record is not found");
+ }
+ } else {
+ scheduleNoteAppKill(app, reason, subReason, msg);
+ }
+ }
+ }
+
+ interface LmkdKillListener {
+ /**
+ * Called when there is a process kill by lmkd.
+ */
+ void onLmkdKillOccurred(int pid, int uid);
+ }
+
+ void setLmkdKillListener(final LmkdKillListener listener) {
+ synchronized (mLock) {
+ mAppExitInfoSourceLmkd.setOnProcDiedListener((pid, uid) ->
+ listener.onLmkdKillOccurred(pid, uid));
+ }
+ }
+
+ /** Called when there is a low memory kill */
+ void scheduleNoteLmkdProcKilled(final int pid, final int uid) {
+ mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid)
+ .sendToTarget();
+ }
+
+ private void scheduleChildProcDied(int pid, int uid, int status) {
+ mKillHandler.obtainMessage(KillHandler.MSG_CHILD_PROC_DIED, pid, uid, (Integer) status)
+ .sendToTarget();
+ }
+
+ /** Calls when zygote sends us SIGCHLD */
+ void handleZygoteSigChld(int pid, int uid, int status) {
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "Got SIGCHLD from zygote: pid=" + pid + ", uid=" + uid
+ + ", status=" + Integer.toHexString(status));
+ }
+ scheduleChildProcDied(pid, uid, status);
+ }
+
+ /**
+ * Main routine to create or update the {@link android.app.ApplicationExitInfo} for the given
+ * ProcessRecord, also query the zygote and lmkd records to make the information more accurate.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void handleNoteProcessDiedLocked(final ApplicationExitInfo raw) {
+ if (raw != null) {
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "Update process exit info for " + raw.getPackageName()
+ + "(" + raw.getPid() + "/u" + raw.getRealUid() + ")");
+ }
+
+ ApplicationExitInfo info = getExitInfo(raw.getPackageName(),
+ raw.getPackageUid(), raw.getPid());
+
+ // query zygote and lmkd to get the exit info, and clear the saved info
+ Pair<Long, Object> zygote = mAppExitInfoSourceZygote.remove(
+ raw.getPid(), raw.getRealUid());
+ Pair<Long, Object> lmkd = mAppExitInfoSourceLmkd.remove(
+ raw.getPid(), raw.getRealUid());
+ mIsolatedUidRecords.removeIsolatedUid(raw.getRealUid());
+
+ if (info == null) {
+ info = addExitInfoLocked(raw);
+ }
+
+ if (lmkd != null) {
+ updateExistingExitInfoRecordLocked(info, null,
+ ApplicationExitInfo.REASON_LOW_MEMORY);
+ } else if (zygote != null) {
+ updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null);
+ }
+ }
+ }
+
+ /**
+ * Make note when ActivityManagerService decides to kill an application process.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void handleNoteAppKillLocked(final ApplicationExitInfo raw) {
+ ApplicationExitInfo info = getExitInfo(
+ raw.getPackageName(), raw.getPackageUid(), raw.getPid());
+
+ if (info == null) {
+ addExitInfoLocked(raw);
+ } else {
+ // always override the existing info since we are now more informational.
+ info.setReason(raw.getReason());
+ info.setStatus(0);
+ info.setTimestamp(System.currentTimeMillis());
+ info.setDescription(raw.getDescription());
+ }
+ }
+
+ @GuardedBy("mLock")
+ private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
+ if (!mAppExitInfoLoaded) {
+ Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
+ return null;
+ }
+
+ final ApplicationExitInfo info = new ApplicationExitInfo(raw);
+ final String[] packages = raw.getPackageList();
+ final int uid = raw.getPackageUid();
+ for (int i = 0; i < packages.length; i++) {
+ addExitInfoInner(packages[i], uid, info);
+ }
+
+ schedulePersistProcessExitInfo(false);
+
+ return info;
+ }
+
+ /**
+ * Update an existing {@link android.app.ApplicationExitInfo} record with given information.
+ */
+ @GuardedBy("mLock")
+ private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info,
+ Integer status, Integer reason) {
+ if (info == null || !isFresh(info.getTimestamp())) {
+ // if the record is way outdated, don't update it then (because of potential pid reuse)
+ return;
+ }
+ if (status != null) {
+ if (OsConstants.WIFEXITED(status)) {
+ info.setReason(ApplicationExitInfo.REASON_EXIT_SELF);
+ info.setStatus(OsConstants.WEXITSTATUS(status));
+ } else if (OsConstants.WIFSIGNALED(status)) {
+ if (info.getReason() == ApplicationExitInfo.REASON_UNKNOWN) {
+ info.setReason(ApplicationExitInfo.REASON_SIGNALED);
+ info.setStatus(OsConstants.WTERMSIG(status));
+ } else if (info.getReason() == ApplicationExitInfo.REASON_CRASH_NATIVE) {
+ info.setStatus(OsConstants.WTERMSIG(status));
+ }
+ }
+ }
+ if (reason != null) {
+ info.setReason(reason);
+ }
+ }
+
+ /**
+ * Update an existing {@link android.app.ApplicationExitInfo} record with given information.
+ *
+ * @return true if a recond is updated
+ */
+ private boolean updateExitInfoIfNecessary(int pid, int uid, Integer status, Integer reason) {
+ synchronized (mLock) {
+ if (UserHandle.isIsolated(uid)) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+ if (k != null) {
+ uid = k;
+ }
+ }
+ ArrayList<ApplicationExitInfo> tlist = mTmpInfoList;
+ tlist.clear();
+ final int targetUid = uid;
+ forEachPackage((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) {
+ 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;
+ });
+ return tlist.size() > 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) {
+ synchronized (mLock) {
+ boolean emptyPackageName = TextUtils.isEmpty(packageName);
+ if (!emptyPackageName) {
+ // fast path
+ AppExitInfoContainer container = mData.get(packageName, filterUid);
+ if (container != null) {
+ container.getExitInfoLocked(filterPid, maxNum, results);
+ }
+ } else {
+ // slow path
+ final ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
+ list.clear();
+ // get all packages
+ forEachPackage((name, records) -> {
+ AppExitInfoContainer container = records.get(filterUid);
+ if (container != null) {
+ mTmpInfoList.clear();
+ results.addAll(container.toListLocked(mTmpInfoList, filterPid));
+ }
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ });
+
+ Collections.sort(list, (a, b) -> (int) (b.getTimestamp() - a.getTimestamp()));
+ int size = list.size();
+ if (maxNum > 0) {
+ size = Math.min(size, maxNum);
+ }
+ for (int i = 0; i < size; i++) {
+ results.add(list.get(i));
+ }
+ list.clear();
+ }
+ }
+ }
+
+ /**
+ * Return the first matching exit info record, for internal use, the parameters are not supposed
+ * to be empty.
+ */
+ private ApplicationExitInfo getExitInfo(final String packageName,
+ final int filterUid, final int filterPid) {
+ synchronized (mLock) {
+ ArrayList<ApplicationExitInfo> list = mTmpInfoList;
+ list.clear();
+ getExitInfo(packageName, filterUid, filterPid, 1, list);
+
+ ApplicationExitInfo info = list.size() > 0 ? list.get(0) : null;
+ list.clear();
+ return info;
+ }
+ }
+
+ @VisibleForTesting
+ void onUserRemoved(int userId) {
+ mAppExitInfoSourceZygote.removeByUserId(userId);
+ mAppExitInfoSourceLmkd.removeByUserId(userId);
+ mIsolatedUidRecords.removeByUserId(userId);
+ removeByUserId(userId);
+ schedulePersistProcessExitInfo(true);
+ }
+
+ @VisibleForTesting
+ void onPackageRemoved(String packageName, int uid, boolean allUsers) {
+ if (packageName != null) {
+ mAppExitInfoSourceZygote.removeByUid(uid, allUsers);
+ mAppExitInfoSourceLmkd.removeByUid(uid, allUsers);
+ mIsolatedUidRecords.removeAppUid(uid, allUsers);
+ removePackage(packageName, allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid));
+ schedulePersistProcessExitInfo(true);
+ }
+ }
+
+ private void registerForUserRemoval() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId < 1) return;
+ onUserRemoved(userId);
+ }
+ }, filter, null, mKillHandler);
+ }
+
+ private void registerForPackageRemoval() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
+ boolean allUsers = intent.getBooleanExtra(
+ Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);
+ onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers);
+ }
+ }, filter, null, mKillHandler);
+ }
+
+ /**
+ * Load the existing {@link android.app.ApplicationExitInfo} records from persistent storage.
+ */
+ @VisibleForTesting
+ void loadExistingProcessExitInfo() {
+ if (!mProcExitInfoFile.canRead()) {
+ synchronized (mLock) {
+ mAppExitInfoLoaded = true;
+ }
+ return;
+ }
+
+ FileInputStream fin = null;
+ try {
+ AtomicFile af = new AtomicFile(mProcExitInfoFile);
+ fin = af.openRead();
+ ProtoInputStream proto = new ProtoInputStream(fin);
+ for (int next = proto.nextField();
+ next != ProtoInputStream.NO_MORE_FIELDS;
+ next = proto.nextField()) {
+ switch (next) {
+ case (int) AppsExitInfoProto.LAST_UPDATE_TIMESTAMP:
+ synchronized (mLock) {
+ mLastAppExitInfoPersistTimestamp =
+ proto.readLong(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP);
+ }
+ break;
+ case (int) AppsExitInfoProto.PACKAGES:
+ loadPackagesFromProto(proto, next);
+ break;
+ }
+ }
+ } catch (IOException | IllegalArgumentException | WireTypeMismatchException e) {
+ Slog.w(TAG, "Error in loading historical app exit info from persistent storage: " + e);
+ } finally {
+ if (fin != null) {
+ try {
+ fin.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ synchronized (mLock) {
+ mAppExitInfoLoaded = true;
+ }
+ }
+
+ private void loadPackagesFromProto(ProtoInputStream proto, long fieldId)
+ throws IOException, WireTypeMismatchException {
+ long token = proto.start(fieldId);
+ String pkgName = "";
+ for (int next = proto.nextField();
+ next != ProtoInputStream.NO_MORE_FIELDS;
+ next = proto.nextField()) {
+ switch (next) {
+ case (int) AppsExitInfoProto.Package.PACKAGE_NAME:
+ pkgName = proto.readString(AppsExitInfoProto.Package.PACKAGE_NAME);
+ break;
+ case (int) AppsExitInfoProto.Package.USERS:
+ AppExitInfoContainer container = new AppExitInfoContainer(
+ mAppExitInfoHistoryListSize);
+ int uid = container.readFromProto(proto, AppsExitInfoProto.Package.USERS);
+ synchronized (mLock) {
+ mData.put(pkgName, uid, container);
+ }
+ break;
+ }
+ }
+ proto.end(token);
+ }
+
+ /**
+ * Persist the existing {@link android.app.ApplicationExitInfo} records to storage.
+ */
+ @VisibleForTesting
+ void persistProcessExitInfo() {
+ AtomicFile af = new AtomicFile(mProcExitInfoFile);
+ FileOutputStream out = null;
+ long now = System.currentTimeMillis();
+ try {
+ out = af.startWrite();
+ ProtoOutputStream proto = new ProtoOutputStream(out);
+ proto.write(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP, now);
+ synchronized (mLock) {
+ forEachPackage((packageName, records) -> {
+ long token = proto.start(AppsExitInfoProto.PACKAGES);
+ proto.write(AppsExitInfoProto.Package.PACKAGE_NAME, packageName);
+ int uidArraySize = records.size();
+ for (int j = 0; j < uidArraySize; j++) {
+ records.valueAt(j).writeToProto(proto, AppsExitInfoProto.Package.USERS);
+ }
+ proto.end(token);
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ });
+ mLastAppExitInfoPersistTimestamp = now;
+ }
+ proto.flush();
+ af.finishWrite(out);
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to write historical app exit info into persistent storage: " + e);
+ af.failWrite(out);
+ }
+ synchronized (mLock) {
+ mAppExitInfoPersistTask = null;
+ }
+ }
+
+ /**
+ * Schedule a task to persist the {@link android.app.ApplicationExitInfo} records to storage.
+ */
+ @VisibleForTesting
+ void schedulePersistProcessExitInfo(boolean immediately) {
+ synchronized (mLock) {
+ if (mAppExitInfoPersistTask == null || immediately) {
+ if (mAppExitInfoPersistTask != null) {
+ IoThread.getHandler().removeCallbacks(mAppExitInfoPersistTask);
+ }
+ mAppExitInfoPersistTask = this::persistProcessExitInfo;
+ IoThread.getHandler().postDelayed(mAppExitInfoPersistTask,
+ immediately ? 0 : APP_EXIT_INFO_PERSIST_INTERVAL);
+ }
+ }
+ }
+
+ /**
+ * Helper function for testing only.
+ */
+ @VisibleForTesting
+ void clearProcessExitInfo(boolean removeFile) {
+ synchronized (mLock) {
+ if (mAppExitInfoPersistTask != null) {
+ IoThread.getHandler().removeCallbacks(mAppExitInfoPersistTask);
+ mAppExitInfoPersistTask = null;
+ }
+ if (removeFile && mProcExitInfoFile != null) {
+ mProcExitInfoFile.delete();
+ }
+ mData.getMap().clear();
+ }
+ }
+
+ /**
+ * Helper function for shell command
+ */
+ void clearHistoryProcessExitInfo(String packageName, int userId) {
+ synchronized (mLock) {
+ if (TextUtils.isEmpty(packageName)) {
+ if (userId == UserHandle.USER_ALL) {
+ mData.getMap().clear();
+ } else {
+ removeByUserId(userId);
+ }
+ } else {
+ removePackage(packageName, userId);
+ }
+ }
+ schedulePersistProcessExitInfo(true);
+ }
+
+ void dumpHistoryProcessExitInfo(PrintWriter pw, String packageName) {
+ pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity exit-info)");
+ SimpleDateFormat sdf = new SimpleDateFormat();
+ synchronized (mLock) {
+ pw.println("Last Timestamp of Persistence Into Persistent Storage: "
+ + sdf.format(new Date(mLastAppExitInfoPersistTimestamp)));
+ if (TextUtils.isEmpty(packageName)) {
+ forEachPackage((name, records) -> {
+ dumpHistoryProcessExitInfoLocked(pw, " ", name, records, sdf);
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ });
+ } else {
+ SparseArray<AppExitInfoContainer> array = mData.getMap().get(packageName);
+ if (array != null) {
+ dumpHistoryProcessExitInfoLocked(pw, " ", packageName, array, sdf);
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void dumpHistoryProcessExitInfoLocked(PrintWriter pw, String prefix,
+ String packageName, SparseArray<AppExitInfoContainer> array,
+ SimpleDateFormat sdf) {
+ pw.println(prefix + "package: " + packageName);
+ int size = array.size();
+ for (int i = 0; i < size; i++) {
+ pw.println(prefix + " Historical Process Exit for userId=" + array.keyAt(i));
+ array.valueAt(i).dumpLocked(pw, prefix + " ", sdf);
+ }
+ }
+
+ private void addExitInfoInner(String packageName, int userId, ApplicationExitInfo info) {
+ synchronized (mLock) {
+ AppExitInfoContainer container = mData.get(packageName, userId);
+ if (container == null) {
+ container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
+ if (UserHandle.isIsolated(info.getRealUid())) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(info.getRealUid());
+ if (k != null) {
+ container.mUid = k;
+ }
+ } else {
+ container.mUid = info.getRealUid();
+ }
+ mData.put(packageName, userId, container);
+ }
+ container.addExitInfoLocked(info);
+ }
+ }
+
+ private void forEachPackage(
+ BiFunction<String, SparseArray<AppExitInfoContainer>, Integer> callback) {
+ if (callback != null) {
+ synchronized (mLock) {
+ ArrayMap<String, SparseArray<AppExitInfoContainer>> map = mData.getMap();
+ for (int i = map.size() - 1; i >= 0; i--) {
+ switch (callback.apply(map.keyAt(i), map.valueAt(i))) {
+ case FOREACH_ACTION_REMOVE_PACKAGE:
+ map.removeAt(i);
+ break;
+ case FOREACH_ACTION_STOP_ITERATION:
+ i = 0;
+ break;
+ case FOREACH_ACTION_NONE:
+ default:
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private void removePackage(String packageName, int userId) {
+ synchronized (mLock) {
+ if (userId == UserHandle.USER_ALL) {
+ mData.getMap().remove(packageName);
+ } else {
+ ArrayMap<String, SparseArray<AppExitInfoContainer>> map =
+ mData.getMap();
+ SparseArray<AppExitInfoContainer> array = map.get(packageName);
+ if (array == null) {
+ return;
+ }
+ for (int i = array.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(array.keyAt(i)) == userId) {
+ array.removeAt(i);
+ break;
+ }
+ }
+ if (array.size() == 0) {
+ map.remove(packageName);
+ }
+ }
+ }
+ }
+
+ private void removeByUserId(final int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ synchronized (mLock) {
+ mData.getMap().clear();
+ }
+ return;
+ }
+ forEachPackage((packageName, records) -> {
+ for (int i = records.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(records.keyAt(i)) == userId) {
+ records.removeAt(i);
+ break;
+ }
+ }
+ return records.size() == 0 ? FOREACH_ACTION_REMOVE_PACKAGE : FOREACH_ACTION_NONE;
+ });
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mService")
+ ApplicationExitInfo obtainRawRecordLocked(ProcessRecord app) {
+ ApplicationExitInfo info = mRawRecordsPool.acquire();
+ if (info == null) {
+ info = new ApplicationExitInfo();
+ }
+
+ final int definingUid = app.hostingRecord != null ? app.hostingRecord.getDefiningUid() : 0;
+ info.setPid(app.pid);
+ info.setRealUid(app.uid);
+ info.setPackageUid(app.info.uid);
+ info.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
+ info.setProcessName(app.processName);
+ info.setConnectionGroup(app.connectionGroup);
+ info.setPackageName(app.info.packageName);
+ info.setPackageList(app.getPackageList());
+ info.setReason(ApplicationExitInfo.REASON_UNKNOWN);
+ info.setStatus(0);
+ info.setImportance(procStateToImportance(app.setProcState));
+ info.setPss(app.lastMemInfo == null ? 0 : app.lastMemInfo.getTotalPss());
+ info.setRss(app.lastMemInfo == null ? 0 : app.lastMemInfo.getTotalRss());
+ info.setTimestamp(System.currentTimeMillis());
+
+ return info;
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mService")
+ void recycleRawRecordLocked(ApplicationExitInfo info) {
+ info.setProcessName(null);
+ info.setDescription(null);
+ info.setPackageList(null);
+
+ mRawRecordsPool.release(info);
+ }
+
+ /**
+ * A container class of {@link android.app.ApplicationExitInfo}
+ */
+ final class AppExitInfoContainer {
+ private SparseArray<ApplicationExitInfo> mInfos; // index is pid
+ private int mMaxCapacity;
+ private int mUid; // Application uid, not isolated uid.
+
+ AppExitInfoContainer(final int maxCapacity) {
+ mInfos = new SparseArray<ApplicationExitInfo>();
+ mMaxCapacity = maxCapacity;
+ }
+
+ @GuardedBy("mLock")
+ void getExitInfoLocked(final int filterPid, final int maxNum,
+ ArrayList<ApplicationExitInfo> results) {
+ if (filterPid > 0) {
+ ApplicationExitInfo r = mInfos.get(filterPid);
+ if (r != null) {
+ results.add(r);
+ }
+ } else {
+ final int numRep = mInfos.size();
+ if (maxNum <= 0 || numRep <= maxNum) {
+ // Return all records.
+ for (int i = 0; i < numRep; i++) {
+ results.add(mInfos.valueAt(i));
+ }
+ Collections.sort(results,
+ (a, b) -> (int) (b.getTimestamp() - a.getTimestamp()));
+ } else {
+ if (maxNum == 1) {
+ // Most of the caller might be only interested with the most recent one
+ ApplicationExitInfo r = mInfos.valueAt(0);
+ for (int i = 1; i < numRep; i++) {
+ ApplicationExitInfo t = mInfos.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(mInfos.valueAt(i));
+ }
+ Collections.sort(list,
+ (a, b) -> (int) (b.getTimestamp() - a.getTimestamp()));
+ for (int i = 0; i < maxNum; i++) {
+ results.add(list.get(i));
+ }
+ list.clear();
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void addExitInfoLocked(ApplicationExitInfo info) {
+ int size;
+ if ((size = mInfos.size()) >= mMaxCapacity) {
+ int oldestIndex = -1;
+ long oldestTimeStamp = Long.MAX_VALUE;
+ for (int i = 0; i < size; i++) {
+ ApplicationExitInfo r = mInfos.valueAt(i);
+ if (r.getTimestamp() < oldestTimeStamp) {
+ oldestTimeStamp = r.getTimestamp();
+ oldestIndex = i;
+ }
+ }
+ if (oldestIndex >= 0) {
+ mInfos.removeAt(oldestIndex);
+ }
+ }
+ mInfos.append(info.getPid(), info);
+ }
+
+ @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));
+ }
+ Collections.sort(list, (a, b) -> (int) (b.getTimestamp() - a.getTimestamp()));
+ int size = list.size();
+ for (int i = 0; i < size; i++) {
+ list.get(i).dump(pw, prefix + " ", "#" + i, sdf);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(AppsExitInfoProto.Package.User.UID, mUid);
+ int size = mInfos.size();
+ for (int i = 0; i < size; i++) {
+ mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
+ }
+ proto.end(token);
+ }
+
+ int readFromProto(ProtoInputStream proto, long fieldId)
+ throws IOException, WireTypeMismatchException {
+ long token = proto.start(fieldId);
+ for (int next = proto.nextField();
+ next != ProtoInputStream.NO_MORE_FIELDS;
+ next = proto.nextField()) {
+ switch (next) {
+ case (int) AppsExitInfoProto.Package.User.UID:
+ mUid = proto.readInt(AppsExitInfoProto.Package.User.UID);
+ break;
+ 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;
+ }
+ }
+ 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));
+ }
+ }
+ return list;
+ }
+ }
+
+ /**
+ * Maintains the mapping between real UID and the application uid.
+ */
+ final class IsolatedUidRecords {
+ /**
+ * A mapping from application uid (with the userId) to isolated uids.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<ArraySet<Integer>> mUidToIsolatedUidMap;
+
+ /**
+ * A mapping from isolated uids to application uid (with the userId)
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<Integer> mIsolatedUidToUidMap;
+
+ IsolatedUidRecords() {
+ mUidToIsolatedUidMap = new SparseArray<ArraySet<Integer>>();
+ mIsolatedUidToUidMap = new SparseArray<Integer>();
+ }
+
+ void addIsolatedUid(int isolatedUid, int uid) {
+ synchronized (mLock) {
+ ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
+ if (set == null) {
+ set = new ArraySet<Integer>();
+ mUidToIsolatedUidMap.put(uid, set);
+ }
+ set.add(isolatedUid);
+
+ mIsolatedUidToUidMap.put(isolatedUid, uid);
+ }
+ }
+
+ Integer getUidByIsolatedUid(int isolatedUid) {
+ if (UserHandle.isIsolated(isolatedUid)) {
+ synchronized (mLock) {
+ return mIsolatedUidToUidMap.get(isolatedUid);
+ }
+ }
+ return isolatedUid;
+ }
+
+ @GuardedBy("mLock")
+ private void removeAppUidLocked(int uid) {
+ ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
+ if (set != null) {
+ for (int i = set.size() - 1; i >= 0; i--) {
+ int isolatedUid = set.removeAt(i);
+ mIsolatedUidToUidMap.remove(isolatedUid);
+ }
+ }
+ }
+
+ void removeAppUid(int uid, boolean allUsers) {
+ synchronized (mLock) {
+ if (allUsers) {
+ uid = UserHandle.getAppId(uid);
+ for (int i = mUidToIsolatedUidMap.size() - 1; i >= 0; i--) {
+ int u = mUidToIsolatedUidMap.keyAt(i);
+ if (uid == UserHandle.getAppId(u)) {
+ removeAppUidLocked(u);
+ }
+ mUidToIsolatedUidMap.removeAt(i);
+ }
+ } else {
+ removeAppUidLocked(uid);
+ mUidToIsolatedUidMap.remove(uid);
+ }
+ }
+ }
+
+ int removeIsolatedUid(int isolatedUid) {
+ if (!UserHandle.isIsolated(isolatedUid)) {
+ return isolatedUid;
+ }
+ synchronized (mLock) {
+ int uid = mIsolatedUidToUidMap.get(isolatedUid, -1);
+ if (uid == -1) {
+ return isolatedUid;
+ }
+ mIsolatedUidToUidMap.remove(isolatedUid);
+ ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
+ if (set != null) {
+ set.remove(isolatedUid);
+ }
+ // let the ArraySet stay in the mUidToIsolatedUidMap even if it's empty
+ return uid;
+ }
+ }
+
+ void removeByUserId(int userId) {
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = mService.mUserController.getCurrentUserId();
+ }
+ synchronized (mLock) {
+ if (userId == UserHandle.USER_ALL) {
+ mIsolatedUidToUidMap.clear();
+ mUidToIsolatedUidMap.clear();
+ return;
+ }
+ for (int i = mIsolatedUidToUidMap.size() - 1; i >= 0; i--) {
+ int isolatedUid = mIsolatedUidToUidMap.keyAt(i);
+ int uid = mIsolatedUidToUidMap.valueAt(i);
+ if (UserHandle.getUserId(uid) == userId) {
+ mIsolatedUidToUidMap.removeAt(i);
+ mUidToIsolatedUidMap.remove(uid);
+ }
+ }
+ }
+ }
+ }
+
+ final class KillHandler extends Handler {
+ static final int MSG_LMKD_PROC_KILLED = 4101;
+ static final int MSG_CHILD_PROC_DIED = 4102;
+ static final int MSG_PROC_DIED = 4103;
+ static final int MSG_APP_KILL = 4104;
+
+ KillHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LMKD_PROC_KILLED:
+ mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
+ null /* status */);
+ break;
+ case MSG_CHILD_PROC_DIED:
+ mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
+ (Integer) msg.obj /* status */);
+ break;
+ case MSG_PROC_DIED: {
+ ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+ synchronized (mLock) {
+ handleNoteProcessDiedLocked(raw);
+ }
+ synchronized (mService) {
+ recycleRawRecordLocked(raw);
+ }
+ }
+ break;
+ case MSG_APP_KILL: {
+ ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+ synchronized (mLock) {
+ handleNoteAppKillLocked(raw);
+ }
+ synchronized (mService) {
+ recycleRawRecordLocked(raw);
+ }
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ private static boolean isFresh(long timestamp) {
+ // A process could be dying but being stuck in some state, i.e.,
+ // being TRACED by tombstoned, thus the zygote receives SIGCHILD
+ // way after we already knew the kill (maybe because we did the kill :P),
+ // so here check if the last known kill information is "fresh" enough.
+ long now = System.currentTimeMillis();
+
+ return (timestamp + AppExitInfoExternalSource.APP_EXIT_INFO_FRESHNESS_MS) >= now;
+ }
+
+ /**
+ * Keep the raw information about app kills from external sources, i.e., lmkd
+ */
+ final class AppExitInfoExternalSource {
+ private static final long APP_EXIT_INFO_FRESHNESS_MS = 300 * 1000;
+
+ /**
+ * A mapping between uid -> pid -> {timestamp, extra info(Nullable)}.
+ * The uid here is the application uid, not the isolated uid.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<SparseArray<Pair<Long, Object>>> mData;
+
+ /** A tag for logging only */
+ private final String mTag;
+
+ /** A preset reason in case a proc dies */
+ private final Integer mPresetReason;
+
+ /** A callback that will be notified when a proc dies */
+ private BiConsumer<Integer, Integer> mProcDiedListener;
+
+ AppExitInfoExternalSource(String tag, Integer reason) {
+ mData = new SparseArray<SparseArray<Pair<Long, Object>>>();
+ mTag = tag;
+ mPresetReason = reason;
+ }
+
+ void add(int pid, int uid, Object extra) {
+ if (UserHandle.isIsolated(uid)) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+ if (k != null) {
+ uid = k;
+ }
+ }
+
+ synchronized (mLock) {
+ SparseArray<Pair<Long, Object>> array = mData.get(uid);
+ if (array == null) {
+ array = new SparseArray<Pair<Long, Object>>();
+ mData.put(uid, array);
+ }
+ array.put(pid, new Pair<Long, Object>(System.currentTimeMillis(), extra));
+ }
+ }
+
+ Pair<Long, Object> remove(int pid, int uid) {
+ if (UserHandle.isIsolated(uid)) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+ if (k != null) {
+ uid = k;
+ }
+ }
+
+ synchronized (mLock) {
+ SparseArray<Pair<Long, Object>> array = mData.get(uid);
+ if (array != null) {
+ Pair<Long, Object> p = array.get(pid);
+ if (p != null) {
+ array.remove(pid);
+ return isFresh(p.first) ? p : null;
+ }
+ }
+ }
+ return null;
+ }
+
+ void removeByUserId(int userId) {
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = mService.mUserController.getCurrentUserId();
+ }
+ synchronized (mLock) {
+ if (userId == UserHandle.USER_ALL) {
+ mData.clear();
+ return;
+ }
+ for (int i = mData.size() - 1; i >= 0; i--) {
+ int uid = mData.keyAt(i);
+ if (UserHandle.getUserId(uid) == userId) {
+ mData.removeAt(i);
+ }
+ }
+ }
+ }
+
+ void removeByUid(int uid, boolean allUsers) {
+ if (UserHandle.isIsolated(uid)) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+ if (k != null) {
+ uid = k;
+ }
+ }
+
+ if (allUsers) {
+ uid = UserHandle.getAppId(uid);
+ synchronized (mLock) {
+ for (int i = mData.size() - 1; i >= 0; i--) {
+ if (UserHandle.getAppId(mData.keyAt(i)) == uid) {
+ mData.removeAt(i);
+ }
+ }
+ }
+ } else {
+ synchronized (mLock) {
+ mData.remove(uid);
+ }
+ }
+ }
+
+ void setOnProcDiedListener(BiConsumer<Integer, Integer> listener) {
+ synchronized (mLock) {
+ mProcDiedListener = listener;
+ }
+ }
+
+ void onProcDied(final int pid, final int uid, final Integer status) {
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid
+ + ", status=" + status);
+ }
+
+ if (mService == null) {
+ return;
+ }
+
+ // Unlikely but possible: the record has been created
+ // Let's update it if we could find a ApplicationExitInfo record
+ if (!updateExitInfoIfNecessary(pid, uid, status, mPresetReason)) {
+ add(pid, uid, status);
+ }
+
+ // Notify any interesed party regarding the lmkd kills
+ synchronized (mLock) {
+ final BiConsumer<Integer, Integer> listener = mProcDiedListener;
+ if (listener != null) {
+ mService.mHandler.post(()-> listener.accept(pid, uid));
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f86d6a70a076..0b115ea5ca5a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -64,6 +64,7 @@ import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOS
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
import android.app.usage.UsageEvents;
import android.content.Context;
import android.content.pm.ServiceInfo;
@@ -757,7 +758,10 @@ public final class OomAdjuster {
lastCachedGroupUid = lastCachedGroup = 0;
}
if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
- app.kill("cached #" + numCached, true);
+ app.kill("cached #" + numCached,
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
+ true);
}
break;
case PROCESS_STATE_CACHED_EMPTY:
@@ -765,11 +769,17 @@ public final class OomAdjuster {
&& app.lastActivityTime < oldTime) {
app.kill("empty for "
+ ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
- / 1000) + "s", true);
+ / 1000) + "s",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
+ true);
} else {
numEmpty++;
if (numEmpty > emptyProcessLimit) {
- app.kill("empty #" + numEmpty, true);
+ app.kill("empty #" + numEmpty,
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
+ true);
}
}
break;
@@ -786,7 +796,7 @@ public final class OomAdjuster {
// definition not re-use the same process again, and it is
// good to avoid having whatever code was running in them
// left sitting around after no longer needed.
- app.kill("isolated not needed", true);
+ app.kill("isolated not needed", ApplicationExitInfo.REASON_OTHER, true);
} else {
// Keeping this process, update its uid.
updateAppUidRecLocked(app);
@@ -2032,7 +2042,7 @@ public final class OomAdjuster {
}
if (app.waitingToKill != null && app.curReceivers.isEmpty()
&& app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
- app.kill(app.waitingToKill, true);
+ app.kill(app.waitingToKill, ApplicationExitInfo.REASON_OTHER, true);
success = false;
} else {
int processGroup;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2432e83c11b7..c8a8cd79c9b0 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -54,6 +54,9 @@ import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppProtoEnums;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationExitInfo.Reason;
+import android.app.ApplicationExitInfo.SubReason;
import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
@@ -424,12 +427,6 @@ public final class ProcessList {
ActiveUids mActiveUids;
/**
- * The listener who is intereted with the lmkd kills.
- */
- @GuardedBy("mService")
- private LmkdKillListener mLmkdKillListener = null;
-
- /**
* The currently running isolated processes.
*/
final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>();
@@ -440,6 +437,12 @@ public final class ProcessList {
final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
/**
+ * Managees the {@link android.app.ApplicationExitInfo} records.
+ */
+ @GuardedBy("mAppExitInfoTracker")
+ final AppExitInfoTracker mAppExitInfoTracker = new AppExitInfoTracker();
+
+ /**
* The processes that are forked off an application zygote.
*/
final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
@@ -627,7 +630,6 @@ public final class ProcessList {
final class KillHandler extends Handler {
static final int KILL_PROCESS_GROUP_MSG = 4000;
static final int LMKD_RECONNECT_MSG = 4001;
- static final int LMKD_PROC_KILLED_MSG = 4002;
public KillHandler(Looper looper) {
super(looper, null, true);
@@ -650,10 +652,6 @@ public final class ProcessList {
KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
}
break;
- case LMKD_PROC_KILLED_MSG:
- handleLmkdProcKilled(msg.arg1 /* pid */, msg.arg2 /* uid */);
- break;
-
default:
super.handleMessage(msg);
}
@@ -722,9 +720,8 @@ public final class ProcessList {
if (receivedLen != 12) {
return false;
}
- sKillHandler.obtainMessage(KillHandler.LMKD_PROC_KILLED_MSG,
- dataReceived.getInt(4), dataReceived.getInt(8))
- .sendToTarget();
+ mAppExitInfoTracker.scheduleNoteLmkdProcKilled(
+ dataReceived.getInt(4), dataReceived.getInt(8));
return true;
default:
return false;
@@ -739,9 +736,14 @@ public final class ProcessList {
mSystemServerSocketForZygote.getFileDescriptor(),
EVENT_INPUT, this::handleZygoteMessages);
}
+ mAppExitInfoTracker.init(mService, sKillThread.getLooper());
}
}
+ void onSystemReady() {
+ mAppExitInfoTracker.onSystemReady();
+ }
+
void applyDisplaySize(WindowManagerService wm) {
if (!mHaveDisplaySize) {
Point p = new Point();
@@ -1474,7 +1476,10 @@ public final class ProcessList {
proc.lastCachedPss, holder.appVersion);
}
}
- proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true);
+ proc.kill(Long.toString(proc.lastCachedPss) + "k from cached",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_LARGE_CACHED,
+ true);
} else if (proc != null && !keepIfLarge
&& mService.mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
&& proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
@@ -1493,7 +1498,10 @@ public final class ProcessList {
proc.lastCachedPss, holder.appVersion);
}
}
- proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true);
+ proc.kill(Long.toString(proc.lastCachedPss) + "k from cached",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_LARGE_CACHED,
+ true);
}
}
return proc;
@@ -2188,6 +2196,9 @@ public final class ProcessList {
if (doKill) {
// do the killing
ProcessList.killProcessGroup(app.uid, app.pid);
+ noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ String.format(formatString, ""));
}
// wait for the death
@@ -2266,6 +2277,8 @@ public final class ProcessList {
app.pendingStart = false;
killProcessQuiet(pid);
Process.killProcessGroup(app.uid, app.pid);
+ noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
return false;
}
mService.mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
@@ -2351,6 +2364,8 @@ public final class ProcessList {
if (app.pid > 0) {
killProcessQuiet(app.pid);
ProcessList.killProcessGroup(app.uid, app.pid);
+ noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNKNOWN, "hasn't been killed");
} else {
app.pendingStart = false;
}
@@ -2481,6 +2496,12 @@ public final class ProcessList {
@GuardedBy("mService")
boolean removeProcessLocked(ProcessRecord app,
boolean callerWillRestart, boolean allowRestart, String reason) {
+ return removeProcessLocked(app, callerWillRestart, allowRestart, reason,
+ ApplicationExitInfo.REASON_OTHER);
+ }
+
+ boolean removeProcessLocked(ProcessRecord app,
+ boolean callerWillRestart, boolean allowRestart, String reason, int reasonCode) {
final String name = app.processName;
final int uid = app.uid;
if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES,
@@ -2516,7 +2537,7 @@ public final class ProcessList {
needRestart = true;
}
}
- app.kill(reason, true);
+ app.kill(reason, reasonCode, true);
mService.handleAppDiedLocked(app, willRestart, allowRestart);
if (willRestart) {
removeLruProcessLocked(app);
@@ -2601,6 +2622,7 @@ public final class ProcessList {
// the uid of the isolated process is specified by the caller.
uid = isolatedUid;
}
+ mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(uid, info.uid);
mService.getPackageManagerInternalLocked().addIsolatedUid(uid, info.uid);
// Register the isolated UID with this application so BatteryStats knows to
@@ -3574,38 +3596,6 @@ public final class ProcessList {
}
}
- void setLmkdKillListener(final LmkdKillListener listener) {
- synchronized (mService) {
- mLmkdKillListener = listener;
- }
- }
-
- private void handleLmkdProcKilled(final int pid, final int uid) {
- // Log only now
- if (DEBUG_PROCESSES) {
- Slog.i(TAG, "lmkd kill: pid=" + pid + " uid=" + uid);
- }
-
- if (mService == null) {
- return;
- }
- // Notify any interesed party regarding the lmkd kills
- synchronized (mService) {
- final LmkdKillListener listener = mLmkdKillListener;
- if (listener != null) {
- mService.mHandler.post(()-> listener.onLmkdKillOccurred(pid, uid));
- }
- }
- }
-
- private void handleZygoteSigChld(int pid, int uid, int status) {
- // Just log it now.
- if (DEBUG_PROCESSES) {
- Slog.i(TAG, "Got SIGCHLD from zygote: pid=" + pid + ", uid=" + uid
- + ", status=" + Integer.toHexString(status));
- }
- }
-
/**
* Create a server socket in system_server, zygote will connect to it
* in order to send unsolicited messages to system_server.
@@ -3649,7 +3639,8 @@ public final class ProcessList {
mZygoteUnsolicitedMessage.length);
if (len > 0 && mZygoteSigChldMessage.length == Zygote.nativeParseSigChld(
mZygoteUnsolicitedMessage, len, mZygoteSigChldMessage)) {
- handleZygoteSigChld(mZygoteSigChldMessage[0] /* pid */,
+ mAppExitInfoTracker.handleZygoteSigChld(
+ mZygoteSigChldMessage[0] /* pid */,
mZygoteSigChldMessage[1] /* uid */,
mZygoteSigChldMessage[2] /* status */);
}
@@ -3659,4 +3650,39 @@ public final class ProcessList {
}
return EVENT_INPUT;
}
+
+ /**
+ * Called by ActivityManagerService when a process died.
+ */
+ @GuardedBy("mService")
+ void noteProcessDiedLocked(final ProcessRecord app) {
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "note: " + app + " died, saving the exit info");
+ }
+
+ mAppExitInfoTracker.scheduleNoteProcessDiedLocked(app);
+ }
+
+ /**
+ * Called by ActivityManagerService when it decides to kill an application process.
+ */
+ void noteAppKill(final ProcessRecord app, final @Reason int reason,
+ final @SubReason int subReason, final String msg) {
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "note: " + app + " is being killed, reason: " + reason
+ + ", sub-reason: " + subReason + ", message: " + msg);
+ }
+ mAppExitInfoTracker.scheduleNoteAppKill(app, reason, subReason, msg);
+ }
+
+ void noteAppKill(final int pid, final int uid, final @Reason int reason,
+ final @SubReason int subReason, final String msg) {
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "note: " + pid + " is being killed, reason: " + reason
+ + ", sub-reason: " + subReason + ", message: " + msg);
+ }
+
+ mAppExitInfoTracker.scheduleNoteAppKill(pid, uid, reason, subReason, msg);
+ }
}
+
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 1fef3538165a..cbf058700909 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -27,6 +27,9 @@ import static com.android.server.am.ActivityManagerService.MY_PID;
import android.app.ActivityManager;
import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationExitInfo.Reason;
+import android.app.ApplicationExitInfo.SubReason;
import android.app.Dialog;
import android.app.IApplicationThread;
import android.content.ComponentName;
@@ -776,7 +779,8 @@ class ProcessRecord implements WindowProcessListener {
} catch (RemoteException e) {
// If it's already dead our work is done. If it's wedged just kill it.
// We won't get the crash dialog or the error reporting.
- kill("scheduleCrash for '" + message + "' failed", true);
+ kill("scheduleCrash for '" + message + "' failed",
+ ApplicationExitInfo.REASON_CRASH, true);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -784,7 +788,11 @@ class ProcessRecord implements WindowProcessListener {
}
}
- void kill(String reason, boolean noisy) {
+ void kill(String reason, @Reason int reasonCode, boolean noisy) {
+ kill(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
+ }
+
+ void kill(String reason, @Reason int reasonCode, @SubReason int subReason, boolean noisy) {
if (!killedByAm) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
if (mService != null && (noisy || info.uid == mService.mCurOomAdjUid)) {
@@ -793,6 +801,7 @@ class ProcessRecord implements WindowProcessListener {
info.uid);
}
if (pid > 0) {
+ mService.mProcessList.noteAppKill(this, reasonCode, subReason, reason);
EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
Process.killProcessQuiet(pid);
ProcessList.killProcessGroup(uid, pid);
@@ -1374,9 +1383,9 @@ class ProcessRecord implements WindowProcessListener {
}
@Override
- public void appDied() {
+ public void appDied(String reason) {
synchronized (mService) {
- mService.appDiedLocked(this);
+ mService.appDiedLocked(this, reason);
}
}
@@ -1441,7 +1450,8 @@ class ProcessRecord implements WindowProcessListener {
ArrayList<Integer> firstPids = new ArrayList<>(5);
SparseArray<Boolean> lastPids = new SparseArray<>(20);
- mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr", true));
+ mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr",
+ ApplicationExitInfo.REASON_ANR, true));
long anrTime = SystemClock.uptimeMillis();
if (isMonitorCpuUsage()) {
@@ -1592,7 +1602,8 @@ class ProcessRecord implements WindowProcessListener {
mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
parentShortComponentName, parentPr, annotation, cpuInfo, tracesFile, null);
- if (mWindowProcessController.appNotResponding(info.toString(), () -> kill("anr", true),
+ if (mWindowProcessController.appNotResponding(info.toString(), () -> kill("anr",
+ ApplicationExitInfo.REASON_ANR, true),
() -> {
synchronized (mService) {
mService.mServices.scheduleServiceTimeoutLocked(this);
@@ -1609,7 +1620,7 @@ class ProcessRecord implements WindowProcessListener {
}
if (isSilentAnr() && !isDebugging()) {
- kill("bg anr", true);
+ kill("bg anr", ApplicationExitInfo.REASON_ANR, true);
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 0a68408d49a5..8ef01e3f4776 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -946,7 +946,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// This is the second time we failed -- finish activity and give up.
Slog.e(TAG, "Second failure launching "
+ r.intent.getComponent().flattenToShortString() + ", giving up", e);
- proc.appDied();
+ proc.appDied("2nd-crash");
r.finishIfPossible("2nd-crash", false /* oomAdj */);
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index ceb38f7d9789..c755e60ec977 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -971,11 +971,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mAtm.mH.sendMessageAtFrontOfQueue(m);
}
- public void appDied() {
+ void appDied(String reason) {
if (mListener == null) return;
// Posting on handler so WM lock isn't held when we call into AM.
final Message m = PooledLambda.obtainMessage(
- WindowProcessListener::appDied, mListener);
+ WindowProcessListener::appDied, mListener, reason);
mAtm.mH.sendMessage(m);
}
@@ -1136,6 +1136,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
public void appEarlyNotResponding(String annotation, Runnable killAppCallback) {
+ Runnable targetRunnable = null;
synchronized (mAtm.mGlobalLock) {
if (mAtm.mController == null) {
return;
@@ -1145,13 +1146,16 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// 0 == continue, -1 = kill process immediately
int res = mAtm.mController.appEarlyNotResponding(mName, mPid, annotation);
if (res < 0 && mPid != MY_PID) {
- killAppCallback.run();
+ targetRunnable = killAppCallback;
}
} catch (RemoteException e) {
mAtm.mController = null;
Watchdog.getInstance().setActivityController(null);
}
}
+ if (targetRunnable != null) {
+ targetRunnable.run();
+ }
}
public boolean appNotResponding(String info, Runnable killAppCallback,
@@ -1179,6 +1183,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
}
if (targetRunnable != null) {
+ // Execute runnable outside WM lock since the runnable will hold AM lock
targetRunnable.run();
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessListener.java b/services/core/java/com/android/server/wm/WindowProcessListener.java
index 870cbb00dff8..9505191d5eb3 100644
--- a/services/core/java/com/android/server/wm/WindowProcessListener.java
+++ b/services/core/java/com/android/server/wm/WindowProcessListener.java
@@ -59,7 +59,7 @@ public interface WindowProcessListener {
long versionCode);
/** App died :(...oh well */
- void appDied();
+ void appDied(String reason);
void dumpDebug(ProtoOutputStream proto, long fieldId);
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
new file mode 100644
index 000000000000..8d2a152dba83
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -0,0 +1,954 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.app.ApplicationExitInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.system.OsConstants;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Test class for {@link android.app.ApplicationExitInfo}.
+ *
+ * Build/Install/Run:
+ * atest ApplicationExitInfoTest
+ */
+@Presubmit
+public class ApplicationExitInfoTest {
+ private static final String TAG = ApplicationExitInfoTest.class.getSimpleName();
+
+ @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+ @Mock private AppOpsService mAppOpsService;
+ @Mock private PackageManagerInternal mPackageManagerInt;
+
+ private Context mContext = getInstrumentation().getTargetContext();
+ private TestInjector mInjector;
+ private ActivityManagerService mAms;
+ private ProcessList mProcessList;
+ private AppExitInfoTracker mAppExitInfoTracker;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+
+ @BeforeClass
+ public static void setUpOnce() {
+ System.setProperty("dexmaker.share_classloader", "true");
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mProcessList = spy(new ProcessList());
+ mAppExitInfoTracker = spy(new AppExitInfoTracker());
+ setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mIsolatedUidRecords",
+ spy(mAppExitInfoTracker.new IsolatedUidRecords()));
+ setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceZygote",
+ spy(mAppExitInfoTracker.new AppExitInfoExternalSource("zygote", null)));
+ setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceLmkd",
+ spy(mAppExitInfoTracker.new AppExitInfoExternalSource("lmkd",
+ ApplicationExitInfo.REASON_LOW_MEMORY)));
+ setFieldValue(ProcessList.class, mProcessList, "mAppExitInfoTracker", mAppExitInfoTracker);
+ mInjector = new TestInjector(mContext);
+ mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
+ mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+ mAms.mPackageManagerInt = mPackageManagerInt;
+ }
+
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ }
+
+ private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+ try {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Field mfield = Field.class.getDeclaredField("accessFlags");
+ mfield.setAccessible(true);
+ mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+ field.set(obj, val);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ }
+ }
+
+ private void updateExitInfo(ProcessRecord app) {
+ ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecordLocked(app);
+ mAppExitInfoTracker.handleNoteProcessDiedLocked(raw);
+ mAppExitInfoTracker.recycleRawRecordLocked(raw);
+ }
+
+ private void noteAppKill(ProcessRecord app, int reason, int subReason, String msg) {
+ ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecordLocked(app);
+ raw.setReason(reason);
+ raw.setSubReason(subReason);
+ raw.setDescription(msg);
+ mAppExitInfoTracker.handleNoteAppKillLocked(raw);
+ mAppExitInfoTracker.recycleRawRecordLocked(raw);
+ }
+
+ @Test
+ public void testApplicationExitInfo() throws Exception {
+ mAppExitInfoTracker.clearProcessExitInfo(true);
+ mAppExitInfoTracker.mAppExitInfoLoaded = true;
+
+ // Test application calls System.exit()
+ doNothing().when(mAppExitInfoTracker).schedulePersistProcessExitInfo(anyBoolean());
+
+ final int app1Uid = 10123;
+ final int app1Pid1 = 12345;
+ final int app1Pid2 = 12346;
+ final int app1DefiningUid = 23456;
+ final int app1ConnectiongGroup = 10;
+ final int app1UidUser2 = 1010123;
+ final int app1PidUser2 = 12347;
+ final int app1Pss1 = 34567;
+ final int app1Rss1 = 45678;
+ final int app1Pss2 = 34568;
+ final int app1Rss2 = 45679;
+ final int app1Pss3 = 34569;
+ final int app1Rss3 = 45680;
+ final String app1ProcessName = "com.android.test.stub1:process";
+ final String app1PackageName = "com.android.test.stub1";
+
+ final long now1 = System.currentTimeMillis();
+ ProcessRecord app = makeProcessRecord(
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ 0, // connectionGroup
+ PROCESS_STATE_LAST_ACTIVITY, // procstate
+ app1Pss1, // pss
+ app1Rss1, // rss
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+
+ // Case 1: basic System.exit() test
+ int exitCode = 5;
+ doReturn(new Pair<Long, Object>(now1, Integer.valueOf(makeExitStatus(exitCode))))
+ .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+ .remove(anyInt(), anyInt());
+ doReturn(null)
+ .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+ .remove(anyInt(), anyInt());
+ updateExitInfo(app);
+
+ ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>();
+ mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ assertEquals(1, list.size());
+
+ ApplicationExitInfo info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now1, // timestamp
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_EXIT_SELF, // reason
+ null, // subReason
+ exitCode, // status
+ app1Pss1, // pss
+ app1Rss1, // rss
+ IMPORTANCE_CACHED, // importance
+ null); // description
+
+ // Case 2: create another app1 process record with a different pid
+ sleep(1);
+ final long now2 = System.currentTimeMillis();
+ app = makeProcessRecord(
+ app1Pid2, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ app1DefiningUid, // definingUid
+ app1ConnectiongGroup, // connectionGroup
+ PROCESS_STATE_RECEIVER, // procstate
+ app1Pss2, // pss
+ app1Rss2, // rss
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+ exitCode = 6;
+ doReturn(new Pair<Long, Object>(now2, Integer.valueOf(makeExitStatus(exitCode))))
+ .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+ .remove(anyInt(), anyInt());
+ updateExitInfo(app);
+ list.clear();
+
+ // Get all the records for app1Uid
+ mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, 0, 0, list);
+ assertEquals(2, list.size());
+
+ info = list.get(0);
+
+ // Verify the most recent one
+ verifyApplicationExitInfo(
+ info, // info
+ now2, // timestamp
+ app1Pid2, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ app1DefiningUid, // definingUid
+ app1ProcessName, // processName
+ app1ConnectiongGroup, // connectionGroup
+ ApplicationExitInfo.REASON_EXIT_SELF, // reason
+ null, // subReason
+ exitCode, // status
+ app1Pss2, // pss
+ app1Rss2, // rss
+ IMPORTANCE_SERVICE, // importance
+ null); // description
+
+ // Case 3: Create an instance of app1 with different user, and died because of SIGKILL
+ sleep(1);
+ final long now3 = System.currentTimeMillis();
+ int sigNum = OsConstants.SIGKILL;
+ app = makeProcessRecord(
+ app1PidUser2, // pid
+ app1UidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ 0, // connectionGroup
+ PROCESS_STATE_BOUND_FOREGROUND_SERVICE, // procstate
+ app1Pss3, // pss
+ app1Rss3, // rss
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+ doReturn(new Pair<Long, Object>(now3, Integer.valueOf(makeSignalStatus(sigNum))))
+ .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+ .remove(anyInt(), anyInt());
+ updateExitInfo(app);
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+
+ assertEquals(1, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now3, // timestamp
+ app1PidUser2, // pid
+ app1UidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_SIGNALED, // reason
+ null, // subReason
+ sigNum, // status
+ app1Pss3, // pss
+ app1Rss3, // rss
+ IMPORTANCE_FOREGROUND_SERVICE, // importance
+ null); // description
+
+ // try go get all from app1UidUser2
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list);
+ assertEquals(1, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now3, // timestamp
+ app1PidUser2, // pid
+ app1UidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_SIGNALED, // reason
+ null, // subReason
+ sigNum, // status
+ app1Pss3, // pss
+ app1Rss3, // rss
+ IMPORTANCE_FOREGROUND_SERVICE, // importance
+ null); // description
+
+ // Case 4: Create a process from another package with kill from lmkd
+ final int app2UidUser2 = 1010234;
+ final int app2PidUser2 = 12348;
+ final int app2Pss1 = 54321;
+ final int app2Rss1 = 65432;
+ final String app2ProcessName = "com.android.test.stub2:process";
+ final String app2PackageName = "com.android.test.stub2";
+
+ sleep(1);
+ final long now4 = System.currentTimeMillis();
+ doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0)))
+ .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+ .remove(anyInt(), anyInt());
+ doReturn(new Pair<Long, Object>(now4, null))
+ .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+ .remove(anyInt(), anyInt());
+
+ app = makeProcessRecord(
+ app2PidUser2, // pid
+ app2UidUser2, // uid
+ app2UidUser2, // packageUid
+ null, // definingUid
+ 0, // connectionGroup
+ PROCESS_STATE_CACHED_EMPTY, // procstate
+ app2Pss1, // pss
+ app2Rss1, // rss
+ app2ProcessName, // processName
+ app2PackageName); // packageName
+ updateExitInfo(app);
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
+ assertEquals(1, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now4, // timestamp
+ app2PidUser2, // pid
+ app2UidUser2, // uid
+ app2UidUser2, // packageUid
+ null, // definingUid
+ app2ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_LOW_MEMORY, // reason
+ null, // subReason
+ 0, // status
+ app2Pss1, // pss
+ app2Rss1, // rss
+ IMPORTANCE_CACHED, // importance
+ null); // description
+
+ // Verify to get all from User2 regarding app2
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
+ assertEquals(1, list.size());
+
+ // Case 5: App native crash
+ final int app3UidUser2 = 1010345;
+ final int app3PidUser2 = 12349;
+ final int app3ConnectiongGroup = 4;
+ final int app3Pss1 = 54320;
+ final int app3Rss1 = 65430;
+ final String app3ProcessName = "com.android.test.stub3:process";
+ final String app3PackageName = "com.android.test.stub3";
+ final String app3Description = "native crash";
+
+ sleep(1);
+ final long now5 = System.currentTimeMillis();
+ sigNum = OsConstants.SIGABRT;
+ doReturn(new Pair<Long, Object>(now5, Integer.valueOf(makeSignalStatus(sigNum))))
+ .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+ .remove(anyInt(), anyInt());
+ doReturn(null)
+ .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+ .remove(anyInt(), anyInt());
+ app = makeProcessRecord(
+ app3PidUser2, // pid
+ app3UidUser2, // uid
+ app3UidUser2, // packageUid
+ null, // definingUid
+ app3ConnectiongGroup, // connectionGroup
+ PROCESS_STATE_BOUND_TOP, // procstate
+ app3Pss1, // pss
+ app3Rss1, // rss
+ app3ProcessName, // processName
+ app3PackageName); // packageName
+ noteAppKill(app, ApplicationExitInfo.REASON_CRASH_NATIVE,
+ ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description);
+
+ updateExitInfo(app);
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app3PackageName, app3UidUser2, app3PidUser2, 0, list);
+ assertEquals(1, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now5, // timestamp
+ app3PidUser2, // pid
+ app3UidUser2, // uid
+ app3UidUser2, // packageUid
+ null, // definingUid
+ app3ProcessName, // processName
+ app3ConnectiongGroup, // connectionGroup
+ ApplicationExitInfo.REASON_CRASH_NATIVE, // reason
+ null, // subReason
+ sigNum, // status
+ app3Pss1, // pss
+ app3Rss1, // rss
+ IMPORTANCE_FOREGROUND, // importance
+ app3Description); // description
+
+ // Verify the most recent kills, sorted by timestamp
+ int maxNum = 3;
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(null, app3UidUser2, 0, maxNum, list);
+ assertEquals(1, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now5, // timestamp
+ app3PidUser2, // pid
+ app3UidUser2, // uid
+ app3UidUser2, // packageUid
+ null, // definingUid
+ app3ProcessName, // processName
+ app3ConnectiongGroup, // connectionGroup
+ ApplicationExitInfo.REASON_CRASH_NATIVE, // reason
+ null, // subReason
+ sigNum, // status
+ app3Pss1, // pss
+ app3Rss1, // rss
+ IMPORTANCE_FOREGROUND, // importance
+ app3Description); // description
+
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, maxNum, list);
+ assertEquals(1, list.size());
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now4, // timestamp
+ app2PidUser2, // pid
+ app2UidUser2, // uid
+ app2UidUser2, // packageUid
+ null, // definingUid
+ app2ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_LOW_MEMORY, // reason
+ null, // subReason
+ 0, // status
+ app2Pss1, // pss
+ app2Rss1, // rss
+ IMPORTANCE_CACHED, // importance
+ null); // description
+
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, maxNum, list);
+ assertEquals(1, list.size());
+ info = list.get(0);
+
+ sigNum = OsConstants.SIGKILL;
+ verifyApplicationExitInfo(
+ info, // info
+ now3, // timestamp
+ app1PidUser2, // pid
+ app1UidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_SIGNALED, // reason
+ null, // subReason
+ sigNum, // status
+ app1Pss3, // pss
+ app1Rss3, // rss
+ IMPORTANCE_FOREGROUND_SERVICE, // importance
+ null); // description
+
+ // Case 6: App Java crash
+ final int app3Uid = 10345;
+ final int app3IsolatedUid = 99001; // it's an isolated process
+ final int app3Pid = 12350;
+ final int app3Pss2 = 23232;
+ final int app3Rss2 = 32323;
+ final String app3Description2 = "force close";
+
+ sleep(1);
+ final long now6 = System.currentTimeMillis();
+ doReturn(null)
+ .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+ .remove(anyInt(), anyInt());
+ doReturn(null)
+ .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+ .remove(anyInt(), anyInt());
+ app = makeProcessRecord(
+ app3Pid, // pid
+ app3IsolatedUid, // uid
+ app3Uid, // packageUid
+ null, // definingUid
+ 0, // connectionGroup
+ PROCESS_STATE_CACHED_EMPTY, // procstate
+ app3Pss2, // pss
+ app3Rss2, // rss
+ app3ProcessName, // processName
+ app3PackageName); // packageName
+ mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid);
+ noteAppKill(app, ApplicationExitInfo.REASON_CRASH,
+ ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description2);
+
+ assertEquals(app3Uid, mAppExitInfoTracker.mIsolatedUidRecords
+ .getUidByIsolatedUid(app3IsolatedUid).longValue());
+ updateExitInfo(app);
+ assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid));
+
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app3PackageName, app3Uid, 0, 1, list);
+ assertEquals(1, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now6, // timestamp
+ app3Pid, // pid
+ app3IsolatedUid, // uid
+ app3Uid, // packageUid
+ null, // definingUid
+ app3ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_CRASH, // reason
+ null, // subReason
+ 0, // status
+ app3Pss2, // pss
+ app3Rss2, // rss
+ IMPORTANCE_CACHED, // importance
+ app3Description2); // description
+
+ // Case 7: App1 is "uninstalled" from User2
+ mAppExitInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list);
+ assertEquals(0, list.size());
+
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, 0, 0, list);
+ assertEquals(2, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now2, // timestamp
+ app1Pid2, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ app1DefiningUid, // definingUid
+ app1ProcessName, // processName
+ app1ConnectiongGroup, // connectionGroup
+ ApplicationExitInfo.REASON_EXIT_SELF, // reason
+ null, // subReason
+ exitCode, // status
+ app1Pss2, // pss
+ app1Rss2, // rss
+ IMPORTANCE_SERVICE, // importance
+ null); // description
+
+ // Case 8: App1 gets "remove task"
+ final String app1Description = "remove task";
+
+ sleep(1);
+ final int app1IsolatedUidUser2 = 1099002; // isolated uid
+ final int app1Pss4 = 34343;
+ final int app1Rss4 = 43434;
+ final long now8 = System.currentTimeMillis();
+ sigNum = OsConstants.SIGKILL;
+ doReturn(new Pair<Long, Object>(now8, makeSignalStatus(sigNum)))
+ .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+ .remove(anyInt(), anyInt());
+ doReturn(null)
+ .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+ .remove(anyInt(), anyInt());
+ app = makeProcessRecord(
+ app1PidUser2, // pid
+ app1IsolatedUidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ 0, // connectionGroup
+ PROCESS_STATE_CACHED_EMPTY, // procstate
+ app1Pss4, // pss
+ app1Rss4, // rss
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+
+ mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
+ noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNKNOWN, app1Description);
+
+ updateExitInfo(app);
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 1, list);
+ assertEquals(1, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now8, // timestamp
+ app1PidUser2, // pid
+ app1IsolatedUidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_OTHER, // reason
+ ApplicationExitInfo.SUBREASON_UNKNOWN, // subReason
+ 0, // status
+ app1Pss4, // pss
+ app1Rss4, // rss
+ IMPORTANCE_CACHED, // importance
+ app1Description); // description
+
+ // App1 gets "too many empty"
+ final String app1Description2 = "too many empty";
+ sleep(1);
+ final int app1Pid2User2 = 56565;
+ final int app1IsolatedUid2User2 = 1099003; // isolated uid
+ final int app1Pss5 = 34344;
+ final int app1Rss5 = 43435;
+ final long now9 = System.currentTimeMillis();
+ sigNum = OsConstants.SIGKILL;
+ doReturn(new Pair<Long, Object>(now9, makeSignalStatus(sigNum)))
+ .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+ .remove(anyInt(), anyInt());
+ doReturn(null)
+ .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+ .remove(anyInt(), anyInt());
+ app = makeProcessRecord(
+ app1Pid2User2, // pid
+ app1IsolatedUid2User2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ 0, // connectionGroup
+ PROCESS_STATE_CACHED_EMPTY, // procstate
+ app1Pss5, // pss
+ app1Rss5, // rss
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+
+ mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUid2User2, app1UidUser2);
+ noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, app1Description2);
+
+ updateExitInfo(app);
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1Pid2User2, 1, list);
+ assertEquals(1, list.size());
+
+ info = list.get(0);
+
+ verifyApplicationExitInfo(
+ info, // info
+ now9, // timestamp
+ app1Pid2User2, // pid
+ app1IsolatedUid2User2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_OTHER, // reason
+ ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, // subReason
+ 0, // status
+ app1Pss5, // pss
+ app1Rss5, // rss
+ IMPORTANCE_CACHED, // importance
+ app1Description2); // description
+
+
+ // Case 9: User2 gets removed
+ sleep(1);
+ mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
+ mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid);
+
+ mAppExitInfoTracker.onUserRemoved(UserHandle.getUserId(app1UidUser2));
+
+ assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
+ app1IsolatedUidUser2));
+ assertNotNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
+ app3IsolatedUid));
+ mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(
+ app1IsolatedUidUser2, app1UidUser2);
+ mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app1UidUser2, false);
+ assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
+ app1IsolatedUidUser2));
+ mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app3Uid, true);
+ assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid));
+
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, 0, list);
+ assertEquals(0, list.size());
+
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
+ assertEquals(2, list.size());
+
+ info = list.get(0);
+
+ exitCode = 6;
+ verifyApplicationExitInfo(
+ info, // info
+ now2, // timestamp
+ app1Pid2, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ app1DefiningUid, // definingUid
+ app1ProcessName, // processName
+ app1ConnectiongGroup, // connectionGroup
+ ApplicationExitInfo.REASON_EXIT_SELF, // reason
+ null, // subReason
+ exitCode, // status
+ app1Pss2, // pss
+ app1Rss2, // rss
+ IMPORTANCE_SERVICE, // importance
+ null); // description
+
+ info = list.get(1);
+ exitCode = 5;
+ verifyApplicationExitInfo(
+ info, // info
+ now1, // timestamp
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ 0, // connectionGroup
+ ApplicationExitInfo.REASON_EXIT_SELF, // reason
+ null, // subReason
+ exitCode, // status
+ app1Pss1, // pss
+ app1Rss1, // rss
+ IMPORTANCE_CACHED, // importance
+ null); // description
+
+ // Case 10: Save the info and load them again
+ ArrayList<ApplicationExitInfo> original = new ArrayList<ApplicationExitInfo>();
+ mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, original);
+ assertTrue(original.size() > 0);
+
+ mAppExitInfoTracker.mProcExitInfoFile = new File(mContext.getFilesDir(),
+ AppExitInfoTracker.APP_EXIT_INFO_FILE);
+ mAppExitInfoTracker.persistProcessExitInfo();
+ assertTrue(mAppExitInfoTracker.mProcExitInfoFile.exists());
+
+ mAppExitInfoTracker.clearProcessExitInfo(false);
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
+ assertEquals(0, list.size());
+
+ mAppExitInfoTracker.loadExistingProcessExitInfo();
+ list.clear();
+ mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
+ assertEquals(original.size(), list.size());
+
+ for (int i = list.size() - 1; i >= 0; i--) {
+ assertTrue(list.get(i).equals(original.get(i)));
+ }
+ }
+
+ private static int makeExitStatus(int exitCode) {
+ return (exitCode << 8) & 0xff00;
+ }
+
+ private static int makeSignalStatus(int sigNum) {
+ return sigNum & 0x7f;
+ }
+
+ private void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+ int connectionGroup, int procState, int pss, int rss,
+ String processName, String packageName) {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
+ app.pid = pid;
+ app.info.uid = packageUid;
+ if (definingUid != null) {
+ final String dummyPackageName = "com.android.test";
+ final String dummyClassName = ".Foo";
+ app.hostingRecord = HostingRecord.byAppZygote(new ComponentName(
+ dummyPackageName, dummyClassName), "", definingUid);
+ }
+ app.connectionGroup = connectionGroup;
+ app.setProcState = procState;
+ app.lastMemInfo = spy(new Debug.MemoryInfo());
+ doReturn(pss).when(app.lastMemInfo).getTotalPss();
+ doReturn(rss).when(app.lastMemInfo).getTotalRss();
+ return app;
+ }
+
+ private void verifyApplicationExitInfo(ApplicationExitInfo info,
+ Long timestamp, Integer pid, Integer uid, Integer packageUid,
+ Integer definingUid, String processName, Integer connectionGroup,
+ Integer reason, Integer subReason, Integer status,
+ Integer pss, Integer rss, Integer importance, String description) {
+ assertNotNull(info);
+
+ if (timestamp != null) {
+ final long tolerance = 1000; // ms
+ assertTrue(timestamp - tolerance <= info.getTimestamp());
+ assertTrue(timestamp + tolerance >= info.getTimestamp());
+ }
+ if (pid != null) {
+ assertEquals(pid.intValue(), info.getPid());
+ }
+ if (uid != null) {
+ assertEquals(uid.intValue(), info.getRealUid());
+ }
+ if (packageUid != null) {
+ assertEquals(packageUid.intValue(), info.getPackageUid());
+ }
+ if (definingUid != null) {
+ assertEquals(definingUid.intValue(), info.getDefiningUid());
+ }
+ if (processName != null) {
+ assertTrue(TextUtils.equals(processName, info.getProcessName()));
+ }
+ if (connectionGroup != null) {
+ assertEquals(connectionGroup.intValue(), info.getConnectionGroup());
+ }
+ if (reason != null) {
+ assertEquals(reason.intValue(), info.getReason());
+ }
+ if (subReason != null) {
+ assertEquals(subReason.intValue(), info.getSubReason());
+ }
+ if (status != null) {
+ assertEquals(status.intValue(), info.getStatus());
+ }
+ if (pss != null) {
+ assertEquals(pss.intValue(), info.getPss());
+ }
+ if (rss != null) {
+ assertEquals(rss.intValue(), info.getRss());
+ }
+ if (importance != null) {
+ assertEquals(importance.intValue(), info.getImportance());
+ }
+ if (description != null) {
+ assertTrue(TextUtils.equals(description, info.getDescription()));
+ }
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mProcessList;
+ }
+ }
+
+ static class ServiceThreadRule implements TestRule {
+
+ private ServiceThread mThread;
+
+ ServiceThread getThread() {
+ return mThread;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ mThread = new ServiceThread("TestServiceThread",
+ Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
+ mThread.start();
+ try {
+ base.evaluate();
+ } finally {
+ mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
+ }
+ }
+ };
+ }
+ }
+}