CB-10319 android: Adding reflective helper methods for permission requests
diff --git a/plugin.xml b/plugin.xml
index 7f5dfcc..3b2763f 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -28,9 +28,6 @@
     <keywords>cordova,file</keywords>
     <repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git</repo>
     <issue>https://issues.apache.org/jira/browse/CB/component/12320651</issue>
-    <engines>
-        <engine name="cordova-android" version=">=5.0.0-dev" /><!-- Uses Marshmallow Permissions -->
-    </engines>
 
     <js-module src="www/DirectoryEntry.js" name="DirectoryEntry">
         <clobbers target="window.DirectoryEntry" />
@@ -104,18 +101,18 @@
     <js-module src="www/resolveLocalFileSystemURI.js" name="resolveLocalFileSystemURI">
         <merges target="window" />
     </js-module>
-  
+
     <info>
 The Android Persistent storage location now defaults to "Internal". Please check this plugins README to see if you application needs any changes in its config.xml.
-      
+
 If this is a new application no changes are required.
-      
+
 If this is an update to an existing application that did not specify an "AndroidPersistentFileLocation" you may need to add:
-      
+
       "&lt;preference name="AndroidPersistentFileLocation" value="Compatibility" /&gt;"
-      
+
 to config.xml in order for the application to find previously stored files.
-      
+
     </info>
 
     <!-- android -->
@@ -143,6 +140,7 @@
         <source-file src="src/android/LocalFilesystem.java" target-dir="src/org/apache/cordova/file" />
         <source-file src="src/android/ContentFilesystem.java" target-dir="src/org/apache/cordova/file" />
         <source-file src="src/android/AssetFilesystem.java" target-dir="src/org/apache/cordova/file" />
+        <source-file src="src/android/PermissionHelper.java" target-dir="src/org/apache/cordova/file" />
 
         <!-- android specific file apis -->
         <js-module src="www/android/FileSystem.js" name="androidFileSystem">
@@ -183,7 +181,7 @@
         <source-file src="src/android/ContentFilesystem.java" target-dir="src/org/apache/cordova/file" />
         <source-file src="src/android/AssetFilesystem.java" target-dir="src/org/apache/cordova/file" />
 
-        
+
         <!-- android specific file apis -->
         <js-module src="www/android/FileSystem.js" name="androidFileSystem">
             <merges target="window.FileSystem" />
@@ -410,7 +408,7 @@
         <js-module src="src/browser/FileProxy.js" name="browserFileProxy">
             <runs />
         </js-module>
-        
+
         <js-module src="www/fileSystemPaths.js" name="fileSystemPaths">
             <merges target="cordova" />
             <runs />
diff --git a/src/android/FileUtils.java b/src/android/FileUtils.java
index ba44c51..e9c937f 100644
--- a/src/android/FileUtils.java
+++ b/src/android/FileUtils.java
@@ -534,26 +534,20 @@
     }
 
     private void getReadPermission() {
-        cordova.requestPermission(this, READ_PERM, Manifest.permission.READ_EXTERNAL_STORAGE);
+        PermissionHelper.requestPermission(this, READ_PERM, Manifest.permission.READ_EXTERNAL_STORAGE);
     }
 
     private void getWritePermission() {
-        cordova.requestPermission(this, WRITE_PERM, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        PermissionHelper.requestPermission(this, WRITE_PERM, Manifest.permission.WRITE_EXTERNAL_STORAGE);
     }
 
 
     private boolean hasReadPermission() {
-        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
-            return PackageManager.PERMISSION_GRANTED == cordova.getActivity().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
-        else
-            return true;
+        return PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
     }
 
     private boolean hasWritePermission() {
-        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
-            return PackageManager.PERMISSION_GRANTED == cordova.getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-        else
-            return true;
+        return PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
     }
 
 
diff --git a/src/android/PermissionHelper.java b/src/android/PermissionHelper.java
new file mode 100644
index 0000000..778e5ce
--- /dev/null
+++ b/src/android/PermissionHelper.java
@@ -0,0 +1,138 @@
+/*

+       Licensed to the Apache Software Foundation (ASF) under one

+       or more contributor license agreements.  See the NOTICE file

+       distributed with this work for additional information

+       regarding copyright ownership.  The ASF licenses this file

+       to you 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 org.apache.cordova.file;

+

+import java.lang.reflect.InvocationTargetException;

+import java.lang.reflect.Method;

+import java.util.Arrays;

+

+import org.apache.cordova.CordovaInterface;

+import org.apache.cordova.CordovaPlugin;

+import org.apache.cordova.LOG;

+

+import android.content.pm.PackageManager;

+

+/**

+ * This class provides reflective methods for permission requesting and checking so that plugins

+ * written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.

+ */

+public class PermissionHelper {

+    private static final String LOG_TAG = "CordovaPermissionHelper";

+

+    /**

+     * Requests a "dangerous" permission for the application at runtime. This is a helper method

+     * alternative to cordovaInterface.requestPermission() that does not require the project to be

+     * built with cordova-android 5.0.0+

+     *

+     * @param plugin        The plugin the permission is being requested for

+     * @param requestCode   A requestCode to be passed to the plugin's onRequestPermissionResult()

+     *                      along with the result of the permission request

+     * @param permission    The permission to be requested

+     */

+    public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {

+        PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});

+    }

+

+    /**

+     * Requests "dangerous" permissions for the application at runtime. This is a helper method

+     * alternative to cordovaInterface.requestPermissions() that does not require the project to be

+     * built with cordova-android 5.0.0+

+     *

+     * @param plugin        The plugin the permissions are being requested for

+     * @param requestCode   A requestCode to be passed to the plugin's onRequestPermissionResult()

+     *                      along with the result of the permissions request

+     * @param permissions   The permissions to be requested

+     */

+    public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {

+        try {

+            Method requestPermission = CordovaInterface.class.getDeclaredMethod(

+                    "requestPermissions", CordovaPlugin.class, int.class, String[].class);

+

+            // If there is no exception, then this is cordova-android 5.0.0+

+            requestPermission.invoke(plugin.cordova, plugin, requestCode, permissions);

+        } catch (NoSuchMethodException noSuchMethodException) {

+            // cordova-android version is less than 5.0.0, so permission is implicitly granted

+            LOG.d(LOG_TAG, "No need to request permissions " + Arrays.toString(permissions));

+

+            // Notify the plugin that all were granted by using more reflection

+            deliverPermissionResult(plugin, requestCode, permissions);

+        } catch (IllegalAccessException illegalAccessException) {

+            // Should never be caught; this is a public method

+            LOG.e(LOG_TAG, "IllegalAccessException when requesting permissions " + Arrays.toString(permissions), illegalAccessException);

+        } catch(InvocationTargetException invocationTargetException) {

+            // This method does not throw any exceptions, so this should never be caught

+            LOG.e(LOG_TAG, "invocationTargetException when requesting permissions " + Arrays.toString(permissions), invocationTargetException);

+        }

+    }

+

+    /**

+     * Checks at runtime to see if the application has been granted a permission. This is a helper

+     * method alternative to cordovaInterface.hasPermission() that does not require the project to

+     * be built with cordova-android 5.0.0+

+     *

+     * @param plugin        The plugin the permission is being checked against

+     * @param permission    The permission to be checked

+     *

+     * @return              True if the permission has already been granted and false otherwise

+     */

+    public static boolean hasPermission(CordovaPlugin plugin, String permission) {

+        try {

+            Method hasPermission = CordovaInterface.class.getDeclaredMethod("hasPermission", String.class);

+

+            // If there is no exception, then this is cordova-android 5.0.0+

+            return (Boolean) hasPermission.invoke(plugin.cordova, permission);

+        } catch (NoSuchMethodException noSuchMethodException) {

+            // cordova-android version is less than 5.0.0, so permission is implicitly granted

+            LOG.d(LOG_TAG, "No need to check for permission " + permission);

+            return true;

+        } catch (IllegalAccessException illegalAccessException) {

+            // Should never be caught; this is a public method

+            LOG.e(LOG_TAG, "IllegalAccessException when checking permission " + permission, illegalAccessException);

+        } catch(InvocationTargetException invocationTargetException) {

+            // This method does not throw any exceptions, so this should never be caught

+            LOG.e(LOG_TAG, "invocationTargetException when checking permission " + permission, invocationTargetException);

+        }

+        return false;

+    }

+

+    private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {

+        // Generate the request results

+        int[] requestResults = new int[permissions.length];

+        Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);

+

+        try {

+            Method onRequestPermissionResult = CordovaPlugin.class.getDeclaredMethod(

+                    "onRequestPermissionResult", int.class, String[].class, int[].class);

+

+            onRequestPermissionResult.invoke(plugin, requestCode, permissions, requestResults);

+        } catch (NoSuchMethodException noSuchMethodException) {

+            // Should never be caught since the plugin must be written for cordova-android 5.0.0+ if it

+            // made it to this point

+            LOG.e(LOG_TAG, "NoSuchMethodException when delivering permissions results", noSuchMethodException);

+        } catch (IllegalAccessException illegalAccessException) {

+            // Should never be caught; this is a public method

+            LOG.e(LOG_TAG, "IllegalAccessException when delivering permissions results", illegalAccessException);

+        } catch(InvocationTargetException invocationTargetException) {

+            // This method may throw a JSONException. We are just duplicating cordova-android's

+            // exception handling behavior here; all it does is log the exception in CordovaActivity,

+            // print the stacktrace, and ignore it

+            LOG.e(LOG_TAG, "InvocationTargetException when delivering permissions results", invocationTargetException);

+        }

+    }

+}