Repository: square/leakcanary
Issue: Crash opening "Leaks" activity on 1.4-beta1

My test device is running Android M, and I have not had a leak to get the notification to grant storage permissions. When I run the Leaks activity, it crashes with the dump below. The activity is able to open after granting storage permission.

```
D  Shutting down VM
E  FATAL EXCEPTION: main
E  Process: com.meiste.tempalarm, PID: 14231
E  java.lang.RuntimeException: Unable to resume activity {com.meiste.tempalarm/com.squareup.leakcanary.internal.DisplayLeakActivity}: java.lang.Unsupport
                        edOperationException: Could not create leak directory /storage/emulated/0/Download/leakcanary-com.meiste.tempalarm
E      at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3121)
E      at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3152)
E      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2495)
E      at android.app.ActivityThread.-wrap11(ActivityThread.java)
E      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1354)
E      at android.os.Handler.dispatchMessage(Handler.java:102)
E      at android.os.Looper.loop(Looper.java:148)
E      at android.app.ActivityThread.main(ActivityThread.java:5443)
E      at java.lang.reflect.Method.invoke(Native Method)
E      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
E      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
E  Caused by: java.lang.UnsupportedOperationException: Could not create leak directory /storage/emulated/0/Download/leakcanary-com.meiste.tempalarm
E      at com.squareup.leakcanary.DefaultLeakDirectoryProvider.leakDirectory(DefaultLeakDirectoryProvider.java:45)
E      at com.squareup.leakcanary.internal.DisplayLeakActivity.getLeakDirectory(DisplayLeakActivity.java:94)
E      at com.squareup.leakcanary.internal.DisplayLeakActivity$LoadLeaks.<init>(DisplayLeakActivity.java:431)
E      at com.squareup.leakcanary.internal.DisplayLeakActivity$LoadLeaks.load(DisplayLeakActivity.java:413)
E      at com.squareup.leakcanary.internal.DisplayLeakActivity.onResume(DisplayLeakActivity.java:142)
E      at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1281)
E      at android.app.Activity.performResume(Activity.java:6320)
E      at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3110)
E      ... 10 more
```


Changed files:
leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java
leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java
leakcanary-android/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java
leakcanary-android/src/main/java/com/squareup/leakcanary/LeakDirectoryProvider.java

diff --git a/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java b/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java
index 86a232f0a..c46435f35 100644
--- a/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java
+++ b/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java
@@ -47,7 +47,7 @@ public final class AndroidHeapDumper implements HeapDumper {
   @Override public File dumpHeap() {
     if (!leakDirectoryProvider.isLeakStorageWritable()) {
       CanaryLog.d("Could not write to leak storage to dump heap.");
-      leakDirectoryProvider.requestWritePermission();
+      leakDirectoryProvider.requestWritePermissionNotification();
       return NO_DUMP;
     }
     File heapDumpFile = getHeapDumpFile();
diff --git a/leakcanary-android/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java b/leakcanary-android/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java
index 988906e31..812b6e647 100644
--- a/leakcanary-android/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java
+++ b/leakcanary-android/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java
@@ -16,6 +16,7 @@
 package com.squareup.leakcanary;
 
 import android.annotation.TargetApi;
+import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.os.Environment;
@@ -48,7 +49,7 @@ public final class DefaultLeakDirectoryProvider implements LeakDirectoryProvider
     return directory;
   }
 
-  @Override public void requestWritePermission() {
+  @Override public void requestWritePermissionNotification() {
     if (hasStoragePermission()) {
       return;
     }
@@ -60,6 +61,16 @@ public final class DefaultLeakDirectoryProvider implements LeakDirectoryProvider
     showNotification(context, contentTitle, contentText, pendingIntent);
   }
 
+  @TargetApi(M) @Override public void requestPermission(Activity activity) {
+    if (hasStoragePermission()) {
+      return;
+    }
+    String[] permissions = {
+        WRITE_EXTERNAL_STORAGE
+    };
+    activity.requestPermissions(permissions, 42);
+  }
+
   @Override public boolean isLeakStorageWritable() {
     if (!hasStoragePermission()) {
       return false;
diff --git a/leakcanary-android/src/main/java/com/squareup/leakcanary/LeakDirectoryProvider.java b/leakcanary-android/src/main/java/com/squareup/leakcanary/LeakDirectoryProvider.java
index 6c8cf0568..1e7ebf87b 100644
--- a/leakcanary-android/src/main/java/com/squareup/leakcanary/LeakDirectoryProvider.java
+++ b/leakcanary-android/src/main/java/com/squareup/leakcanary/LeakDirectoryProvider.java
@@ -15,6 +15,7 @@
  */
 package com.squareup.leakcanary;
 
+import android.app.Activity;
 import java.io.File;
 
 /**
@@ -27,7 +28,9 @@ public interface LeakDirectoryProvider {
   /** Returns a path to an existing directory were leaks can be stored. */
   File leakDirectory();
 
-  void requestWritePermission();
+  void requestWritePermissionNotification();
+
+  void requestPermission(Activity activity);
 
   /** True if we can currently write to the leak directory. */
   boolean isLeakStorageWritable();
diff --git a/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java b/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java
index 2be0290d3..e746b04c5 100644
--- a/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java
+++ b/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java
@@ -88,13 +88,12 @@ public final class DisplayLeakActivity extends Activity {
     DisplayLeakActivity.leakDirectoryProvider = leakDirectoryProvider;
   }
 
-  static File getLeakDirectory(Context context) {
+  private static LeakDirectoryProvider leakDirectoryProvider(Context context) {
     LeakDirectoryProvider leakDirectoryProvider = DisplayLeakActivity.leakDirectoryProvider;
-    if (leakDirectoryProvider != null) {
-      return leakDirectoryProvider.leakDirectory();
-    } else {
-      return new DefaultLeakDirectoryProvider(context).leakDirectory();
+    if (leakDirectoryProvider == null) {
+      leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
     }
+    return leakDirectoryProvider;
   }
 
   // null until it's been first loaded.
@@ -141,7 +140,13 @@ public final class DisplayLeakActivity extends Activity {
 
   @Override protected void onResume() {
     super.onResume();
-    LoadLeaks.load(this);
+    LeakDirectoryProvider leakDirectoryProvider = leakDirectoryProvider(this);
+    if (leakDirectoryProvider.isLeakStorageWritable()) {
+      File leakDirectory = leakDirectoryProvider.leakDirectory();
+      LoadLeaks.load(this, leakDirectory);
+    } else {
+      leakDirectoryProvider.requestPermission(this);
+    }
   }
 
   @Override public void setTheme(int resid) {
@@ -237,7 +242,7 @@ public final class DisplayLeakActivity extends Activity {
   }
 
   void deleteAllLeaks() {
-    File leakDirectory = getLeakDirectory(DisplayLeakActivity.this);
+    File leakDirectory = leakDirectoryProvider(DisplayLeakActivity.this).leakDirectory();
     File[] files = leakDirectory.listFiles();
     if (files != null) {
       for (File file : files) {
@@ -428,8 +433,8 @@ public final class DisplayLeakActivity extends Activity {
 
     static final Executor backgroundExecutor = newSingleThreadExecutor("LoadLeaks");
 
-    static void load(DisplayLeakActivity activity) {
-      LoadLeaks loadLeaks = new LoadLeaks(activity);
+    static void load(DisplayLeakActivity activity, File leakDirectory) {
+      LoadLeaks loadLeaks = new LoadLeaks(activity, leakDirectory);
       inFlight.add(loadLeaks);
       backgroundExecutor.execute(loadLeaks);
     }
@@ -445,9 +450,9 @@ public final class DisplayLeakActivity extends Activity {
     private final File leakDirectory;
     private final Handler mainHandler;
 
-    LoadLeaks(DisplayLeakActivity activity) {
+    LoadLeaks(DisplayLeakActivity activity, File leakDirectory) {
       this.activityOrNull = activity;
-      leakDirectory = getLeakDirectory(activity);
+      this.leakDirectory = leakDirectory;
       mainHandler = new Handler(Looper.getMainLooper());
     }