diff --git a/library/leakcanary-android/src/main/AndroidManifest.xml b/library/leakcanary-android/src/main/AndroidManifest.xml
index f0cdef25aa..05d4ad8127 100644
--- a/library/leakcanary-android/src/main/AndroidManifest.xml
+++ b/library/leakcanary-android/src/main/AndroidManifest.xml
@@ -19,6 +19,9 @@
package="com.squareup.leakcanary"
>
+
+
+
serviceClass) {
- PackageManager packageManager = context.getPackageManager();
- PackageInfo packageInfo;
- try {
- packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
- } catch (Exception e) {
- Log.e("AndroidUtils", "Could not get package info for " + context.getPackageName(), e);
- return false;
- }
- String mainProcess = packageInfo.applicationInfo.processName;
-
- ComponentName component = new ComponentName(context, serviceClass);
- ServiceInfo serviceInfo;
- try {
- serviceInfo = packageManager.getServiceInfo(component, 0);
- } catch (PackageManager.NameNotFoundException ignored) {
- // Service is disabled.
- return false;
- }
-
- if (serviceInfo.processName.equals(mainProcess)) {
- Log.e("AndroidUtils",
- "Did not expect service " + serviceClass + " to run in main process " + mainProcess);
- // Technically we are in the service process, but we're not in the service dedicated process.
- return false;
- }
-
- int myPid = android.os.Process.myPid();
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- ActivityManager.RunningAppProcessInfo myProcess = null;
- for (ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses()) {
- if (process.pid == myPid) {
- myProcess = process;
- break;
- }
- }
- if (myProcess == null) {
- Log.e("AndroidUtils", "Could not find running process for " + myPid);
- return false;
- }
-
- return myProcess.processName.equals(serviceInfo.processName);
- }
-
- static void setEnabled(Context context, Class> componentClass, boolean enabled) {
- ComponentName component = new ComponentName(context, componentClass);
- PackageManager packageManager = context.getPackageManager();
- int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
- // Blocks on IPC.
- packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
- }
-
- /** Extracts the class simple name out of a string containing a fully qualified class name. */
- static String classSimpleName(String className) {
- int separator = className.lastIndexOf('.');
- if (separator == -1) {
- return className;
- } else {
- return className.substring(separator + 1);
- }
- }
-
private LeakCanary() {
throw new AssertionError();
}
diff --git a/library/leakcanary-android/src/main/java/com/squareup/leakcanary/ServiceHeapDumpListener.java b/library/leakcanary-android/src/main/java/com/squareup/leakcanary/ServiceHeapDumpListener.java
index b297f152ad..2ccb14227a 100644
--- a/library/leakcanary-android/src/main/java/com/squareup/leakcanary/ServiceHeapDumpListener.java
+++ b/library/leakcanary-android/src/main/java/com/squareup/leakcanary/ServiceHeapDumpListener.java
@@ -19,6 +19,7 @@
import com.squareup.leakcanary.internal.HeapAnalyzerService;
import static com.squareup.leakcanary.Preconditions.checkNotNull;
+import static com.squareup.leakcanary.internal.LeakCanaryInternals.setEnabled;
public final class ServiceHeapDumpListener implements HeapDump.Listener {
@@ -27,8 +28,8 @@ public final class ServiceHeapDumpListener implements HeapDump.Listener {
public ServiceHeapDumpListener(Context context,
Class extends AbstractAnalysisResultService> listenerServiceClass) {
- LeakCanary.setEnabled(context, listenerServiceClass, true);
- LeakCanary.setEnabled(context, HeapAnalyzerService.class, true);
+ setEnabled(context, listenerServiceClass, true);
+ setEnabled(context, HeapAnalyzerService.class, true);
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
diff --git a/library/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java b/library/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java
index 8268dff010..240c2ac4c9 100644
--- a/library/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java
+++ b/library/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java
@@ -57,6 +57,8 @@
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.DateUtils.FORMAT_SHOW_TIME;
import static com.squareup.leakcanary.LeakCanary.leakInfo;
+import static com.squareup.leakcanary.internal.LeakCanaryInternals.detectedLeakDirectory;
+import static com.squareup.leakcanary.internal.LeakCanaryInternals.leakResultFile;
@SuppressWarnings("ConstantConditions") @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public final class DisplayLeakActivity extends Activity {
@@ -64,14 +66,6 @@ public final class DisplayLeakActivity extends Activity {
private static final String TAG = "DisplayLeakActivity";
private static final String SHOW_LEAK_EXTRA = "show_latest";
- public static File leakDirectory(Context context) {
- return new File(context.getFilesDir(), "detected_leaks");
- }
-
- public static File leakResultFile(File heapdumpFile) {
- return new File(heapdumpFile.getParentFile(), heapdumpFile.getName() + ".result");
- }
-
public static PendingIntent createPendingIntent(Context context, String referenceKey) {
Intent intent = new Intent(context, DisplayLeakActivity.class);
intent.putExtra(SHOW_LEAK_EXTRA, referenceKey);
@@ -257,9 +251,9 @@ public void onItemClick(AdapterView> parent, View view, int position, long id)
actionButton.setText("Remove all leaks");
actionButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
- File directory = leakDirectory(DisplayLeakActivity.this);
- if (directory.exists()) {
- for (File file : directory.listFiles()) {
+ File[] files = detectedLeakDirectory().listFiles();
+ if (files != null) {
+ for (File file : files) {
file.delete();
}
}
@@ -359,7 +353,7 @@ static void forgetActivity() {
LoadLeaks(DisplayLeakActivity activity) {
this.activityOrNull = activity;
- leakDirectory = leakDirectory(activity);
+ leakDirectory = detectedLeakDirectory();
mainHandler = new Handler(Looper.getMainLooper());
}
@@ -371,8 +365,8 @@ static void forgetActivity() {
}
});
if (files != null) {
- for (File file : files) {
- File resultFile = leakResultFile(file);
+ for (File heapDumpFile : files) {
+ File resultFile = leakResultFile(heapDumpFile);
FileInputStream fis = null;
try {
fis = new FileInputStream(resultFile);
@@ -383,9 +377,10 @@ static void forgetActivity() {
} catch (IOException | ClassNotFoundException e) {
// Likely a change in the serializable result class.
// Let's remove the files, we can't read them anymore.
- file.delete();
+ heapDumpFile.delete();
resultFile.delete();
- Log.e(TAG, "Could not read result file, deleted result and heap dump:" + file, e);
+ Log.e(TAG, "Could not read result file, deleted result and heap dump:" + heapDumpFile,
+ e);
} finally {
if (fis != null) {
try {
diff --git a/library/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/LeakCanaryInternals.java b/library/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/LeakCanaryInternals.java
new file mode 100644
index 0000000000..efeaa3ab81
--- /dev/null
+++ b/library/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/LeakCanaryInternals.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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.squareup.leakcanary.internal;
+
+import android.app.ActivityManager;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Environment;
+import android.util.Log;
+import java.io.File;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DONT_KILL_APP;
+import static android.content.pm.PackageManager.GET_SERVICES;
+import static android.os.Environment.DIRECTORY_DOWNLOADS;
+
+public final class LeakCanaryInternals {
+
+ public static File storageDirectory() {
+ File downloadsDirectory = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS);
+ File leakCanaryDirectory = new File(downloadsDirectory, "leakcanary");
+ leakCanaryDirectory.mkdirs();
+ return leakCanaryDirectory;
+ }
+
+ public static File detectedLeakDirectory() {
+ File directory = new File(storageDirectory(), "detected_leaks");
+ directory.mkdirs();
+ return directory;
+ }
+
+ public static File leakResultFile(File heapdumpFile) {
+ return new File(heapdumpFile.getParentFile(), heapdumpFile.getName() + ".result");
+ }
+
+ public static boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state);
+ }
+
+ public static File findNextAvailableHprofFile(int maxFiles) {
+ File directory = detectedLeakDirectory();
+ for (int i = 0; i < maxFiles; i++) {
+ String heapDumpName = "heap_dump_" + i + ".hprof";
+ File file = new File(directory, heapDumpName);
+ if (!file.exists()) {
+ return file;
+ }
+ }
+ return null;
+ }
+
+ /** Extracts the class simple name out of a string containing a fully qualified class name. */
+ public static String classSimpleName(String className) {
+ int separator = className.lastIndexOf('.');
+ if (separator == -1) {
+ return className;
+ } else {
+ return className.substring(separator + 1);
+ }
+ }
+
+ public static void setEnabled(Context context, Class> componentClass, boolean enabled) {
+ ComponentName component = new ComponentName(context, componentClass);
+ PackageManager packageManager = context.getPackageManager();
+ int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
+ // Blocks on IPC.
+ packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
+ }
+
+ public static boolean isInServiceProcess(Context context, Class extends Service> serviceClass) {
+ PackageManager packageManager = context.getPackageManager();
+ PackageInfo packageInfo;
+ try {
+ packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
+ } catch (Exception e) {
+ Log.e("AndroidUtils", "Could not get package info for " + context.getPackageName(), e);
+ return false;
+ }
+ String mainProcess = packageInfo.applicationInfo.processName;
+
+ ComponentName component = new ComponentName(context, serviceClass);
+ ServiceInfo serviceInfo;
+ try {
+ serviceInfo = packageManager.getServiceInfo(component, 0);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ // Service is disabled.
+ return false;
+ }
+
+ if (serviceInfo.processName.equals(mainProcess)) {
+ Log.e("AndroidUtils",
+ "Did not expect service " + serviceClass + " to run in main process " + mainProcess);
+ // Technically we are in the service process, but we're not in the service dedicated process.
+ return false;
+ }
+
+ int myPid = android.os.Process.myPid();
+ ActivityManager activityManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager.RunningAppProcessInfo myProcess = null;
+ for (ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses()) {
+ if (process.pid == myPid) {
+ myProcess = process;
+ break;
+ }
+ }
+ if (myProcess == null) {
+ Log.e("AndroidUtils", "Could not find running process for " + myPid);
+ return false;
+ }
+
+ return myProcess.processName.equals(serviceInfo.processName);
+ }
+
+ private LeakCanaryInternals() {
+ throw new AssertionError();
+ }
+}