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"; + } + } + } +}