• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
20 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
21 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
22 
23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
24 
25 import static org.junit.Assert.assertFalse;
26 
27 import android.app.Activity;
28 import android.app.Instrumentation;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.os.Bundle;
32 import android.os.Debug;
33 import android.os.StrictMode;
34 import android.os.strictmode.InstanceCountViolation;
35 import android.util.Log;
36 
37 import com.android.server.wm.utils.CommonUtils;
38 
39 import org.junit.After;
40 import org.junit.Test;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * Tests for Activity leaks.
47  *
48  * Build/Install/Run:
49  *     atest WmTests:ActivityLeakTests
50  */
51 public class ActivityLeakTests {
52 
53     private final Instrumentation mInstrumentation = getInstrumentation();
54     private final Context mContext = mInstrumentation.getTargetContext();
55     private final List<Activity> mStartedActivityList = new ArrayList<>();
56 
57     @After
tearDown()58     public void tearDown() {
59         mInstrumentation.runOnMainSync(() -> {
60             // Reset strict mode.
61             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
62         });
63         for (Activity activity : mStartedActivityList) {
64             if (!activity.isDestroyed()) {
65                 activity.finish();
66             }
67         }
68         if (!mStartedActivityList.isEmpty()) {
69             CommonUtils.waitUntilActivityRemoved(
70                     mStartedActivityList.get(mStartedActivityList.size() - 1));
71         }
72         mStartedActivityList.clear();
73     }
74 
75     @Test
testActivityLeak()76     public void testActivityLeak() {
77         final Bundle intentExtras = new Bundle();
78         intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, true);
79         final DetectLeakActivity activity = (DetectLeakActivity) startActivity(
80                 DetectLeakActivity.class, 0 /* flags */, intentExtras);
81         mStartedActivityList.add(activity);
82 
83         activity.finish();
84 
85         assertFalse("Leak found on activity", activity.isLeakedAfterDestroy());
86     }
87 
88     @Test
testActivityLeakForTwoInstances()89     public void testActivityLeakForTwoInstances() {
90         final Bundle intentExtras = new Bundle();
91 
92         // Launch an activity, then enable strict mode
93         intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, true);
94         final DetectLeakActivity activity1 = (DetectLeakActivity) startActivity(
95                 DetectLeakActivity.class, 0 /* flags */, intentExtras);
96         mStartedActivityList.add(activity1);
97 
98         // Launch second activity instance.
99         intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, false);
100         final DetectLeakActivity activity2 = (DetectLeakActivity) startActivity(
101                 DetectLeakActivity.class,
102                 FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_DOCUMENT, intentExtras);
103         mStartedActivityList.add(activity2);
104 
105         // Destroy the activity
106         activity1.finish();
107         assertFalse("Leak found on activity 1", activity1.isLeakedAfterDestroy());
108 
109         activity2.finish();
110         assertFalse("Leak found on activity 2", activity2.isLeakedAfterDestroy());
111     }
112 
startActivity(Class<?> cls, int flags, Bundle extras)113     private Activity startActivity(Class<?> cls, int flags, Bundle extras) {
114         final Intent intent = new Intent(mContext, cls);
115         intent.addFlags(flags | FLAG_ACTIVITY_NEW_TASK);
116         if (extras != null) {
117             intent.putExtras(extras);
118         }
119         return mInstrumentation.startActivitySync(intent);
120     }
121 
122     public static class DetectLeakActivity extends Activity {
123 
124         private static final String TAG = "DetectLeakActivity";
125 
126         public static final String ENABLE_STRICT_MODE = "enable_strict_mode";
127 
128         private volatile boolean mWasDestroyed;
129         private volatile boolean mIsLeaked;
130 
131         @Override
onCreate(Bundle savedInstanceState)132         protected void onCreate(Bundle savedInstanceState) {
133             super.onCreate(savedInstanceState);
134             if (getIntent().getBooleanExtra(ENABLE_STRICT_MODE, false)) {
135                 enableStrictMode();
136             }
137         }
138 
139         @Override
onDestroy()140         protected void onDestroy() {
141             super.onDestroy();
142             getWindow().getDecorView().post(() -> {
143                 synchronized (this) {
144                     mWasDestroyed = true;
145                     notifyAll();
146                 }
147             });
148         }
149 
isLeakedAfterDestroy()150         public boolean isLeakedAfterDestroy() {
151             synchronized (this) {
152                 while (!mWasDestroyed && !mIsLeaked) {
153                     try {
154                         wait(5000 /* timeoutMs */);
155                     } catch (InterruptedException ignored) {
156                     }
157                 }
158             }
159             return mIsLeaked;
160         }
161 
enableStrictMode()162         private void enableStrictMode() {
163             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
164                     .detectActivityLeaks()
165                     .penaltyLog()
166                     .penaltyListener(Runnable::run, violation -> {
167                         if (!(violation instanceof InstanceCountViolation)) {
168                             return;
169                         }
170                         synchronized (this) {
171                             mIsLeaked = true;
172                             notifyAll();
173                         }
174                         Log.w(TAG, violation.toString() + ", " + dumpHprofData());
175                     })
176                     .build());
177         }
178 
dumpHprofData()179         private String dumpHprofData() {
180             try {
181                 final String fileName = getDataDir().getPath() + "/ActivityLeakHeapDump.hprof";
182                 Debug.dumpHprofData(fileName);
183                 return "memory dump filename: " + fileName;
184             } catch (Throwable e) {
185                 Log.e(TAG, "dumpHprofData failed", e);
186                 return "failed to save memory dump";
187             }
188         }
189     }
190 }
191