Merge "SysUiLeaks: Add GarbageMonitor"
This commit is contained in:
@@ -559,6 +559,16 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.android.systemui.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/fileprovider" />
|
||||
</provider>
|
||||
|
||||
<receiver
|
||||
android:name=".statusbar.KeyboardShortcutsReceiver">
|
||||
<intent-filter>
|
||||
|
||||
20
packages/SystemUI/res/xml/fileprovider.xml
Normal file
20
packages/SystemUI/res/xml/fileprovider.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 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
|
||||
-->
|
||||
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="leak" path="leak/"/>
|
||||
</paths>
|
||||
@@ -67,7 +67,9 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
|
||||
import com.android.systemui.statusbar.policy.ZenModeController;
|
||||
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
import com.android.systemui.util.leak.GarbageMonitor;
|
||||
import com.android.systemui.util.leak.LeakDetector;
|
||||
import com.android.systemui.util.leak.LeakReporter;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -104,6 +106,12 @@ public class Dependency extends SystemUI {
|
||||
*/
|
||||
public static final DependencyKey<Handler> MAIN_HANDLER = new DependencyKey<>("main_handler");
|
||||
|
||||
/**
|
||||
* An email address to send memory leak reports to by default.
|
||||
*/
|
||||
public static final DependencyKey<String> LEAK_REPORT_EMAIL
|
||||
= new DependencyKey<>("leak_report_email");
|
||||
|
||||
private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
|
||||
private final ArrayMap<Object, DependencyProvider> mProviders = new ArrayMap<>();
|
||||
|
||||
@@ -192,6 +200,18 @@ public class Dependency extends SystemUI {
|
||||
|
||||
mProviders.put(LeakDetector.class, LeakDetector::create);
|
||||
|
||||
mProviders.put(LEAK_REPORT_EMAIL, () -> null);
|
||||
|
||||
mProviders.put(LeakReporter.class, () -> new LeakReporter(
|
||||
mContext,
|
||||
getDependency(LeakDetector.class),
|
||||
getDependency(LEAK_REPORT_EMAIL)));
|
||||
|
||||
mProviders.put(GarbageMonitor.class, () -> new GarbageMonitor(
|
||||
getDependency(BG_LOOPER),
|
||||
getDependency(LeakDetector.class),
|
||||
getDependency(LeakReporter.class)));
|
||||
|
||||
mProviders.put(TunerService.class, () ->
|
||||
new TunerService(mContext));
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.phone.StatusBarWindowManager;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
import com.android.systemui.usb.StorageNotification;
|
||||
import com.android.systemui.util.NotificationChannels;
|
||||
import com.android.systemui.util.leak.GarbageMonitor;
|
||||
import com.android.systemui.volume.VolumeUI;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -81,6 +82,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
|
||||
PipUI.class,
|
||||
ShortcutKeyDispatcher.class,
|
||||
VendorServices.class,
|
||||
GarbageMonitor.Service.class,
|
||||
LatencyTester.class,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 com.android.systemui.util.leak;
|
||||
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.SystemUI;
|
||||
|
||||
public class GarbageMonitor {
|
||||
|
||||
private static final String TAG = "GarbageMonitor";
|
||||
|
||||
private static final long GARBAGE_INSPECTION_INTERVAL = 5 * 60 * 1000; // 5min
|
||||
private static final int GARBAGE_ALLOWANCE = 5;
|
||||
|
||||
private final Handler mHandler;
|
||||
private final TrackedGarbage mTrackedGarbage;
|
||||
private final LeakReporter mLeakReporter;
|
||||
|
||||
public GarbageMonitor(Looper bgLooper, LeakDetector leakDetector,
|
||||
LeakReporter leakReporter) {
|
||||
mHandler = bgLooper != null ? new Handler(bgLooper): null;
|
||||
mTrackedGarbage = leakDetector.getTrackedGarbage();
|
||||
mLeakReporter = leakReporter;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (mTrackedGarbage == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleInspectGarbage(this::inspectGarbage);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void scheduleInspectGarbage(Runnable runnable) {
|
||||
mHandler.postDelayed(runnable, GARBAGE_INSPECTION_INTERVAL);
|
||||
}
|
||||
|
||||
private void inspectGarbage() {
|
||||
if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) {
|
||||
Runtime.getRuntime().gc();
|
||||
|
||||
// Allow some time to for ReferenceQueue to catch up.
|
||||
scheduleReinspectGarbage(this::reinspectGarbageAfterGc);
|
||||
}
|
||||
scheduleInspectGarbage(this::inspectGarbage);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void scheduleReinspectGarbage(Runnable runnable) {
|
||||
mHandler.postDelayed(runnable, (long) 100);
|
||||
}
|
||||
|
||||
private void reinspectGarbageAfterGc() {
|
||||
int count = mTrackedGarbage.countOldGarbage();
|
||||
if (count > GARBAGE_ALLOWANCE) {
|
||||
mLeakReporter.dumpLeak(count);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Service extends SystemUI {
|
||||
|
||||
private GarbageMonitor mGarbageMonitor;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!Build.IS_DEBUGGABLE) {
|
||||
return;
|
||||
}
|
||||
mGarbageMonitor = Dependency.get(GarbageMonitor.class);
|
||||
mGarbageMonitor.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,10 @@ public class LeakDetector implements Dumpable {
|
||||
}
|
||||
}
|
||||
|
||||
TrackedGarbage getTrackedGarbage() {
|
||||
return mTrackedGarbage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor df, PrintWriter w, String[] args) {
|
||||
IndentingPrintWriter pw = new IndentingPrintWriter(w, " ");
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 com.android.systemui.util.leak;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Debug;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Dumps data to debug leaks and posts a notification to share the data.
|
||||
*/
|
||||
public class LeakReporter {
|
||||
|
||||
static final String TAG = "LeakReporter";
|
||||
|
||||
public static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
|
||||
|
||||
static final String LEAK_DIR = "leak";
|
||||
static final String LEAK_HPROF = "leak.hprof";
|
||||
static final String LEAK_DUMP = "leak.dump";
|
||||
|
||||
private final Context mContext;
|
||||
private final LeakDetector mLeakDetector;
|
||||
private final String mLeakReportEmail;
|
||||
|
||||
public LeakReporter(Context context, LeakDetector leakDetector, String leakReportEmail) {
|
||||
mContext = context;
|
||||
mLeakDetector = leakDetector;
|
||||
mLeakReportEmail = leakReportEmail;
|
||||
}
|
||||
|
||||
public void dumpLeak(int garbageCount) {
|
||||
try {
|
||||
File leakDir = new File(mContext.getCacheDir(), LEAK_DIR);
|
||||
leakDir.mkdir();
|
||||
|
||||
File hprofFile = new File(leakDir, LEAK_HPROF);
|
||||
Debug.dumpHprofData(hprofFile.getAbsolutePath());
|
||||
|
||||
File dumpFile = new File(leakDir, LEAK_DUMP);
|
||||
try (FileOutputStream fos = new FileOutputStream(dumpFile)) {
|
||||
PrintWriter w = new PrintWriter(fos);
|
||||
w.print("Build: "); w.println(SystemProperties.get("ro.build.description"));
|
||||
w.println();
|
||||
w.flush();
|
||||
mLeakDetector.dump(fos.getFD(), w, new String[0]);
|
||||
w.close();
|
||||
}
|
||||
|
||||
NotificationManager notiMan = mContext.getSystemService(NotificationManager.class);
|
||||
|
||||
NotificationChannel channel = new NotificationChannel("leak", "Leak Alerts",
|
||||
NotificationManager.IMPORTANCE_HIGH);
|
||||
channel.enableVibration(true);
|
||||
|
||||
notiMan.createNotificationChannel(channel);
|
||||
Notification.Builder builder = new Notification.Builder(mContext, channel.getId())
|
||||
.setAutoCancel(true)
|
||||
.setShowWhen(true)
|
||||
.setContentTitle("Memory Leak Detected")
|
||||
.setContentText(String.format(
|
||||
"SystemUI has detected %d leaked objects. Tap to send", garbageCount))
|
||||
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
|
||||
.setContentIntent(PendingIntent.getActivityAsUser(mContext, 0,
|
||||
getIntent(hprofFile, dumpFile),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT, null, UserHandle.CURRENT));
|
||||
notiMan.notify(TAG, 0, builder.build());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Couldn't dump heap for leak", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Intent getIntent(File hprofFile, File dumpFile) {
|
||||
Uri dumpUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, dumpFile);
|
||||
Uri hprofUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, hprofFile);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
String mimeType = "application/vnd.android.leakreport";
|
||||
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setType(mimeType);
|
||||
|
||||
final String subject = "SystemUI leak report";
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
|
||||
// EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
|
||||
// So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
|
||||
// create the ClipData object with the attachments URIs.
|
||||
final StringBuilder messageBody = new StringBuilder("Build info: ")
|
||||
.append(SystemProperties.get("ro.build.description"));
|
||||
intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
|
||||
final ClipData clipData = new ClipData(null, new String[] { mimeType },
|
||||
new ClipData.Item(null, null, null, dumpUri));
|
||||
final ArrayList<Uri> attachments = Lists.newArrayList(dumpUri);
|
||||
|
||||
clipData.addItem(new ClipData.Item(null, null, null, hprofUri));
|
||||
attachments.add(hprofUri);
|
||||
|
||||
intent.setClipData(clipData);
|
||||
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
|
||||
|
||||
String leakReportEmail = mLeakReportEmail;
|
||||
if (leakReportEmail != null) {
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, new String[] { leakReportEmail });
|
||||
}
|
||||
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,20 @@ public class TrackedGarbage {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized int countOldGarbage() {
|
||||
cleanUp();
|
||||
|
||||
long now = SystemClock.uptimeMillis();
|
||||
|
||||
int result = 0;
|
||||
for (LeakReference ref : mGarbage) {
|
||||
if (isOld(ref.createdUptimeMillis, now)) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isOld(long createdUptimeMillis, long now) {
|
||||
return createdUptimeMillis + GARBAGE_COLLECTION_DEADLINE_MILLIS < now;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 com.android.systemui.util.leak;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class GarbageMonitorTest {
|
||||
|
||||
private LeakReporter mLeakReporter;
|
||||
private TrackedGarbage mTrackedGarbage;
|
||||
private TestableGarbageMonitor mGarbageMonitor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mTrackedGarbage = mock(TrackedGarbage.class);
|
||||
mLeakReporter = mock(LeakReporter.class);
|
||||
mGarbageMonitor = new TestableGarbageMonitor(
|
||||
new LeakDetector(null, mTrackedGarbage, null),
|
||||
mLeakReporter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbacks_getScheduled() {
|
||||
mGarbageMonitor.start();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoGarbage_doesntDump() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
|
||||
|
||||
mGarbageMonitor.start();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
|
||||
verify(mLeakReporter, never()).dumpLeak(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testALittleGarbage_doesntDump() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(4);
|
||||
|
||||
mGarbageMonitor.start();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
|
||||
verify(mLeakReporter, never()).dumpLeak(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransientGarbage_doesntDump() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(100);
|
||||
|
||||
mGarbageMonitor.start();
|
||||
mGarbageMonitor.runInspectCallback();
|
||||
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
|
||||
|
||||
mGarbageMonitor.runReinspectCallback();
|
||||
|
||||
verify(mLeakReporter, never()).dumpLeak(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLotsOfPersistentGarbage_dumps() {
|
||||
when(mTrackedGarbage.countOldGarbage()).thenReturn(100);
|
||||
|
||||
mGarbageMonitor.start();
|
||||
mGarbageMonitor.runCallbacksOnce();
|
||||
|
||||
verify(mLeakReporter).dumpLeak(anyInt());
|
||||
}
|
||||
|
||||
private static class TestableGarbageMonitor extends GarbageMonitor {
|
||||
Runnable mInspectCallback;
|
||||
Runnable mReinspectCallback;
|
||||
|
||||
public TestableGarbageMonitor(LeakDetector leakDetector,
|
||||
LeakReporter leakReporter) {
|
||||
super(null /* bgLooper */, leakDetector, leakReporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
void scheduleInspectGarbage(Runnable runnable) {
|
||||
assertNull("must not have more than one pending inspect callback", mInspectCallback);
|
||||
mInspectCallback = runnable;
|
||||
}
|
||||
|
||||
void runInspectCallback() {
|
||||
assertNotNull("expected an inspect callback to be scheduled", mInspectCallback);
|
||||
Runnable callback = mInspectCallback;
|
||||
mInspectCallback = null;
|
||||
callback.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
void scheduleReinspectGarbage(Runnable runnable) {
|
||||
assertNull("must not have more than one reinspect callback", mReinspectCallback);
|
||||
mReinspectCallback = runnable;
|
||||
}
|
||||
|
||||
void runReinspectCallback() {
|
||||
assertNotNull("expected a reinspect callback to be scheduled", mInspectCallback);
|
||||
maybeRunReinspectCallback();
|
||||
}
|
||||
|
||||
void maybeRunReinspectCallback() {
|
||||
Runnable callback = mReinspectCallback;
|
||||
mReinspectCallback = null;
|
||||
if (callback != null) {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
|
||||
void runCallbacksOnce() {
|
||||
runInspectCallback();
|
||||
maybeRunReinspectCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 com.android.systemui.util.leak;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LeakReporterTest extends SysuiTestCase {
|
||||
|
||||
private LeakDetector mLeakDetector;
|
||||
private LeakReporter mLeakReporter;
|
||||
private File mLeakDir;
|
||||
private File mLeakDump;
|
||||
private File mLeakHprof;
|
||||
private NotificationManager mNotificationManager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mLeakDir = new File(mContext.getCacheDir(), LeakReporter.LEAK_DIR);
|
||||
mLeakDump = new File(mLeakDir, LeakReporter.LEAK_DUMP);
|
||||
mLeakHprof = new File(mLeakDir, LeakReporter.LEAK_HPROF);
|
||||
|
||||
mNotificationManager = mock(NotificationManager.class);
|
||||
mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
|
||||
|
||||
mLeakDetector = mock(LeakDetector.class);
|
||||
doAnswer(invocation -> {
|
||||
invocation.<PrintWriter>getArgument(1).println("test");
|
||||
return null;
|
||||
}).when(mLeakDetector).dump(any(), any(), any());
|
||||
|
||||
mLeakReporter = new LeakReporter(mContext, mLeakDetector, "test@example.com");
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
mLeakDump.delete();
|
||||
mLeakHprof.delete();
|
||||
mLeakDir.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDump_postsNotification() {
|
||||
mLeakReporter.dumpLeak(5);
|
||||
verify(mNotificationManager).notify(any(), anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDump_Repeated() {
|
||||
mLeakReporter.dumpLeak(1);
|
||||
mLeakReporter.dumpLeak(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDump_ProducesNonZeroFiles() {
|
||||
mLeakReporter.dumpLeak(5);
|
||||
|
||||
assertTrue(mLeakDump.exists());
|
||||
assertTrue(mLeakDump.length() > 0);
|
||||
|
||||
assertTrue(mLeakHprof.exists());
|
||||
assertTrue(mLeakHprof.length() > 0);
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ import android.util.ArrayMap;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import com.android.systemui.SysUiServiceProvider;
|
||||
import com.android.systemui.utils.leaks.Tracker;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.utils.leaks.Tracker;
|
||||
|
||||
public class TestableContext extends ContextWrapper implements SysUiServiceProvider {
|
||||
|
||||
@@ -81,6 +81,10 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi
|
||||
return super.getResources();
|
||||
}
|
||||
|
||||
public <T> void addMockSystemService(Class<T> service, T mock) {
|
||||
addMockSystemService(getSystemServiceName(service), mock);
|
||||
}
|
||||
|
||||
public void addMockSystemService(String name, Object service) {
|
||||
mMockSystemServices = lazyInit(mMockSystemServices);
|
||||
mMockSystemServices.put(name, service);
|
||||
|
||||
Reference in New Issue
Block a user