Merge "Add support to provide app its own ANR stack trace" into rvc-dev
This commit is contained in:
@@ -4049,6 +4049,7 @@ package android.app {
|
||||
method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
|
||||
method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int, android.os.Bundle);
|
||||
method @Deprecated public void restartPackage(String);
|
||||
method public void setProcessStateSummary(@Nullable byte[]);
|
||||
method public static void setVrThread(int);
|
||||
method public void setWatchHeapLimit(long);
|
||||
field public static final String ACTION_REPORT_HEAP_LIMIT = "android.app.action.REPORT_HEAP_LIMIT";
|
||||
@@ -4561,12 +4562,14 @@ package android.app {
|
||||
method public int getPackageUid();
|
||||
method public int getPid();
|
||||
method @NonNull public String getProcessName();
|
||||
method @Nullable public byte[] getProcessStateSummary();
|
||||
method public long getPss();
|
||||
method public int getRealUid();
|
||||
method public int getReason();
|
||||
method public long getRss();
|
||||
method public int getStatus();
|
||||
method public long getTimestamp();
|
||||
method @Nullable public java.io.InputStream getTraceInputStream() throws java.io.IOException;
|
||||
method @NonNull public android.os.UserHandle getUserHandle();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationExitInfo> CREATOR;
|
||||
|
||||
@@ -3628,11 +3628,40 @@ public class ActivityManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom state data for this process. It will be included in the record of
|
||||
* {@link ApplicationExitInfo} on the death of the current calling process; the new process
|
||||
* of the app can retrieve this state data by calling
|
||||
* {@link ApplicationExitInfo#getProcessStateSummary} on the record returned by
|
||||
* {@link #getHistoricalProcessExitReasons}.
|
||||
*
|
||||
* <p> This would be useful for the calling app to save its stateful data: if it's
|
||||
* killed later for any reason, the new process of the app can know what the
|
||||
* previous process of the app was doing. For instance, you could use this to encode
|
||||
* the current level in a game, or a set of features/experiments that were enabled. Later you
|
||||
* could analyze under what circumstances the app tends to crash or use too much memory.
|
||||
* However, it's not suggested to rely on this to restore the applications previous UI state
|
||||
* or so, it's only meant for analyzing application healthy status.</p>
|
||||
*
|
||||
* <p> System might decide to throttle the calls to this API; so call this API in a reasonable
|
||||
* manner, excessive calls to this API could result a {@link java.lang.RuntimeException}.
|
||||
* </p>
|
||||
*
|
||||
* @param state The state data
|
||||
*/
|
||||
public void setProcessStateSummary(@Nullable byte[] state) {
|
||||
try {
|
||||
getService().setProcessStateSummary(state);
|
||||
} 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}
|
||||
* @see ApplicationExitInfo#REASON_LOW_MEMORY
|
||||
*/
|
||||
public static boolean isLowMemoryKillReportSupported() {
|
||||
return SystemProperties.getBoolean("persist.sys.lmk.reportkills", false);
|
||||
|
||||
@@ -23,7 +23,9 @@ import android.annotation.Nullable;
|
||||
import android.app.ActivityManager.RunningAppProcessInfo.Importance;
|
||||
import android.icu.text.SimpleDateFormat;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DebugUtils;
|
||||
@@ -31,12 +33,17 @@ import android.util.proto.ProtoInputStream;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
import android.util.proto.WireTypeMismatchException;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* Describes the information of an application process's death.
|
||||
@@ -321,85 +328,105 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
|
||||
|
||||
/**
|
||||
* @see {@link #getPid}
|
||||
* @see #getPid
|
||||
*/
|
||||
private int mPid;
|
||||
|
||||
/**
|
||||
* @see {@link #getRealUid}
|
||||
* @see #getRealUid
|
||||
*/
|
||||
private int mRealUid;
|
||||
|
||||
/**
|
||||
* @see {@link #getPackageUid}
|
||||
* @see #getPackageUid
|
||||
*/
|
||||
private int mPackageUid;
|
||||
|
||||
/**
|
||||
* @see {@link #getDefiningUid}
|
||||
* @see #getDefiningUid
|
||||
*/
|
||||
private int mDefiningUid;
|
||||
|
||||
/**
|
||||
* @see {@link #getProcessName}
|
||||
* @see #getProcessName
|
||||
*/
|
||||
private String mProcessName;
|
||||
|
||||
/**
|
||||
* @see {@link #getReason}
|
||||
* @see #getReason
|
||||
*/
|
||||
private @Reason int mReason;
|
||||
|
||||
/**
|
||||
* @see {@link #getStatus}
|
||||
* @see #getStatus
|
||||
*/
|
||||
private int mStatus;
|
||||
|
||||
/**
|
||||
* @see {@link #getImportance}
|
||||
* @see #getImportance
|
||||
*/
|
||||
private @Importance int mImportance;
|
||||
|
||||
/**
|
||||
* @see {@link #getPss}
|
||||
* @see #getPss
|
||||
*/
|
||||
private long mPss;
|
||||
|
||||
/**
|
||||
* @see {@link #getRss}
|
||||
* @see #getRss
|
||||
*/
|
||||
private long mRss;
|
||||
|
||||
/**
|
||||
* @see {@link #getTimestamp}
|
||||
* @see #getTimestamp
|
||||
*/
|
||||
private @CurrentTimeMillisLong long mTimestamp;
|
||||
|
||||
/**
|
||||
* @see {@link #getDescription}
|
||||
* @see #getDescription
|
||||
*/
|
||||
private @Nullable String mDescription;
|
||||
|
||||
/**
|
||||
* @see {@link #getSubReason}
|
||||
* @see #getSubReason
|
||||
*/
|
||||
private @SubReason int mSubReason;
|
||||
|
||||
/**
|
||||
* @see {@link #getConnectionGroup}
|
||||
* @see #getConnectionGroup
|
||||
*/
|
||||
private int mConnectionGroup;
|
||||
|
||||
/**
|
||||
* @see {@link #getPackageName}
|
||||
* @see #getPackageName
|
||||
*/
|
||||
private String mPackageName;
|
||||
|
||||
/**
|
||||
* @see {@link #getPackageList}
|
||||
* @see #getPackageList
|
||||
*/
|
||||
private String[] mPackageList;
|
||||
|
||||
/**
|
||||
* @see #getProcessStateSummary
|
||||
*/
|
||||
private byte[] mState;
|
||||
|
||||
/**
|
||||
* The file to the trace file in the storage;
|
||||
*
|
||||
* for system internal use only, will not retain across processes.
|
||||
*
|
||||
* @see #getTraceInputStream
|
||||
*/
|
||||
private File mTraceFile;
|
||||
|
||||
/**
|
||||
* The Binder interface to retrieve the file descriptor to
|
||||
* the trace file from the system.
|
||||
*/
|
||||
private IAppTraceRetriever mAppTraceRetriever;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "REASON_" }, value = {
|
||||
REASON_UNKNOWN,
|
||||
@@ -556,6 +583,54 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
return UserHandle.of(UserHandle.getUserId(mRealUid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the state data set by calling {@link ActivityManager#setProcessStateSummary}
|
||||
* from the process before its death.
|
||||
*
|
||||
* @return The process-customized data
|
||||
* @see ActivityManager#setProcessStateSummary(byte[])
|
||||
*/
|
||||
public @Nullable byte[] getProcessStateSummary() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the InputStream to the traces that was taken by the system
|
||||
* prior to the death of the process; typically it'll be available when
|
||||
* the reason is {@link #REASON_ANR}, though if the process gets an ANR
|
||||
* but recovers, and dies for another reason later, this trace will be included
|
||||
* in the record of {@link ApplicationExitInfo} still.
|
||||
*
|
||||
* @return The input stream to the traces that was taken by the system
|
||||
* prior to the death of the process.
|
||||
*/
|
||||
public @Nullable InputStream getTraceInputStream() throws IOException {
|
||||
if (mAppTraceRetriever == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor(
|
||||
mPackageName, mPackageUid, mPid);
|
||||
if (fd == null) {
|
||||
return null;
|
||||
}
|
||||
return new GZIPInputStream(new ParcelFileDescriptor.AutoCloseInputStream(fd));
|
||||
} catch (RemoteException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #getTraceInputStream} but return the File object.
|
||||
*
|
||||
* For internal use only.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public @Nullable File getTraceFile() {
|
||||
return mTraceFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* A subtype reason in conjunction with {@link #mReason}.
|
||||
*
|
||||
@@ -569,7 +644,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
|
||||
/**
|
||||
* The connection group this process belongs to, if there is any.
|
||||
* @see {@link android.content.Context#updateServiceGroup}.
|
||||
* @see android.content.Context#updateServiceGroup
|
||||
*
|
||||
* For internal use only.
|
||||
*
|
||||
@@ -582,8 +657,6 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
/**
|
||||
* Name of first package running in this process;
|
||||
*
|
||||
* For system internal use only, will not retain across processes.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public String getPackageName() {
|
||||
@@ -602,7 +675,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getPid}
|
||||
* @see #getPid
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -611,7 +684,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getRealUid}
|
||||
* @see #getRealUid
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -620,7 +693,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getPackageUid}
|
||||
* @see #getPackageUid
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -629,7 +702,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getDefiningUid}
|
||||
* @see #getDefiningUid
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -638,7 +711,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getProcessName}
|
||||
* @see #getProcessName
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -647,7 +720,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getReason}
|
||||
* @see #getReason
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -656,7 +729,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getStatus}
|
||||
* @see #getStatus
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -665,7 +738,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getImportance}
|
||||
* @see #getImportance
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -674,7 +747,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getPss}
|
||||
* @see #getPss
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -683,7 +756,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getRss}
|
||||
* @see #getRss
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -692,7 +765,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getTimestamp}
|
||||
* @see #getTimestamp
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -701,7 +774,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getDescription}
|
||||
* @see #getDescription
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -710,7 +783,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getSubReason}
|
||||
* @see #getSubReason
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -719,7 +792,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getConnectionGroup}
|
||||
* @see #getConnectionGroup
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -728,7 +801,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getPackageName}
|
||||
* @see #getPackageName
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -737,7 +810,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #getPackageList}
|
||||
* @see #getPackageList
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -745,6 +818,33 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
mPackageList = packageList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getProcessStateSummary
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setProcessStateSummary(final byte[] state) {
|
||||
mState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getTraceFile
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setTraceFile(final File traceFile) {
|
||||
mTraceFile = traceFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #mAppTraceRetriever
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setAppTraceRetriever(final IAppTraceRetriever retriever) {
|
||||
mAppTraceRetriever = retriever;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -757,6 +857,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
dest.writeInt(mPackageUid);
|
||||
dest.writeInt(mDefiningUid);
|
||||
dest.writeString(mProcessName);
|
||||
dest.writeString(mPackageName);
|
||||
dest.writeInt(mConnectionGroup);
|
||||
dest.writeInt(mReason);
|
||||
dest.writeInt(mSubReason);
|
||||
@@ -766,6 +867,13 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
dest.writeLong(mRss);
|
||||
dest.writeLong(mTimestamp);
|
||||
dest.writeString(mDescription);
|
||||
dest.writeByteArray(mState);
|
||||
if (mAppTraceRetriever != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeStrongBinder(mAppTraceRetriever.asBinder());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -779,6 +887,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
mPackageUid = other.mPackageUid;
|
||||
mDefiningUid = other.mDefiningUid;
|
||||
mProcessName = other.mProcessName;
|
||||
mPackageName = other.mPackageName;
|
||||
mConnectionGroup = other.mConnectionGroup;
|
||||
mReason = other.mReason;
|
||||
mStatus = other.mStatus;
|
||||
@@ -790,6 +899,9 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
mDescription = other.mDescription;
|
||||
mPackageName = other.mPackageName;
|
||||
mPackageList = other.mPackageList;
|
||||
mState = other.mState;
|
||||
mTraceFile = other.mTraceFile;
|
||||
mAppTraceRetriever = other.mAppTraceRetriever;
|
||||
}
|
||||
|
||||
private ApplicationExitInfo(@NonNull Parcel in) {
|
||||
@@ -798,6 +910,7 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
mPackageUid = in.readInt();
|
||||
mDefiningUid = in.readInt();
|
||||
mProcessName = in.readString();
|
||||
mPackageName = in.readString();
|
||||
mConnectionGroup = in.readInt();
|
||||
mReason = in.readInt();
|
||||
mSubReason = in.readInt();
|
||||
@@ -807,6 +920,10 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
mRss = in.readLong();
|
||||
mTimestamp = in.readLong();
|
||||
mDescription = in.readString();
|
||||
mState = in.createByteArray();
|
||||
if (in.readInt() == 1) {
|
||||
mAppTraceRetriever = IAppTraceRetriever.Stub.asInterface(in.readStrongBinder());
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull static final Creator<ApplicationExitInfo> CREATOR =
|
||||
@@ -839,6 +956,9 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
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);
|
||||
pw.println(prefix + " state=" + (ArrayUtils.isEmpty(mState)
|
||||
? "empty" : Integer.toString(mState.length) + " bytes"));
|
||||
pw.println(prefix + " trace=" + mTraceFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -859,6 +979,9 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
sb.append(" pss="); DebugUtils.sizeValueToString(mPss << 10, sb);
|
||||
sb.append(" rss="); DebugUtils.sizeValueToString(mRss << 10, sb);
|
||||
sb.append(" description=").append(mDescription);
|
||||
sb.append(" state=").append(ArrayUtils.isEmpty(mState)
|
||||
? "empty" : Integer.toString(mState.length) + " bytes");
|
||||
sb.append(" trace=").append(mTraceFile);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -961,6 +1084,9 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
proto.write(ApplicationExitInfoProto.RSS, mRss);
|
||||
proto.write(ApplicationExitInfoProto.TIMESTAMP, mTimestamp);
|
||||
proto.write(ApplicationExitInfoProto.DESCRIPTION, mDescription);
|
||||
proto.write(ApplicationExitInfoProto.STATE, mState);
|
||||
proto.write(ApplicationExitInfoProto.TRACE_FILE,
|
||||
mTraceFile == null ? null : mTraceFile.getAbsolutePath());
|
||||
proto.end(token);
|
||||
}
|
||||
|
||||
@@ -1019,6 +1145,15 @@ public final class ApplicationExitInfo implements Parcelable {
|
||||
case (int) ApplicationExitInfoProto.DESCRIPTION:
|
||||
mDescription = proto.readString(ApplicationExitInfoProto.DESCRIPTION);
|
||||
break;
|
||||
case (int) ApplicationExitInfoProto.STATE:
|
||||
mState = proto.readBytes(ApplicationExitInfoProto.STATE);
|
||||
break;
|
||||
case (int) ApplicationExitInfoProto.TRACE_FILE:
|
||||
final String path = proto.readString(ApplicationExitInfoProto.TRACE_FILE);
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
mTraceFile = new File(path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
proto.end(token);
|
||||
|
||||
@@ -652,4 +652,27 @@ interface IActivityManager {
|
||||
*/
|
||||
void setActivityLocusContext(in ComponentName activity, in LocusId locusId,
|
||||
in IBinder appToken);
|
||||
|
||||
/**
|
||||
* Set custom state data for this process. It will be included in the record of
|
||||
* {@link ApplicationExitInfo} on the death of the current calling process; the new process
|
||||
* of the app can retrieve this state data by calling
|
||||
* {@link ApplicationExitInfo#getProcessStateSummary} on the record returned by
|
||||
* {@link #getHistoricalProcessExitReasons}.
|
||||
*
|
||||
* <p> This would be useful for the calling app to save its stateful data: if it's
|
||||
* killed later for any reason, the new process of the app can know what the
|
||||
* previous process of the app was doing. For instance, you could use this to encode
|
||||
* the current level in a game, or a set of features/experiments that were enabled. Later you
|
||||
* could analyze under what circumstances the app tends to crash or use too much memory.
|
||||
* However, it's not suggested to rely on this to restore the applications previous UI state
|
||||
* or so, it's only meant for analyzing application healthy status.</p>
|
||||
*
|
||||
* <p> System might decide to throttle the calls to this API; so call this API in a reasonable
|
||||
* manner, excessive calls to this API could result a {@link java.lang.RuntimeException}.
|
||||
* </p>
|
||||
*
|
||||
* @param state The customized state data
|
||||
*/
|
||||
void setProcessStateSummary(in byte[] state);
|
||||
}
|
||||
|
||||
38
core/java/android/app/IAppTraceRetriever.aidl
Normal file
38
core/java/android/app/IAppTraceRetriever.aidl
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
/**
|
||||
* An interface that's to be used by {@link ApplicationExitInfo#getTraceFile()}
|
||||
* to retrieve the actual file descriptor to its trace file.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
interface IAppTraceRetriever {
|
||||
/**
|
||||
* Retrieve the trace file with given packageName/uid/pid.
|
||||
*
|
||||
* @param packagename The target package name of the trace
|
||||
* @param uid The target UID of the trace
|
||||
* @param pid The target PID of the trace
|
||||
* @return The file descriptor to the trace file, or null if it's not found.
|
||||
*/
|
||||
ParcelFileDescriptor getTraceFileDescriptor(in String packageName,
|
||||
int uid, int pid);
|
||||
}
|
||||
@@ -42,4 +42,6 @@ message ApplicationExitInfoProto {
|
||||
optional int64 rss = 12;
|
||||
optional int64 timestamp = 13;
|
||||
optional string description = 14;
|
||||
optional bytes state = 15;
|
||||
optional string trace_file = 16;
|
||||
}
|
||||
|
||||
@@ -579,6 +579,13 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
|
||||
static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
|
||||
|
||||
/**
|
||||
* The maximum number of bytes that {@link #setProcessStateSummary} accepts.
|
||||
*
|
||||
* @see {@link android.app.ActivityManager#setProcessStateSummary(byte[])}
|
||||
*/
|
||||
static final int MAX_STATE_DATA_SIZE = 128;
|
||||
|
||||
/** All system services */
|
||||
SystemServiceManager mSystemServiceManager;
|
||||
|
||||
@@ -3202,7 +3209,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
return mAtmInternal.compatibilityInfoForPackage(ai);
|
||||
}
|
||||
|
||||
private void enforceNotIsolatedCaller(String caller) {
|
||||
/* package */ void enforceNotIsolatedCaller(String caller) {
|
||||
if (UserHandle.isIsolated(Binder.getCallingUid())) {
|
||||
throw new SecurityException("Isolated process not allowed to call " + caller);
|
||||
}
|
||||
@@ -3887,6 +3894,18 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
public static File dumpStackTraces(ArrayList<Integer> firstPids,
|
||||
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
|
||||
ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile) {
|
||||
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
|
||||
logExceptionCreatingFile, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param firstPidOffsets Optional, when it's set, it receives the start/end offset
|
||||
* of the very first pid to be dumped.
|
||||
*/
|
||||
/* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
|
||||
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
|
||||
ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
|
||||
long[] firstPidOffsets) {
|
||||
ArrayList<Integer> extraPids = null;
|
||||
|
||||
Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);
|
||||
@@ -3938,12 +3957,22 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
return null;
|
||||
}
|
||||
|
||||
dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids);
|
||||
Pair<Long, Long> offsets = dumpStackTraces(
|
||||
tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids);
|
||||
if (firstPidOffsets != null) {
|
||||
if (offsets == null) {
|
||||
firstPidOffsets[0] = firstPidOffsets[1] = -1;
|
||||
} else {
|
||||
firstPidOffsets[0] = offsets.first; // Start offset to the ANR trace file
|
||||
firstPidOffsets[1] = offsets.second; // End offset to the ANR trace file
|
||||
}
|
||||
}
|
||||
return tracesFile;
|
||||
}
|
||||
|
||||
@GuardedBy("ActivityManagerService.class")
|
||||
private static SimpleDateFormat sAnrFileDateFormat;
|
||||
static final String ANR_FILE_PREFIX = "anr_";
|
||||
|
||||
private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
|
||||
if (sAnrFileDateFormat == null) {
|
||||
@@ -3951,7 +3980,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
}
|
||||
|
||||
final String formattedDate = sAnrFileDateFormat.format(new Date());
|
||||
final File anrFile = new File(tracesDir, "anr_" + formattedDate);
|
||||
final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate);
|
||||
|
||||
if (anrFile.createNewFile()) {
|
||||
FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
|
||||
@@ -4020,7 +4049,10 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
return SystemClock.elapsedRealtime() - timeStart;
|
||||
}
|
||||
|
||||
public static void dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,
|
||||
/**
|
||||
* @return The start/end offset of the trace of the very first PID
|
||||
*/
|
||||
public static Pair<Long, Long> dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,
|
||||
ArrayList<Integer> nativePids, ArrayList<Integer> extraPids) {
|
||||
|
||||
Slog.i(TAG, "Dumping to " + tracesFile);
|
||||
@@ -4032,21 +4064,39 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
// We must complete all stack dumps within 20 seconds.
|
||||
long remainingTime = 20 * 1000;
|
||||
|
||||
// As applications are usually interested with the ANR stack traces, but we can't share with
|
||||
// them the stack traces other than their own stacks. So after the very first PID is
|
||||
// dumped, remember the current file size.
|
||||
long firstPidStart = -1;
|
||||
long firstPidEnd = -1;
|
||||
|
||||
// First collect all of the stacks of the most important pids.
|
||||
if (firstPids != null) {
|
||||
int num = firstPids.size();
|
||||
for (int i = 0; i < num; i++) {
|
||||
Slog.i(TAG, "Collecting stacks for pid " + firstPids.get(i));
|
||||
final long timeTaken = dumpJavaTracesTombstoned(firstPids.get(i), tracesFile,
|
||||
final int pid = firstPids.get(i);
|
||||
// We don't copy ANR traces from the system_server intentionally.
|
||||
final boolean firstPid = i == 0 && MY_PID != pid;
|
||||
File tf = null;
|
||||
if (firstPid) {
|
||||
tf = new File(tracesFile);
|
||||
firstPidStart = tf.exists() ? tf.length() : 0;
|
||||
}
|
||||
|
||||
Slog.i(TAG, "Collecting stacks for pid " + pid);
|
||||
final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile,
|
||||
remainingTime);
|
||||
|
||||
remainingTime -= timeTaken;
|
||||
if (remainingTime <= 0) {
|
||||
Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + firstPids.get(i) +
|
||||
"); deadline exceeded.");
|
||||
return;
|
||||
Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
|
||||
+ "); deadline exceeded.");
|
||||
return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
|
||||
}
|
||||
|
||||
if (firstPid) {
|
||||
firstPidEnd = tf.length();
|
||||
}
|
||||
if (DEBUG_ANR) {
|
||||
Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms");
|
||||
}
|
||||
@@ -4068,7 +4118,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
if (remainingTime <= 0) {
|
||||
Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid +
|
||||
"); deadline exceeded.");
|
||||
return;
|
||||
return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
|
||||
}
|
||||
|
||||
if (DEBUG_ANR) {
|
||||
@@ -4088,7 +4138,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
if (remainingTime <= 0) {
|
||||
Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid +
|
||||
"); deadline exceeded.");
|
||||
return;
|
||||
return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
|
||||
}
|
||||
|
||||
if (DEBUG_ANR) {
|
||||
@@ -4097,6 +4147,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
}
|
||||
}
|
||||
Slog.i(TAG, "Done dumping");
|
||||
return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -10280,6 +10331,15 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
return new ParceledListSlice<ApplicationExitInfo>(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProcessStateSummary(@Nullable byte[] state) {
|
||||
if (state != null && state.length > MAX_STATE_DATA_SIZE) {
|
||||
throw new IllegalArgumentException("Data size is too large");
|
||||
}
|
||||
mProcessList.mAppExitInfoTracker.setProcessStateSummary(Binder.getCallingUid(),
|
||||
Binder.getCallingPid(), state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the calling process has the permission to dump given package,
|
||||
* throw SecurityException if it doesn't have the permission.
|
||||
@@ -10287,7 +10347,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
* @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,
|
||||
int enforceDumpPermissionForPackage(String packageName, int userId, int callingUid,
|
||||
String function) {
|
||||
long identity = Binder.clearCallingIdentity();
|
||||
int uid = Process.INVALID_UID;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,6 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ProcessInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.net.LocalSocket;
|
||||
@@ -99,7 +98,6 @@ import android.provider.DeviceConfig;
|
||||
import android.system.Os;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.EventLog;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.Pair;
|
||||
@@ -138,10 +136,8 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Activity manager code dealing with processes.
|
||||
@@ -512,13 +508,6 @@ public final class ProcessList {
|
||||
*/
|
||||
private final int[] mZygoteSigChldMessage = new int[3];
|
||||
|
||||
interface LmkdKillListener {
|
||||
/**
|
||||
* Called when there is a process kill by lmkd.
|
||||
*/
|
||||
void onLmkdKillOccurred(int pid, int uid);
|
||||
}
|
||||
|
||||
final class IsolatedUidRange {
|
||||
@VisibleForTesting
|
||||
public final int mFirstUid;
|
||||
|
||||
@@ -1621,9 +1621,11 @@ class ProcessRecord implements WindowProcessListener {
|
||||
// For background ANRs, don't pass the ProcessCpuTracker to
|
||||
// avoid spending 1/2 second collecting stats to rank lastPids.
|
||||
StringWriter tracesFileException = new StringWriter();
|
||||
// To hold the start and end offset to the ANR trace file respectively.
|
||||
final long[] offsets = new long[2];
|
||||
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
|
||||
(isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids,
|
||||
nativePids, tracesFileException);
|
||||
nativePids, tracesFileException, offsets);
|
||||
|
||||
if (isMonitorCpuUsage()) {
|
||||
mService.updateCpuStatsNow();
|
||||
@@ -1641,6 +1643,10 @@ class ProcessRecord implements WindowProcessListener {
|
||||
if (tracesFile == null) {
|
||||
// There is no trace file, so dump (only) the alleged culprit's threads to the log
|
||||
Process.sendSignal(pid, Process.SIGNAL_QUIT);
|
||||
} else if (offsets[1] > 0) {
|
||||
// We've dumped into the trace file successfully
|
||||
mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace(
|
||||
pid, uid, getPackageList(), tracesFile, offsets[0], offsets[1]);
|
||||
}
|
||||
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.ANR_OCCURRED, uid, processName,
|
||||
|
||||
@@ -46,6 +46,7 @@ import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.os.Debug;
|
||||
import android.os.FileUtils;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
@@ -55,6 +56,7 @@ import android.system.OsConstants;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.appop.AppOpsService;
|
||||
@@ -71,10 +73,17 @@ import org.junit.runners.model.Statement;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* Test class for {@link android.app.ApplicationExitInfo}.
|
||||
@@ -119,6 +128,8 @@ public class ApplicationExitInfoTest {
|
||||
setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceLmkd",
|
||||
spy(mAppExitInfoTracker.new AppExitInfoExternalSource("lmkd",
|
||||
ApplicationExitInfo.REASON_LOW_MEMORY)));
|
||||
setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppTraceRetriever",
|
||||
spy(mAppExitInfoTracker.new AppTraceRetriever()));
|
||||
setFieldValue(ProcessList.class, mProcessList, "mAppExitInfoTracker", mAppExitInfoTracker);
|
||||
mInjector = new TestInjector(mContext);
|
||||
mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
|
||||
@@ -169,6 +180,11 @@ public class ApplicationExitInfoTest {
|
||||
public void testApplicationExitInfo() throws Exception {
|
||||
mAppExitInfoTracker.clearProcessExitInfo(true);
|
||||
mAppExitInfoTracker.mAppExitInfoLoaded = true;
|
||||
mAppExitInfoTracker.mProcExitStoreDir = new File(mContext.getFilesDir(),
|
||||
AppExitInfoTracker.APP_EXIT_STORE_DIR);
|
||||
assertTrue(FileUtils.createDir(mAppExitInfoTracker.mProcExitStoreDir));
|
||||
mAppExitInfoTracker.mProcExitInfoFile = new File(mAppExitInfoTracker.mProcExitStoreDir,
|
||||
AppExitInfoTracker.APP_EXIT_INFO_FILE);
|
||||
|
||||
// Test application calls System.exit()
|
||||
doNothing().when(mAppExitInfoTracker).schedulePersistProcessExitInfo(anyBoolean());
|
||||
@@ -188,6 +204,10 @@ public class ApplicationExitInfoTest {
|
||||
final long app1Rss3 = 45680;
|
||||
final String app1ProcessName = "com.android.test.stub1:process";
|
||||
final String app1PackageName = "com.android.test.stub1";
|
||||
final byte[] app1Cookie1 = {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
|
||||
(byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08};
|
||||
final byte[] app1Cookie2 = {(byte) 0x08, (byte) 0x07, (byte) 0x06, (byte) 0x05,
|
||||
(byte) 0x04, (byte) 0x03, (byte) 0x02, (byte) 0x01};
|
||||
|
||||
final long now1 = System.currentTimeMillis();
|
||||
ProcessRecord app = makeProcessRecord(
|
||||
@@ -204,6 +224,9 @@ public class ApplicationExitInfoTest {
|
||||
|
||||
// Case 1: basic System.exit() test
|
||||
int exitCode = 5;
|
||||
mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid1, app1Cookie1);
|
||||
assertTrue(ArrayUtils.equals(mAppExitInfoTracker.getProcessStateSummary(app1Uid,
|
||||
app1Pid1), app1Cookie1, app1Cookie1.length));
|
||||
doReturn(new Pair<Long, Object>(now1, Integer.valueOf(makeExitStatus(exitCode))))
|
||||
.when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
|
||||
.remove(anyInt(), anyInt());
|
||||
@@ -235,6 +258,10 @@ public class ApplicationExitInfoTest {
|
||||
IMPORTANCE_CACHED, // importance
|
||||
null); // description
|
||||
|
||||
assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie1,
|
||||
app1Cookie1.length));
|
||||
assertEquals(info.getTraceInputStream(), null);
|
||||
|
||||
// Case 2: create another app1 process record with a different pid
|
||||
sleep(1);
|
||||
final long now2 = System.currentTimeMillis();
|
||||
@@ -250,6 +277,12 @@ public class ApplicationExitInfoTest {
|
||||
app1ProcessName, // processName
|
||||
app1PackageName); // packageName
|
||||
exitCode = 6;
|
||||
|
||||
mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid2, app1Cookie1);
|
||||
// Override with a different cookie
|
||||
mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid2, app1Cookie2);
|
||||
assertTrue(ArrayUtils.equals(mAppExitInfoTracker.getProcessStateSummary(app1Uid,
|
||||
app1Pid2), app1Cookie2, app1Cookie2.length));
|
||||
doReturn(new Pair<Long, Object>(now2, Integer.valueOf(makeExitStatus(exitCode))))
|
||||
.when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
|
||||
.remove(anyInt(), anyInt());
|
||||
@@ -280,6 +313,12 @@ public class ApplicationExitInfoTest {
|
||||
IMPORTANCE_SERVICE, // importance
|
||||
null); // description
|
||||
|
||||
assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie2,
|
||||
app1Cookie2.length));
|
||||
info = list.get(1);
|
||||
assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie1,
|
||||
app1Cookie1.length));
|
||||
|
||||
// Case 3: Create an instance of app1 with different user, and died because of SIGKILL
|
||||
sleep(1);
|
||||
final long now3 = System.currentTimeMillis();
|
||||
@@ -702,9 +741,19 @@ public class ApplicationExitInfoTest {
|
||||
app1PackageName); // packageName
|
||||
|
||||
mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUid2User2, app1UidUser2);
|
||||
|
||||
// Pretent it gets an ANR trace too (although the reason here should be REASON_ANR)
|
||||
final File traceFile = new File(mContext.getFilesDir(), "anr_original.txt");
|
||||
final int traceSize = 10240;
|
||||
final int traceStart = 1024;
|
||||
final int traceEnd = 8192;
|
||||
createRandomFile(traceFile, traceSize);
|
||||
assertEquals(traceSize, traceFile.length());
|
||||
mAppExitInfoTracker.handleLogAnrTrace(app.pid, app.uid, app.getPackageList(),
|
||||
traceFile, traceStart, traceEnd);
|
||||
|
||||
noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
|
||||
ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, app1Description2);
|
||||
|
||||
updateExitInfo(app);
|
||||
list.clear();
|
||||
mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1Pid2User2, 1, list);
|
||||
@@ -729,6 +778,10 @@ public class ApplicationExitInfoTest {
|
||||
IMPORTANCE_CACHED, // importance
|
||||
app1Description2); // description
|
||||
|
||||
// Verify if the traceFile get copied into the records correctly.
|
||||
verifyTraceFile(traceFile, traceStart, info.getTraceFile(), 0, traceEnd - traceStart);
|
||||
traceFile.delete();
|
||||
info.getTraceFile().delete();
|
||||
|
||||
// Case 9: User2 gets removed
|
||||
sleep(1);
|
||||
@@ -801,8 +854,6 @@ public class ApplicationExitInfoTest {
|
||||
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());
|
||||
|
||||
@@ -836,6 +887,37 @@ public class ApplicationExitInfoTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void createRandomFile(File file, int size) throws IOException {
|
||||
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
|
||||
Random random = new Random();
|
||||
byte[] buf = random.ints('a', 'z').limit(size).collect(
|
||||
StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
|
||||
.toString().getBytes();
|
||||
out.write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyTraceFile(File originFile, int originStart, File traceFile,
|
||||
int traceStart, int length) throws IOException {
|
||||
assertTrue(originFile.exists());
|
||||
assertTrue(traceFile.exists());
|
||||
assertTrue(originStart < originFile.length());
|
||||
try (GZIPInputStream traceIn = new GZIPInputStream(new FileInputStream(traceFile));
|
||||
BufferedInputStream originIn = new BufferedInputStream(
|
||||
new FileInputStream(originFile))) {
|
||||
assertEquals(traceStart, traceIn.skip(traceStart));
|
||||
assertEquals(originStart, originIn.skip(originStart));
|
||||
byte[] buf1 = new byte[8192];
|
||||
byte[] buf2 = new byte[8192];
|
||||
while (length > 0) {
|
||||
int len = traceIn.read(buf1, 0, Math.min(buf1.length, length));
|
||||
assertEquals(len, originIn.read(buf2, 0, len));
|
||||
assertTrue(ArrayUtils.equals(buf1, buf2, len));
|
||||
length -= len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
|
||||
int connectionGroup, int procState, long pss, long rss,
|
||||
String processName, String packageName) {
|
||||
|
||||
Reference in New Issue
Block a user