Merge "Revert "Revert "Un-expose ACTION_SERVICE_PROVIDERS_UPDATED.""" into rvc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index b6f85b2..e14ca99 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -23,8 +23,6 @@
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
-import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_APP_UPDATE;
-import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY;
import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT;
@@ -75,6 +73,7 @@
import android.content.pm.ParceledListSlice;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
+import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -305,7 +304,10 @@
private final AppStandbyHandler mHandler;
private final Context mContext;
+ // TODO: Provide a mechanism to set an external bucketing service
+
private AppWidgetManager mAppWidgetManager;
+ private ConnectivityManager mConnectivityManager;
private PackageManager mPackageManager;
Injector mInjector;
@@ -409,6 +411,7 @@
settingsObserver.updateSettings();
mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
mInjector.registerDisplayListener(mDisplayListener, mHandler);
synchronized (mAppIdleLock) {
@@ -1516,38 +1519,6 @@
}
}
- /**
- * Remove an app from the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
- * bucket if it was forced into the bucket by the system because it was buggy.
- */
- @VisibleForTesting
- void maybeUnrestrictBuggyApp(String packageName, int userId) {
- synchronized (mAppIdleLock) {
- final long elapsedRealtime = mInjector.elapsedRealtime();
- final AppIdleHistory.AppUsageHistory app =
- mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime);
- if (app.currentBucket != STANDBY_BUCKET_RESTRICTED
- || (app.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_FORCED_BY_SYSTEM) {
- return;
- }
-
- final int newBucket;
- final int newReason;
- if ((app.bucketingReason & REASON_SUB_MASK) == REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY) {
- // If bugginess was the only reason the app should be restricted, then lift it out.
- newBucket = STANDBY_BUCKET_RARE;
- newReason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_UPDATE;
- } else {
- // There's another reason the app was restricted. Remove the buggy bit and call
- // it a day.
- newBucket = STANDBY_BUCKET_RESTRICTED;
- newReason = app.bucketingReason & ~REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY;
- }
- mAppIdleHistory.setAppStandbyBucket(
- packageName, userId, elapsedRealtime, newBucket, newReason);
- }
- }
-
private class PackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1557,14 +1528,10 @@
clearCarrierPrivilegedApps();
}
if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
- Intent.ACTION_PACKAGE_ADDED.equals(action))) {
- final String pkgName = intent.getData().getSchemeSpecificPart();
- final int userId = getSendingUserId();
- if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- maybeUnrestrictBuggyApp(pkgName, userId);
- } else {
- clearAppIdleForPackage(pkgName, userId);
- }
+ Intent.ACTION_PACKAGE_ADDED.equals(action))
+ && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
+ getSendingUserId());
}
}
}
diff --git a/api/current.txt b/api/current.txt
index 61b427c..4f52862 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -288,9 +288,9 @@
field public static final int alignmentMode = 16843642; // 0x101037a
field public static final int allContactsName = 16843468; // 0x10102cc
field public static final int allowAudioPlaybackCapture = 16844289; // 0x1010601
+ field public static final int allowAutoRevokePermissionsExemption = 16844309; // 0x1010615
field public static final int allowBackup = 16843392; // 0x1010280
field public static final int allowClearUserData = 16842757; // 0x1010005
- field public static final int allowDontAutoRevokePermissions = 16844309; // 0x1010615
field public static final int allowEmbedded = 16843765; // 0x10103f5
field public static final int allowNativeHeapPointerTagging = 16844307; // 0x1010613
field public static final int allowParallelSyncs = 16843570; // 0x1010332
@@ -1140,7 +1140,7 @@
field public static final int reqKeyboardType = 16843304; // 0x1010228
field public static final int reqNavigation = 16843306; // 0x101022a
field public static final int reqTouchScreen = 16843303; // 0x1010227
- field public static final int requestDontAutoRevokePermissions = 16844308; // 0x1010614
+ field public static final int requestAutoRevokePermissionsExemption = 16844308; // 0x1010614
field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
@@ -4049,6 +4049,7 @@
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 @@
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;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f4ee8fa..b3a0be1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3628,11 +3628,40 @@
}
}
+ /**
+ * 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);
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 5df3257..61be01f 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -23,7 +23,9 @@
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.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 @@
// 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,
@@ -557,6 +584,54 @@
}
/**
+ * 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}.
*
* For internal use only.
@@ -569,7 +644,7 @@
/**
* 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 @@
/**
* 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 @@
}
/**
- * @see {@link #getPid}
+ * @see #getPid
*
* @hide
*/
@@ -611,7 +684,7 @@
}
/**
- * @see {@link #getRealUid}
+ * @see #getRealUid
*
* @hide
*/
@@ -620,7 +693,7 @@
}
/**
- * @see {@link #getPackageUid}
+ * @see #getPackageUid
*
* @hide
*/
@@ -629,7 +702,7 @@
}
/**
- * @see {@link #getDefiningUid}
+ * @see #getDefiningUid
*
* @hide
*/
@@ -638,7 +711,7 @@
}
/**
- * @see {@link #getProcessName}
+ * @see #getProcessName
*
* @hide
*/
@@ -647,7 +720,7 @@
}
/**
- * @see {@link #getReason}
+ * @see #getReason
*
* @hide
*/
@@ -656,7 +729,7 @@
}
/**
- * @see {@link #getStatus}
+ * @see #getStatus
*
* @hide
*/
@@ -665,7 +738,7 @@
}
/**
- * @see {@link #getImportance}
+ * @see #getImportance
*
* @hide
*/
@@ -674,7 +747,7 @@
}
/**
- * @see {@link #getPss}
+ * @see #getPss
*
* @hide
*/
@@ -683,7 +756,7 @@
}
/**
- * @see {@link #getRss}
+ * @see #getRss
*
* @hide
*/
@@ -692,7 +765,7 @@
}
/**
- * @see {@link #getTimestamp}
+ * @see #getTimestamp
*
* @hide
*/
@@ -701,7 +774,7 @@
}
/**
- * @see {@link #getDescription}
+ * @see #getDescription
*
* @hide
*/
@@ -710,7 +783,7 @@
}
/**
- * @see {@link #getSubReason}
+ * @see #getSubReason
*
* @hide
*/
@@ -719,7 +792,7 @@
}
/**
- * @see {@link #getConnectionGroup}
+ * @see #getConnectionGroup
*
* @hide
*/
@@ -728,7 +801,7 @@
}
/**
- * @see {@link #getPackageName}
+ * @see #getPackageName
*
* @hide
*/
@@ -737,7 +810,7 @@
}
/**
- * @see {@link #getPackageList}
+ * @see #getPackageList
*
* @hide
*/
@@ -745,6 +818,33 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 6f0611e..b8221b4 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -652,4 +652,27 @@
*/
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);
}
diff --git a/core/java/android/app/IAppTraceRetriever.aidl b/core/java/android/app/IAppTraceRetriever.aidl
new file mode 100644
index 0000000..1463da7
--- /dev/null
+++ b/core/java/android/app/IAppTraceRetriever.aidl
@@ -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);
+}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 0a67802..0d66198 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -204,16 +204,6 @@
/** @hide */
public static final int REASON_SUB_MASK = 0x00FF;
/**
- * The reason for using the default main reason is unknown or undefined.
- * @hide
- */
- public static final int REASON_SUB_DEFAULT_UNDEFINED = 0x0000;
- /**
- * The app was updated.
- * @hide
- */
- public static final int REASON_SUB_DEFAULT_APP_UPDATE = 0x0001;
- /**
* The app was interacted with in some way by the system.
* @hide
*/
@@ -1079,14 +1069,6 @@
switch (standbyReason & REASON_MAIN_MASK) {
case REASON_MAIN_DEFAULT:
sb.append("d");
- switch (subReason) {
- case REASON_SUB_DEFAULT_UNDEFINED:
- // Historically, undefined didn't have a string, so don't add anything here.
- break;
- case REASON_SUB_DEFAULT_APP_UPDATE:
- sb.append("-au");
- break;
- }
break;
case REASON_MAIN_FORCED_BY_SYSTEM:
sb.append("s");
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index e41ed85..6f8acb6 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -1820,8 +1820,8 @@
.setUseEmbeddedDex(bool(false, R.styleable.AndroidManifestApplication_useEmbeddedDex, sa))
.setUsesNonSdkApi(bool(false, R.styleable.AndroidManifestApplication_usesNonSdkApi, sa))
.setVmSafeMode(bool(false, R.styleable.AndroidManifestApplication_vmSafeMode, sa))
- .setDontAutoRevokePermissions(bool(false, R.styleable.AndroidManifestApplication_requestDontAutoRevokePermissions, sa))
- .setAllowDontAutoRevokePermissions(bool(false, R.styleable.AndroidManifestApplication_allowDontAutoRevokePermissions, sa))
+ .setDontAutoRevokePermissions(bool(false, R.styleable.AndroidManifestApplication_requestAutoRevokePermissionsExemption, sa))
+ .setAllowDontAutoRevokePermissions(bool(false, R.styleable.AndroidManifestApplication_allowAutoRevokePermissionsExemption, sa))
// targetSdkVersion gated
.setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
.setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index b587fbe..cd22ad6 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -98,7 +98,10 @@
}
/**
- * Release the SurfaceControl associated with the SurfacePackage.
+ * Release the {@link SurfaceControl} associated with this package.
+ * It's not necessary to call this if you pass the package to
+ * {@link SurfaceView#setChildSurfacePackage} as {@link SurfaceView} will
+ * take ownership in that case.
*/
public void release() {
if (mSurfaceControl != null) {
@@ -230,7 +233,7 @@
* and render the object unusable.
*/
public void release() {
- mViewRoot.dispatchDetachedFromWindow();
+ mViewRoot.die(false /* immediate */);
mSurfaceControl.release();
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1f7c3504..3e1e393 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -496,8 +496,17 @@
updateSurface();
releaseSurfaces();
- mHaveFrame = false;
+ // We don't release this as part of releaseSurfaces as
+ // that is also called on transient visibility changes. We can't
+ // recreate this Surface, so only release it when we are fully
+ // detached.
+ if (mSurfacePackage != null) {
+ mSurfacePackage.release();
+ mSurfacePackage = null;
+ }
+
+ mHaveFrame = false;
super.onDetachedFromWindow();
}
@@ -1546,7 +1555,9 @@
* Display the view-hierarchy embedded within a {@link SurfaceControlViewHost.SurfacePackage}
* within this SurfaceView. If this SurfaceView is above it's host Surface (see
* {@link #setZOrderOnTop} then the embedded Surface hierarchy will be able to receive
- * input.
+ * input. This will take ownership of the SurfaceControl contained inside the SurfacePackage
+ * and free the caller of the obligation to call
+ * {@link SurfaceControlViewHost.SurfacePackage#release}.
*
* @param p The SurfacePackage to embed.
*/
@@ -1556,6 +1567,7 @@
mSurfacePackage.getSurfaceControl() : null;
if (mSurfaceControl != null && lastSc != null) {
mTmpTransaction.reparent(lastSc, null).apply();
+ mSurfacePackage.release();
} else if (mSurfaceControl != null) {
reparentSurfacePackage(mTmpTransaction, p);
mTmpTransaction.apply();
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 728824c..d661bc6 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -24,7 +24,6 @@
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.MovementMethod;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityNodeInfo;
/*
* This is supposed to be a *very* thin veneer over TextView.
@@ -179,13 +178,4 @@
protected boolean supportsAutoSizeText() {
return false;
}
-
- /** @hide */
- @Override
- public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfoInternal(info);
- if (isEnabled()) {
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
- }
- }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 16d9ed3..4aeea10 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -317,6 +317,7 @@
private SelectionActionModeHelper mSelectionActionModeHelper;
boolean mIsBeingLongClicked;
+ boolean mIsBeingLongClickedByAccessibility;
private SuggestionsPopupWindow mSuggestionsPopupWindow;
SuggestionRangeSpan mSuggestionRangeSpan;
@@ -1312,6 +1313,12 @@
if (TextView.DEBUG_CURSOR) {
logCursor("performLongClick", "handled=%s", handled);
}
+ if (mIsBeingLongClickedByAccessibility) {
+ if (!handled) {
+ toggleInsertionActionMode();
+ }
+ return true;
+ }
// Long press in empty space moves cursor and starts the insertion action mode.
if (!handled && !isPositionOnText(mTouchState.getLastDownX(), mTouchState.getLastDownY())
&& !mTouchState.isOnHandle() && mInsertionControllerEnabled) {
@@ -1359,6 +1366,14 @@
return handled;
}
+ private void toggleInsertionActionMode() {
+ if (mTextActionMode != null) {
+ stopTextActionMode();
+ } else {
+ startInsertionActionMode();
+ }
+ }
+
float getLastUpPositionX() {
return mTouchState.getLastUpX();
}
@@ -5436,11 +5451,7 @@
config.getScaledTouchSlop());
if (isWithinTouchSlop) {
// Tapping on the handle toggles the insertion action mode.
- if (mTextActionMode != null) {
- stopTextActionMode();
- } else {
- startInsertionActionMode();
- }
+ toggleInsertionActionMode();
}
} else {
if (mTextActionMode != null) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 2168018..e178318 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12108,6 +12108,23 @@
onEditorAction(getImeActionId());
}
} return true;
+ case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
+ if (isLongClickable()) {
+ boolean handled;
+ if (isEnabled() && (mBufferType == BufferType.EDITABLE)) {
+ mEditor.mIsBeingLongClickedByAccessibility = true;
+ try {
+ handled = performLongClick();
+ } finally {
+ mEditor.mIsBeingLongClickedByAccessibility = false;
+ }
+ } else {
+ handled = performLongClick();
+ }
+ return handled;
+ }
+ }
+ return false;
default: {
return super.performAccessibilityActionInternal(action, arguments);
}
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
index 66173f6..4b9444e 100644
--- a/core/proto/android/app/appexitinfo.proto
+++ b/core/proto/android/app/appexitinfo.proto
@@ -42,4 +42,6 @@
optional int64 rss = 12;
optional int64 timestamp = 13;
optional string description = 14;
+ optional bytes state = 15;
+ optional string trace_file = 16;
}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 93b2063..2496900 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1832,7 +1832,7 @@
revoked when the app is unused for an extended amount of time.
The default value is {@code false}. -->
- <attr name="requestDontAutoRevokePermissions" format="boolean" />
+ <attr name="requestAutoRevokePermissionsExemption" format="boolean" />
<!-- If {@code true} its permissions shouldn't get automatically
revoked when the app is unused for an extended amount of time.
@@ -1840,7 +1840,7 @@
This implies {@code requestDontAutoRevokePermissions=true}
The default value is {@code false}. -->
- <attr name="allowDontAutoRevokePermissions" format="boolean" />
+ <attr name="allowAutoRevokePermissionsExemption" format="boolean" />
</declare-styleable>
<!-- An attribution is a logical part of an app and is identified by a tag.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index cf68aff..5306518 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3014,8 +3014,8 @@
<!-- @hide @SystemApi -->
<public name="minExtensionVersion" />
<public name="allowNativeHeapPointerTagging" />
- <public name="requestDontAutoRevokePermissions" />
- <public name="allowDontAutoRevokePermissions" />
+ <public name="requestAutoRevokePermissionsExemption" />
+ <public name="allowAutoRevokePermissionsExemption" />
<public name="preserveLegacyExternalStorage" />
<public name="mimeGroup" />
<public name="enableGwpAsan" />
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index a72be25..45d4b38 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -65,6 +65,7 @@
import android.app.Instrumentation;
import android.content.ClipData;
import android.content.ClipboardManager;
+import android.os.Bundle;
import android.support.test.uiautomator.UiDevice;
import android.text.InputType;
import android.text.Selection;
@@ -75,6 +76,7 @@
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
@@ -358,6 +360,20 @@
}
@Test
+ public void testToolbarAppearsAccessibilityLongClick() throws Throwable {
+ final String text = "Toolbar appears after performing accessibility's ACTION_LONG_CLICK.";
+ mActivityRule.runOnUiThread(() -> {
+ final TextView textView = mActivity.findViewById(R.id.textview);
+ final Bundle args = new Bundle();
+ textView.performAccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK, args);
+ });
+ mInstrumentation.waitForIdleSync();
+
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+ }
+
+ @Test
public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable {
final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
final int position = (textLink.getStart() + textLink.getEnd()) / 2;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ebca1f7..a458498 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -579,6 +579,13 @@
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 @@
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 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 @@
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 @@
}
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 @@
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 @@
// 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 @@
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 @@
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 @@
}
}
Slog.i(TAG, "Done dumping");
+ return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
}
@Override
@@ -10280,6 +10331,15 @@
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 @@
* @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;
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 028a059..0c3d02d 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -17,23 +17,31 @@
package com.android.server.am;
import static android.app.ActivityManager.RunningAppProcessInfo.procStateToImportance;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
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.annotation.Nullable;
import android.app.ApplicationExitInfo;
import android.app.ApplicationExitInfo.Reason;
import android.app.ApplicationExitInfo.SubReason;
+import android.app.IAppTraceRetriever;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.icu.text.SimpleDateFormat;
+import android.os.Binder;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.system.OsConstants;
@@ -52,12 +60,17 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ProcessMap;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.IoThread;
import com.android.server.ServiceThread;
import com.android.server.SystemServiceManager;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
@@ -68,6 +81,10 @@
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.zip.GZIPOutputStream;
/**
* A class to manage all the {@link android.app.ApplicationExitInfo} records.
@@ -80,16 +97,21 @@
*/
private static final long APP_EXIT_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30);
- /** These are actions that the forEachPackage should take after each iteration */
+ /** These are actions that the forEach* should take after each iteration */
private static final int FOREACH_ACTION_NONE = 0;
- private static final int FOREACH_ACTION_REMOVE_PACKAGE = 1;
+ private static final int FOREACH_ACTION_REMOVE_ITEM = 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_STORE_DIR = "procexitstore";
+
+ @VisibleForTesting
static final String APP_EXIT_INFO_FILE = "procexitinfo";
+ private static final String APP_TRACE_FILE_SUFFIX = ".gz";
+
private final Object mLock = new Object();
/**
@@ -153,6 +175,13 @@
final ArrayList<ApplicationExitInfo> mTmpInfoList2 = new ArrayList<ApplicationExitInfo>();
/**
+ * The path to the directory which includes the historical proc exit info file
+ * as specified in {@link #mProcExitInfoFile}, as well as the associated trace files.
+ */
+ @VisibleForTesting
+ File mProcExitStoreDir;
+
+ /**
* The path to the historical proc exit info file, persisted in the storage.
*/
@VisibleForTesting
@@ -176,6 +205,35 @@
final AppExitInfoExternalSource mAppExitInfoSourceLmkd =
new AppExitInfoExternalSource("lmkd", ApplicationExitInfo.REASON_LOW_MEMORY);
+ /**
+ * The active per-UID/PID state data set by
+ * {@link android.app.ActivityManager#setProcessStateSummary};
+ * these state data are to be "claimed" when its process dies, by then the data will be moved
+ * from this list to the new instance of ApplicationExitInfo.
+ *
+ * <p> The mapping here is UID -> PID -> state </p>
+ *
+ * @see android.app.ActivityManager#setProcessStateSummary(byte[])
+ */
+ @GuardedBy("mLock")
+ final SparseArray<SparseArray<byte[]>> mActiveAppStateSummary = new SparseArray<>();
+
+ /**
+ * The active per-UID/PID trace file when an ANR occurs but the process hasn't been killed yet,
+ * each record is a path to the actual trace file; these files are to be "claimed"
+ * when its process dies, by then the "ownership" of the files will be transferred
+ * from this list to the new instance of ApplicationExitInfo.
+ *
+ * <p> The mapping here is UID -> PID -> file </p>
+ */
+ @GuardedBy("mLock")
+ final SparseArray<SparseArray<File>> mActiveAppTraces = new SparseArray<>();
+
+ /**
+ * The implementation of the interface IAppTraceRetriever.
+ */
+ final AppTraceRetriever mAppTraceRetriever = new AppTraceRetriever();
+
AppExitInfoTracker() {
mData = new ProcessMap<AppExitInfoContainer>();
mRawRecordsPool = new SynchronizedPool<ApplicationExitInfo>(APP_EXIT_RAW_INFO_POOL_SIZE);
@@ -187,7 +245,13 @@
THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
thread.start();
mKillHandler = new KillHandler(thread.getLooper());
- mProcExitInfoFile = new File(SystemServiceManager.ensureSystemDir(), APP_EXIT_INFO_FILE);
+
+ mProcExitStoreDir = new File(SystemServiceManager.ensureSystemDir(), APP_EXIT_STORE_DIR);
+ if (!FileUtils.createDir(mProcExitStoreDir)) {
+ Slog.e(TAG, "Unable to create " + mProcExitStoreDir);
+ return;
+ }
+ mProcExitInfoFile = new File(mProcExitStoreDir, APP_EXIT_INFO_FILE);
mAppExitInfoHistoryListSize = service.mContext.getResources().getInteger(
com.android.internal.R.integer.config_app_exit_info_history_list_size);
@@ -304,7 +368,7 @@
+ "(" + raw.getPid() + "/u" + raw.getRealUid() + ")");
}
- ApplicationExitInfo info = getExitInfo(raw.getPackageName(),
+ ApplicationExitInfo info = getExitInfoLocked(raw.getPackageName(),
raw.getPackageUid(), raw.getPid());
// query zygote and lmkd to get the exit info, and clear the saved info
@@ -312,7 +376,7 @@
raw.getPid(), raw.getRealUid());
Pair<Long, Object> lmkd = mAppExitInfoSourceLmkd.remove(
raw.getPid(), raw.getRealUid());
- mIsolatedUidRecords.removeIsolatedUid(raw.getRealUid());
+ mIsolatedUidRecords.removeIsolatedUidLocked(raw.getRealUid());
if (info == null) {
info = addExitInfoLocked(raw);
@@ -333,7 +397,7 @@
@VisibleForTesting
@GuardedBy("mLock")
void handleNoteAppKillLocked(final ApplicationExitInfo raw) {
- ApplicationExitInfo info = getExitInfo(
+ ApplicationExitInfo info = getExitInfoLocked(
raw.getPackageName(), raw.getPackageUid(), raw.getPid());
if (info == null) {
@@ -359,7 +423,7 @@
final String[] packages = raw.getPackageList();
final int uid = raw.getPackageUid();
for (int i = 0; i < packages.length; i++) {
- addExitInfoInner(packages[i], uid, info);
+ addExitInfoInnerLocked(packages[i], uid, info);
}
schedulePersistProcessExitInfo(false);
@@ -400,39 +464,37 @@
*
* @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;
+ @GuardedBy("mLock")
+ private boolean updateExitInfoIfNecessaryLocked(
+ int pid, int uid, Integer status, Integer reason) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+ if (k != null) {
+ uid = k;
}
+ ArrayList<ApplicationExitInfo> tlist = mTmpInfoList;
+ tlist.clear();
+ final int targetUid = uid;
+ forEachPackageLocked((packageName, records) -> {
+ AppExitInfoContainer container = records.get(targetUid);
+ if (container == null) {
+ return FOREACH_ACTION_NONE;
+ }
+ tlist.clear();
+ container.getExitInfoLocked(pid, 1, tlist);
+ if (tlist.size() == 0) {
+ 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;
}
/**
@@ -441,38 +503,43 @@
@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);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ boolean emptyPackageName = TextUtils.isEmpty(packageName);
+ if (!emptyPackageName) {
+ // fast path
+ AppExitInfoContainer container = mData.get(packageName, filterUid);
if (container != null) {
- mTmpInfoList.clear();
- results.addAll(container.toListLocked(mTmpInfoList, filterPid));
+ container.getExitInfoLocked(filterPid, maxNum, results);
}
- return AppExitInfoTracker.FOREACH_ACTION_NONE;
- });
+ } else {
+ // slow path
+ final ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
+ list.clear();
+ // get all packages
+ forEachPackageLocked((name, records) -> {
+ AppExitInfoContainer container = records.get(filterUid);
+ if (container != null) {
+ mTmpInfoList.clear();
+ 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);
+ 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();
}
- for (int i = 0; i < size; i++) {
- results.add(list.get(i));
- }
- list.clear();
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
@@ -480,17 +547,16 @@
* Return the first matching exit info record, for internal use, the parameters are not supposed
* to be empty.
*/
- private ApplicationExitInfo getExitInfo(final String packageName,
+ @GuardedBy("mLock")
+ private ApplicationExitInfo getExitInfoLocked(final String packageName,
final int filterUid, final int filterPid) {
- synchronized (mLock) {
- ArrayList<ApplicationExitInfo> list = mTmpInfoList;
- list.clear();
- getExitInfo(packageName, filterUid, filterPid, 1, list);
+ 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;
- }
+ ApplicationExitInfo info = list.size() > 0 ? list.get(0) : null;
+ list.clear();
+ return info;
}
@VisibleForTesting
@@ -498,8 +564,10 @@
mAppExitInfoSourceZygote.removeByUserId(userId);
mAppExitInfoSourceLmkd.removeByUserId(userId);
mIsolatedUidRecords.removeByUserId(userId);
- removeByUserId(userId);
- schedulePersistProcessExitInfo(true);
+ synchronized (mLock) {
+ removeByUserIdLocked(userId);
+ schedulePersistProcessExitInfo(true);
+ }
}
@VisibleForTesting
@@ -507,13 +575,16 @@
if (packageName != null) {
final boolean removeUid = TextUtils.isEmpty(
mService.mPackageManagerInt.getNameForUid(uid));
- if (removeUid) {
- mAppExitInfoSourceZygote.removeByUid(uid, allUsers);
- mAppExitInfoSourceLmkd.removeByUid(uid, allUsers);
- mIsolatedUidRecords.removeAppUid(uid, allUsers);
+ synchronized (mLock) {
+ if (removeUid) {
+ mAppExitInfoSourceZygote.removeByUidLocked(uid, allUsers);
+ mAppExitInfoSourceLmkd.removeByUidLocked(uid, allUsers);
+ mIsolatedUidRecords.removeAppUid(uid, allUsers);
+ }
+ removePackageLocked(packageName, uid, removeUid,
+ allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid));
+ schedulePersistProcessExitInfo(true);
}
- removePackage(packageName, allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid));
- schedulePersistProcessExitInfo(true);
}
}
@@ -593,6 +664,7 @@
}
}
synchronized (mLock) {
+ pruneAnrTracesIfNecessaryLocked();
mAppExitInfoLoaded = true;
}
}
@@ -634,7 +706,7 @@
ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP, now);
synchronized (mLock) {
- forEachPackage((packageName, records) -> {
+ forEachPackageLocked((packageName, records) -> {
long token = proto.start(AppsExitInfoProto.PACKAGES);
proto.write(AppsExitInfoProto.Package.PACKAGE_NAME, packageName);
int uidArraySize = records.size();
@@ -688,6 +760,9 @@
mProcExitInfoFile.delete();
}
mData.getMap().clear();
+ mActiveAppStateSummary.clear();
+ mActiveAppTraces.clear();
+ pruneAnrTracesIfNecessaryLocked();
}
}
@@ -695,15 +770,15 @@
* 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);
+ if (TextUtils.isEmpty(packageName)) {
+ synchronized (mLock) {
+ removeByUserIdLocked(userId);
+ }
+ } else {
+ final int uid = mService.mPackageManagerInt.getPackageUid(packageName,
+ PackageManager.MATCH_ALL, userId);
+ synchronized (mLock) {
+ removePackageLocked(packageName, uid, true, userId);
}
}
schedulePersistProcessExitInfo(true);
@@ -716,7 +791,7 @@
pw.println("Last Timestamp of Persistence Into Persistent Storage: "
+ sdf.format(new Date(mLastAppExitInfoPersistTimestamp)));
if (TextUtils.isEmpty(packageName)) {
- forEachPackage((name, records) -> {
+ forEachPackageLocked((name, records) -> {
dumpHistoryProcessExitInfoLocked(pw, " ", name, records, sdf);
return AppExitInfoTracker.FOREACH_ACTION_NONE;
});
@@ -741,86 +816,108 @@
}
}
- 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();
+ @GuardedBy("mLock")
+ private void addExitInfoInnerLocked(String packageName, int userId, ApplicationExitInfo info) {
+ 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;
}
- mData.put(packageName, userId, container);
+ } else {
+ container.mUid = info.getRealUid();
}
- container.addExitInfoLocked(info);
+ mData.put(packageName, userId, container);
}
+ container.addExitInfoLocked(info);
}
- private void forEachPackage(
+ @GuardedBy("mLocked")
+ private void forEachPackageLocked(
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);
+ 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_ITEM:
+ final SparseArray<AppExitInfoContainer> records = map.valueAt(i);
+ for (int j = records.size() - 1; j >= 0; j--) {
+ records.valueAt(j).destroyLocked();
+ }
+ map.removeAt(i);
break;
- }
- }
- if (array.size() == 0) {
- map.remove(packageName);
+ case FOREACH_ACTION_STOP_ITERATION:
+ i = 0;
+ break;
+ case FOREACH_ACTION_NONE:
+ default:
+ break;
}
}
}
}
- private void removeByUserId(final int userId) {
- if (userId == UserHandle.USER_ALL) {
- synchronized (mLock) {
- mData.getMap().clear();
+ @GuardedBy("mLocked")
+ private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) {
+ if (removeUid) {
+ mActiveAppStateSummary.remove(uid);
+ final int idx = mActiveAppTraces.indexOfKey(uid);
+ if (idx >= 0) {
+ final SparseArray<File> array = mActiveAppTraces.valueAt(idx);
+ for (int i = array.size() - 1; i >= 0; i--) {
+ array.valueAt(i).delete();
+ }
+ mActiveAppTraces.removeAt(idx);
}
+ }
+ ArrayMap<String, SparseArray<AppExitInfoContainer>> map = mData.getMap();
+ SparseArray<AppExitInfoContainer> array = map.get(packageName);
+ if (array == null) {
return;
}
- forEachPackage((packageName, records) -> {
+ if (userId == UserHandle.USER_ALL) {
+ for (int i = array.size() - 1; i >= 0; i--) {
+ array.valueAt(i).destroyLocked();
+ }
+ mData.getMap().remove(packageName);
+ } else {
+ for (int i = array.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(array.keyAt(i)) == userId) {
+ array.valueAt(i).destroyLocked();
+ array.removeAt(i);
+ break;
+ }
+ }
+ if (array.size() == 0) {
+ map.remove(packageName);
+ }
+ }
+ }
+
+ @GuardedBy("mLocked")
+ private void removeByUserIdLocked(final int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ mData.getMap().clear();
+ mActiveAppStateSummary.clear();
+ mActiveAppTraces.clear();
+ pruneAnrTracesIfNecessaryLocked();
+ return;
+ }
+ removeFromSparse2dArray(mActiveAppStateSummary,
+ (v) -> UserHandle.getUserId(v) == userId, null, null);
+ removeFromSparse2dArray(mActiveAppTraces,
+ (v) -> UserHandle.getUserId(v) == userId, null, (v) -> v.delete());
+ forEachPackageLocked((packageName, records) -> {
for (int i = records.size() - 1; i >= 0; i--) {
if (UserHandle.getUserId(records.keyAt(i)) == userId) {
+ records.valueAt(i).destroyLocked();
records.removeAt(i);
break;
}
}
- return records.size() == 0 ? FOREACH_ACTION_REMOVE_PACKAGE : FOREACH_ACTION_NONE;
+ return records.size() == 0 ? FOREACH_ACTION_REMOVE_ITEM : FOREACH_ACTION_NONE;
});
}
@@ -862,6 +959,262 @@
}
/**
+ * Called from {@link ActivityManagerService#setProcessStateSummary}.
+ */
+ @VisibleForTesting
+ void setProcessStateSummary(int uid, final int pid, final byte[] data) {
+ synchronized (mLock) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+ if (k != null) {
+ uid = k;
+ }
+ putToSparse2dArray(mActiveAppStateSummary, uid, pid, data, SparseArray::new, null);
+ }
+ }
+
+ @VisibleForTesting
+ @Nullable byte[] getProcessStateSummary(int uid, final int pid) {
+ synchronized (mLock) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+ if (k != null) {
+ uid = k;
+ }
+ int index = mActiveAppStateSummary.indexOfKey(uid);
+ if (index < 0) {
+ return null;
+ }
+ return mActiveAppStateSummary.valueAt(index).get(pid);
+ }
+ }
+
+ /**
+ * Called from ProcessRecord when an ANR occurred and the ANR trace is taken.
+ */
+ void scheduleLogAnrTrace(final int pid, final int uid, final String[] packageList,
+ final File traceFile, final long startOff, final long endOff) {
+ mKillHandler.sendMessage(PooledLambda.obtainMessage(
+ this::handleLogAnrTrace, pid, uid, packageList,
+ traceFile, startOff, endOff));
+ }
+
+ /**
+ * Copy and compress the given ANR trace file
+ */
+ @VisibleForTesting
+ void handleLogAnrTrace(final int pid, int uid, final String[] packageList,
+ final File traceFile, final long startOff, final long endOff) {
+ if (!traceFile.exists() || ArrayUtils.isEmpty(packageList)) {
+ return;
+ }
+ final long size = traceFile.length();
+ final long length = endOff - startOff;
+ if (startOff >= size || endOff > size || length <= 0) {
+ return;
+ }
+
+ final File outFile = new File(mProcExitStoreDir, traceFile.getName()
+ + APP_TRACE_FILE_SUFFIX);
+ // Copy & compress
+ if (copyToGzFile(traceFile, outFile, startOff, length)) {
+ // Wrote successfully.
+ synchronized (mLock) {
+ Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+ if (k != null) {
+ uid = k;
+ }
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "Stored ANR traces of " + pid + "/u" + uid + " in " + outFile);
+ }
+ boolean pending = true;
+ // Unlikely but possible: the app has died
+ for (int i = 0; i < packageList.length; i++) {
+ final AppExitInfoContainer container = mData.get(packageList[i], uid);
+ // Try to see if we could append this trace to an existing record
+ if (container != null && container.appendTraceIfNecessaryLocked(pid, outFile)) {
+ // Okay someone took it
+ pending = false;
+ }
+ }
+ if (pending) {
+ // Save it into a temporary list for later use (when the app dies).
+ putToSparse2dArray(mActiveAppTraces, uid, pid, outFile,
+ SparseArray::new, (v) -> v.delete());
+ }
+ }
+ }
+ }
+
+ /**
+ * Copy the given portion of the file into a gz file.
+ *
+ * @param inFile The source file.
+ * @param outFile The destination file, which will be compressed in gzip format.
+ * @param start The start offset where the copy should start from.
+ * @param length The number of bytes that should be copied.
+ * @return If the copy was successful or not.
+ */
+ private static boolean copyToGzFile(final File inFile, final File outFile,
+ final long start, final long length) {
+ long remaining = length;
+ try (
+ BufferedInputStream in = new BufferedInputStream(new FileInputStream(inFile));
+ GZIPOutputStream out = new GZIPOutputStream(new BufferedOutputStream(
+ new FileOutputStream(outFile)))) {
+ final byte[] buffer = new byte[8192];
+ in.skip(start);
+ while (remaining > 0) {
+ int t = in.read(buffer, 0, (int) Math.min(buffer.length, remaining));
+ if (t < 0) {
+ break;
+ }
+ out.write(buffer, 0, t);
+ remaining -= t;
+ }
+ } catch (IOException e) {
+ if (DEBUG_PROCESSES) {
+ Slog.e(TAG, "Error in copying ANR trace from " + inFile + " to " + outFile, e);
+ }
+ return false;
+ }
+ return remaining == 0 && outFile.exists();
+ }
+
+ /**
+ * In case there is any orphan ANR trace file, remove it.
+ */
+ @GuardedBy("mLock")
+ private void pruneAnrTracesIfNecessaryLocked() {
+ final ArraySet<String> allFiles = new ArraySet();
+ final File[] files = mProcExitStoreDir.listFiles((f) -> {
+ final String name = f.getName();
+ boolean trace = name.startsWith(ActivityManagerService.ANR_FILE_PREFIX)
+ && name.endsWith(APP_TRACE_FILE_SUFFIX);
+ if (trace) {
+ allFiles.add(name);
+ }
+ return trace;
+ });
+ if (ArrayUtils.isEmpty(files)) {
+ return;
+ }
+ // Find out the owners from the existing records
+ forEachPackageLocked((name, records) -> {
+ for (int i = records.size() - 1; i >= 0; i--) {
+ final AppExitInfoContainer container = records.valueAt(i);
+ container.forEachRecordLocked((pid, info) -> {
+ final File traceFile = info.getTraceFile();
+ if (traceFile != null) {
+ allFiles.remove(traceFile.getName());
+ }
+ return FOREACH_ACTION_NONE;
+ });
+ }
+ return AppExitInfoTracker.FOREACH_ACTION_NONE;
+ });
+ // See if there is any active process owns it.
+ forEachSparse2dArray(mActiveAppTraces, (v) -> allFiles.remove(v.getName()));
+
+ // Remove orphan traces if nobody claims it.
+ for (int i = allFiles.size() - 1; i >= 0; i--) {
+ (new File(mProcExitStoreDir, allFiles.valueAt(i))).delete();
+ }
+ }
+
+ /**
+ * A utility function to add the given value to the given 2d SparseArray
+ */
+ private static <T extends SparseArray<U>, U> void putToSparse2dArray(final SparseArray<T> array,
+ final int outerKey, final int innerKey, final U value, final Supplier<T> newInstance,
+ final Consumer<U> actionToOldValue) {
+ int idx = array.indexOfKey(outerKey);
+ T innerArray = null;
+ if (idx < 0) {
+ innerArray = newInstance.get();
+ array.put(outerKey, innerArray);
+ } else {
+ innerArray = array.valueAt(idx);
+ }
+ idx = innerArray.indexOfKey(innerKey);
+ if (idx >= 0) {
+ if (actionToOldValue != null) {
+ actionToOldValue.accept(innerArray.valueAt(idx));
+ }
+ innerArray.setValueAt(idx, value);
+ } else {
+ innerArray.put(innerKey, value);
+ }
+ }
+
+ /**
+ * A utility function to iterate through the given 2d SparseArray
+ */
+ private static <T extends SparseArray<U>, U> void forEachSparse2dArray(
+ final SparseArray<T> array, final Consumer<U> action) {
+ if (action != null) {
+ for (int i = array.size() - 1; i >= 0; i--) {
+ T innerArray = array.valueAt(i);
+ if (innerArray == null) {
+ continue;
+ }
+ for (int j = innerArray.size() - 1; j >= 0; j--) {
+ action.accept(innerArray.valueAt(j));
+ }
+ }
+ }
+ }
+
+ /**
+ * A utility function to remove elements from the given 2d SparseArray
+ */
+ private static <T extends SparseArray<U>, U> void removeFromSparse2dArray(
+ final SparseArray<T> array, final Predicate<Integer> outerPredicate,
+ final Predicate<Integer> innerPredicate, final Consumer<U> action) {
+ for (int i = array.size() - 1; i >= 0; i--) {
+ if (outerPredicate == null || outerPredicate.test(array.keyAt(i))) {
+ final T innerArray = array.valueAt(i);
+ if (innerArray == null) {
+ continue;
+ }
+ for (int j = innerArray.size() - 1; j >= 0; j--) {
+ if (innerPredicate == null || innerPredicate.test(innerArray.keyAt(j))) {
+ if (action != null) {
+ action.accept(innerArray.valueAt(j));
+ }
+ innerArray.removeAt(j);
+ }
+ }
+ if (innerArray.size() == 0) {
+ array.removeAt(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * A utility function to find and remove elements from the given 2d SparseArray.
+ */
+ private static <T extends SparseArray<U>, U> U findAndRemoveFromSparse2dArray(
+ final SparseArray<T> array, final int outerKey, final int innerKey) {
+ final int idx = array.indexOfKey(outerKey);
+ if (idx >= 0) {
+ T p = array.valueAt(idx);
+ if (p == null) {
+ return null;
+ }
+ final int innerIdx = p.indexOfKey(innerKey);
+ if (innerIdx >= 0) {
+ final U ret = p.valueAt(innerIdx);
+ p.removeAt(innerIdx);
+ if (p.size() == 0) {
+ array.removeAt(idx);
+ }
+ return ret;
+ }
+ }
+ return null;
+ }
+
+ /**
* A container class of {@link android.app.ApplicationExitInfo}
*/
final class AppExitInfoContainer {
@@ -934,10 +1287,68 @@
}
}
if (oldestIndex >= 0) {
+ final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile();
+ if (traceFile != null) {
+ traceFile.delete();
+ }
mInfos.removeAt(oldestIndex);
}
}
- mInfos.append(info.getPid(), info);
+ // Claim the state information if there is any
+ final int uid = info.getPackageUid();
+ final int pid = info.getPid();
+ info.setProcessStateSummary(findAndRemoveFromSparse2dArray(
+ mActiveAppStateSummary, uid, pid));
+ info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid));
+ info.setAppTraceRetriever(mAppTraceRetriever);
+ mInfos.append(pid, info);
+ }
+
+ @GuardedBy("mLock")
+ boolean appendTraceIfNecessaryLocked(final int pid, final File traceFile) {
+ final ApplicationExitInfo r = mInfos.get(pid);
+ if (r != null) {
+ r.setTraceFile(traceFile);
+ r.setAppTraceRetriever(mAppTraceRetriever);
+ return true;
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ void destroyLocked() {
+ for (int i = mInfos.size() - 1; i >= 0; i--) {
+ ApplicationExitInfo ai = mInfos.valueAt(i);
+ final File traceFile = ai.getTraceFile();
+ if (traceFile != null) {
+ traceFile.delete();
+ }
+ ai.setTraceFile(null);
+ ai.setAppTraceRetriever(null);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) {
+ if (callback != null) {
+ for (int i = mInfos.size() - 1; i >= 0; i--) {
+ switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
+ case FOREACH_ACTION_REMOVE_ITEM:
+ final File traceFile = mInfos.valueAt(i).getTraceFile();
+ if (traceFile != null) {
+ traceFile.delete();
+ }
+ mInfos.removeAt(i);
+ break;
+ case FOREACH_ACTION_STOP_ITERATION:
+ i = 0;
+ break;
+ case FOREACH_ACTION_NONE:
+ default:
+ break;
+ }
+ }
+ }
}
@GuardedBy("mLock")
@@ -1033,6 +1444,7 @@
}
}
+ @GuardedBy("mLock")
Integer getUidByIsolatedUid(int isolatedUid) {
if (UserHandle.isIsolated(isolatedUid)) {
synchronized (mLock) {
@@ -1053,6 +1465,7 @@
}
}
+ @VisibleForTesting
void removeAppUid(int uid, boolean allUsers) {
synchronized (mLock) {
if (allUsers) {
@@ -1071,23 +1484,22 @@
}
}
- int removeIsolatedUid(int isolatedUid) {
+ @GuardedBy("mLock")
+ int removeIsolatedUidLocked(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;
+ 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) {
@@ -1193,33 +1605,29 @@
mPresetReason = reason;
}
- void add(int pid, int uid, Object extra) {
- if (UserHandle.isIsolated(uid)) {
- Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
- if (k != null) {
- uid = k;
- }
+ @GuardedBy("mLock")
+ private void addLocked(int pid, int uid, Object extra) {
+ 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));
+ 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));
}
+ @VisibleForTesting
Pair<Long, Object> remove(int pid, int uid) {
- if (UserHandle.isIsolated(uid)) {
+ synchronized (mLock) {
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);
@@ -1228,8 +1636,8 @@
return isFresh(p.first) ? p : null;
}
}
+ return null;
}
- return null;
}
void removeByUserId(int userId) {
@@ -1250,7 +1658,8 @@
}
}
- void removeByUid(int uid, boolean allUsers) {
+ @GuardedBy("mLock")
+ void removeByUidLocked(int uid, boolean allUsers) {
if (UserHandle.isIsolated(uid)) {
Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
if (k != null) {
@@ -1260,17 +1669,13 @@
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);
- }
+ 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);
- }
+ mData.remove(uid);
}
}
@@ -1292,12 +1697,12 @@
// 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) {
+ if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason)) {
+ addLocked(pid, uid, status);
+ }
+
+ // Notify any interesed party regarding the lmkd kills
final BiConsumer<Integer, Integer> listener = mProcDiedListener;
if (listener != null) {
mService.mHandler.post(()-> listener.accept(pid, uid));
@@ -1305,4 +1710,51 @@
}
}
}
+
+ /**
+ * The implementation to the IAppTraceRetriever interface.
+ */
+ @VisibleForTesting
+ class AppTraceRetriever extends IAppTraceRetriever.Stub {
+ @Override
+ public ParcelFileDescriptor getTraceFileDescriptor(final String packageName,
+ final int uid, final int pid) {
+ mService.enforceNotIsolatedCaller("getTraceFileDescriptor");
+
+ if (TextUtils.isEmpty(packageName)) {
+ throw new IllegalArgumentException("Invalid package name");
+ }
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getCallingUserId();
+ final int userId = UserHandle.getUserId(uid);
+
+ mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_NON_FULL, "getTraceFileDescriptor", null);
+ if (mService.enforceDumpPermissionForPackage(packageName, userId,
+ callingUid, "getTraceFileDescriptor") != Process.INVALID_UID) {
+ synchronized (mLock) {
+ final ApplicationExitInfo info = getExitInfoLocked(packageName, uid, pid);
+ if (info == null) {
+ return null;
+ }
+ final File traceFile = info.getTraceFile();
+ if (traceFile == null) {
+ return null;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ // The fd will be closed after being written into Parcel
+ return ParcelFileDescriptor.open(traceFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 16a7c6b..0f9d61f 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -71,7 +71,6 @@
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.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.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 @@
*/
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;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e7f66bb..eec7519 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1621,9 +1621,11 @@
// 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 @@
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,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index efe8119..23381ff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -46,6 +46,7 @@
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.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.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 @@
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 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 05cd26df..f070bff 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -1208,80 +1208,6 @@
STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
}
- public void testAppUpdateOnRestrictedBucketStatus() {
- // Updates shouldn't change bucket if the app timed out.
- // Way past all timeouts. App times out into RESTRICTED bucket.
- reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
- mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
-
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
-
- // Updates shouldn't change bucket if the app was forced by the system for a non-buggy
- // reason.
- reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_FORCED_BY_SYSTEM
- | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
-
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
-
- // Updates should change bucket if the app was forced by the system for a buggy reason.
- reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
-
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertNotEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
-
- // Updates shouldn't change bucket if the app was forced by the system for more than just
- // a buggy reason.
- reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE
- | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
-
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
- getStandbyBucketReason(PACKAGE_1));
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
-
- // Updates shouldn't change bucket if the app was forced by the user.
- reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_FORCED_BY_USER);
-
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
- }
-
private String getAdminAppsStr(int userId) {
return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId));
}