Skip to content

Commit 3da1e59

Browse files
committed
CB-10319 android: Adding reflective helper methods for permission requests
1 parent 77c63ef commit 3da1e59

File tree

3 files changed

+151
-21
lines changed

3 files changed

+151
-21
lines changed

plugin.xml

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ xmlns:android="http://schemas.android.com/apk/res/android"
2828
<keywords>cordova,file</keywords>
2929
<repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git</repo>
3030
<issue>https://issues.apache.org/jira/browse/CB/component/12320651</issue>
31-
<engines>
32-
<engine name="cordova-android" version=">=5.0.0-dev" /><!-- Uses Marshmallow Permissions -->
33-
</engines>
3431

3532
<js-module src="www/DirectoryEntry.js" name="DirectoryEntry">
3633
<clobbers target="window.DirectoryEntry" />
@@ -104,18 +101,18 @@ xmlns:android="http://schemas.android.com/apk/res/android"
104101
<js-module src="www/resolveLocalFileSystemURI.js" name="resolveLocalFileSystemURI">
105102
<merges target="window" />
106103
</js-module>
107-
104+
108105
<info>
109106
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.
110-
107+
111108
If this is a new application no changes are required.
112-
109+
113110
If this is an update to an existing application that did not specify an "AndroidPersistentFileLocation" you may need to add:
114-
111+
115112
"&lt;preference name="AndroidPersistentFileLocation" value="Compatibility" /&gt;"
116-
113+
117114
to config.xml in order for the application to find previously stored files.
118-
115+
119116
</info>
120117

121118
<!-- android -->
@@ -143,6 +140,7 @@ to config.xml in order for the application to find previously stored files.
143140
<source-file src="src/android/LocalFilesystem.java" target-dir="src/org/apache/cordova/file" />
144141
<source-file src="src/android/ContentFilesystem.java" target-dir="src/org/apache/cordova/file" />
145142
<source-file src="src/android/AssetFilesystem.java" target-dir="src/org/apache/cordova/file" />
143+
<source-file src="src/android/PermissionHelper.java" target-dir="src/org/apache/cordova/file" />
146144

147145
<!-- android specific file apis -->
148146
<js-module src="www/android/FileSystem.js" name="androidFileSystem">
@@ -183,7 +181,7 @@ to config.xml in order for the application to find previously stored files.
183181
<source-file src="src/android/ContentFilesystem.java" target-dir="src/org/apache/cordova/file" />
184182
<source-file src="src/android/AssetFilesystem.java" target-dir="src/org/apache/cordova/file" />
185183

186-
184+
187185
<!-- android specific file apis -->
188186
<js-module src="www/android/FileSystem.js" name="androidFileSystem">
189187
<merges target="window.FileSystem" />
@@ -410,7 +408,7 @@ to config.xml in order for the application to find previously stored files.
410408
<js-module src="src/browser/FileProxy.js" name="browserFileProxy">
411409
<runs />
412410
</js-module>
413-
411+
414412
<js-module src="www/fileSystemPaths.js" name="fileSystemPaths">
415413
<merges target="cordova" />
416414
<runs />

src/android/FileUtils.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -534,26 +534,20 @@ public void run(JSONArray args) throws FileNotFoundException, JSONException, Mal
534534
}
535535

536536
private void getReadPermission() {
537-
cordova.requestPermission(this, READ_PERM, Manifest.permission.READ_EXTERNAL_STORAGE);
537+
PermissionHelper.requestPermission(this, READ_PERM, Manifest.permission.READ_EXTERNAL_STORAGE);
538538
}
539539

540540
private void getWritePermission() {
541-
cordova.requestPermission(this, WRITE_PERM, Manifest.permission.WRITE_EXTERNAL_STORAGE);
541+
PermissionHelper.requestPermission(this, WRITE_PERM, Manifest.permission.WRITE_EXTERNAL_STORAGE);
542542
}
543543

544544

545545
private boolean hasReadPermission() {
546-
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
547-
return PackageManager.PERMISSION_GRANTED == cordova.getActivity().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
548-
else
549-
return true;
546+
return PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
550547
}
551548

552549
private boolean hasWritePermission() {
553-
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
554-
return PackageManager.PERMISSION_GRANTED == cordova.getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
555-
else
556-
return true;
550+
return PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
557551
}
558552

559553

src/android/PermissionHelper.java

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
*/
19+
package org.apache.cordova.file;
20+
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.lang.reflect.Method;
23+
import java.util.Arrays;
24+
25+
import org.apache.cordova.CordovaInterface;
26+
import org.apache.cordova.CordovaPlugin;
27+
import org.apache.cordova.LOG;
28+
29+
import android.content.pm.PackageManager;
30+
31+
/**
32+
* This class provides reflective methods for permission requesting and checking so that plugins
33+
* written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.
34+
*/
35+
public class PermissionHelper {
36+
private static final String LOG_TAG = "CordovaPermissionHelper";
37+
38+
/**
39+
* Requests a "dangerous" permission for the application at runtime. This is a helper method
40+
* alternative to cordovaInterface.requestPermission() that does not require the project to be
41+
* built with cordova-android 5.0.0+
42+
*
43+
* @param plugin The plugin the permission is being requested for
44+
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
45+
* along with the result of the permission request
46+
* @param permission The permission to be requested
47+
*/
48+
public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
49+
PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});
50+
}
51+
52+
/**
53+
* Requests "dangerous" permissions for the application at runtime. This is a helper method
54+
* alternative to cordovaInterface.requestPermissions() that does not require the project to be
55+
* built with cordova-android 5.0.0+
56+
*
57+
* @param plugin The plugin the permissions are being requested for
58+
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
59+
* along with the result of the permissions request
60+
* @param permissions The permissions to be requested
61+
*/
62+
public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {
63+
try {
64+
Method requestPermission = CordovaInterface.class.getDeclaredMethod(
65+
"requestPermissions", CordovaPlugin.class, int.class, String[].class);
66+
67+
// If there is no exception, then this is cordova-android 5.0.0+
68+
requestPermission.invoke(plugin.cordova, plugin, requestCode, permissions);
69+
} catch (NoSuchMethodException noSuchMethodException) {
70+
// cordova-android version is less than 5.0.0, so permission is implicitly granted
71+
LOG.d(LOG_TAG, "No need to request permissions " + Arrays.toString(permissions));
72+
73+
// Notify the plugin that all were granted by using more reflection
74+
deliverPermissionResult(plugin, requestCode, permissions);
75+
} catch (IllegalAccessException illegalAccessException) {
76+
// Should never be caught; this is a public method
77+
LOG.e(LOG_TAG, "IllegalAccessException when requesting permissions " + Arrays.toString(permissions), illegalAccessException);
78+
} catch(InvocationTargetException invocationTargetException) {
79+
// This method does not throw any exceptions, so this should never be caught
80+
LOG.e(LOG_TAG, "invocationTargetException when requesting permissions " + Arrays.toString(permissions), invocationTargetException);
81+
}
82+
}
83+
84+
/**
85+
* Checks at runtime to see if the application has been granted a permission. This is a helper
86+
* method alternative to cordovaInterface.hasPermission() that does not require the project to
87+
* be built with cordova-android 5.0.0+
88+
*
89+
* @param plugin The plugin the permission is being checked against
90+
* @param permission The permission to be checked
91+
*
92+
* @return True if the permission has already been granted and false otherwise
93+
*/
94+
public static boolean hasPermission(CordovaPlugin plugin, String permission) {
95+
try {
96+
Method hasPermission = CordovaInterface.class.getDeclaredMethod("hasPermission", String.class);
97+
98+
// If there is no exception, then this is cordova-android 5.0.0+
99+
return (Boolean) hasPermission.invoke(plugin.cordova, permission);
100+
} catch (NoSuchMethodException noSuchMethodException) {
101+
// cordova-android version is less than 5.0.0, so permission is implicitly granted
102+
LOG.d(LOG_TAG, "No need to check for permission " + permission);
103+
return true;
104+
} catch (IllegalAccessException illegalAccessException) {
105+
// Should never be caught; this is a public method
106+
LOG.e(LOG_TAG, "IllegalAccessException when checking permission " + permission, illegalAccessException);
107+
} catch(InvocationTargetException invocationTargetException) {
108+
// This method does not throw any exceptions, so this should never be caught
109+
LOG.e(LOG_TAG, "invocationTargetException when checking permission " + permission, invocationTargetException);
110+
}
111+
return false;
112+
}
113+
114+
private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {
115+
// Generate the request results
116+
int[] requestResults = new int[permissions.length];
117+
Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
118+
119+
try {
120+
Method onRequestPermissionResult = CordovaPlugin.class.getDeclaredMethod(
121+
"onRequestPermissionResult", int.class, String[].class, int[].class);
122+
123+
onRequestPermissionResult.invoke(plugin, requestCode, permissions, requestResults);
124+
} catch (NoSuchMethodException noSuchMethodException) {
125+
// Should never be caught since the plugin must be written for cordova-android 5.0.0+ if it
126+
// made it to this point
127+
LOG.e(LOG_TAG, "NoSuchMethodException when delivering permissions results", noSuchMethodException);
128+
} catch (IllegalAccessException illegalAccessException) {
129+
// Should never be caught; this is a public method
130+
LOG.e(LOG_TAG, "IllegalAccessException when delivering permissions results", illegalAccessException);
131+
} catch(InvocationTargetException invocationTargetException) {
132+
// This method may throw a JSONException. We are just duplicating cordova-android's
133+
// exception handling behavior here; all it does is log the exception in CordovaActivity,
134+
// print the stacktrace, and ignore it
135+
LOG.e(LOG_TAG, "InvocationTargetException when delivering permissions results", invocationTargetException);
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)