diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 02b822a99f2ad..772845d4e6837 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -817,6 +817,9 @@ public final class StrictMode {
/** @hide */
public @NonNull Builder permitActivityLeaks() {
+ synchronized (StrictMode.class) {
+ sExpectedActivityInstanceCount.clear();
+ }
return disable(DETECT_VM_ACTIVITY_LEAKS);
}
@@ -2586,8 +2589,10 @@ public final class StrictMode {
return;
}
+ // Use the instance count from InstanceTracker as initial value.
Integer expected = sExpectedActivityInstanceCount.get(klass);
- Integer newExpected = expected == null ? 1 : expected + 1;
+ Integer newExpected =
+ expected == null ? InstanceTracker.getInstanceCount(klass) + 1 : expected + 1;
sExpectedActivityInstanceCount.put(klass, newExpected);
}
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index f382fbae9280f..30df0d4b4ad91 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -62,6 +62,7 @@
android:resumeWhilePausing="true"/>
+
mStartedActivityList = new ArrayList<>();
+
+ @After
+ public void tearDown() {
+ mInstrumentation.runOnMainSync(() -> {
+ // Reset strict mode.
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
+ });
+ for (Activity activity : mStartedActivityList) {
+ if (!activity.isDestroyed()) {
+ activity.finish();
+ }
+ }
+ mStartedActivityList.clear();
+ }
+
+ @Test
+ public void testActivityLeak() {
+ final Bundle intentExtras = new Bundle();
+ intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, true);
+ final DetectLeakActivity activity = (DetectLeakActivity) startActivity(
+ DetectLeakActivity.class, 0 /* flags */, intentExtras);
+ mStartedActivityList.add(activity);
+
+ activity.finish();
+
+ assertFalse("Leak found on activity", activity.isLeakedAfterDestroy());
+ }
+
+ @Test
+ public void testActivityLeakForTwoInstances() {
+ final Bundle intentExtras = new Bundle();
+
+ // Launch an activity, then enable strict mode
+ intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, true);
+ final DetectLeakActivity activity1 = (DetectLeakActivity) startActivity(
+ DetectLeakActivity.class, 0 /* flags */, intentExtras);
+ mStartedActivityList.add(activity1);
+
+ // Launch second activity instance.
+ intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, false);
+ final DetectLeakActivity activity2 = (DetectLeakActivity) startActivity(
+ DetectLeakActivity.class,
+ FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_DOCUMENT, intentExtras);
+ mStartedActivityList.add(activity2);
+
+ // Destroy the activity
+ activity1.finish();
+ assertFalse("Leak found on activity 1", activity1.isLeakedAfterDestroy());
+
+ activity2.finish();
+ assertFalse("Leak found on activity 2", activity2.isLeakedAfterDestroy());
+ }
+
+ private Activity startActivity(Class> cls, int flags, Bundle extras) {
+ final Intent intent = new Intent(mContext, cls);
+ intent.addFlags(flags | FLAG_ACTIVITY_NEW_TASK);
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ return mInstrumentation.startActivitySync(intent);
+ }
+
+ public static class DetectLeakActivity extends Activity {
+
+ private static final String TAG = "DetectLeakActivity";
+
+ public static final String ENABLE_STRICT_MODE = "enable_strict_mode";
+
+ private volatile boolean mWasDestroyed;
+ private volatile boolean mIsLeaked;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getIntent().getBooleanExtra(ENABLE_STRICT_MODE, false)) {
+ enableStrictMode();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getWindow().getDecorView().post(() -> {
+ synchronized (this) {
+ mWasDestroyed = true;
+ notifyAll();
+ }
+ });
+ }
+
+ public boolean isLeakedAfterDestroy() {
+ synchronized (this) {
+ while (!mWasDestroyed && !mIsLeaked) {
+ try {
+ wait(5000 /* timeoutMs */);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ return mIsLeaked;
+ }
+
+ private void enableStrictMode() {
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
+ .detectActivityLeaks()
+ .penaltyLog()
+ .penaltyListener(Runnable::run, violation -> {
+ if (!(violation instanceof InstanceCountViolation)) {
+ return;
+ }
+ synchronized (this) {
+ mIsLeaked = true;
+ notifyAll();
+ }
+ Log.w(TAG, violation.toString() + ", " + dumpHprofData());
+ })
+ .build());
+ }
+
+ private String dumpHprofData() {
+ try {
+ final String fileName = getDataDir().getPath() + "/ActivityLeakHeapDump.hprof";
+ Debug.dumpHprofData(fileName);
+ return "memory dump filename: " + fileName;
+ } catch (Throwable e) {
+ Log.e(TAG, "dumpHprofData failed", e);
+ return "failed to save memory dump";
+ }
+ }
+ }
+}