Skip to content

Commit 6aeb9d9

Browse files
committed
CB-10977 android: Removing global state used for permission requests
This closes apache#174
1 parent 1e2593f commit 6aeb9d9

File tree

3 files changed

+163
-74
lines changed

3 files changed

+163
-74
lines changed

plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ to config.xml in order for the application to find previously stored files.
146146
<source-file src="src/android/LocalFilesystem.java" target-dir="src/org/apache/cordova/file" />
147147
<source-file src="src/android/ContentFilesystem.java" target-dir="src/org/apache/cordova/file" />
148148
<source-file src="src/android/AssetFilesystem.java" target-dir="src/org/apache/cordova/file" />
149+
<source-file src="src/android/PendingRequests.java" target-dir="src/org/apache/cordova/file" />
149150
<source-file src="src/android/PermissionHelper.java" target-dir="src/org/apache/cordova/file" />
150151

151152
<!-- android specific file apis -->

src/android/FileUtils.java

Lines changed: 68 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,20 @@ public class FileUtils extends CordovaPlugin {
7373
* Permission callback codes
7474
*/
7575

76-
public static final int GET_FILE_CALLBACK_CODE = 0;
77-
public static final int WRITE_CALLBACK_CODE = 1;
78-
public static final int GET_DIRECTORY_CALLBACK_CODE = 2;
76+
public static final int ACTION_GET_FILE = 0;
77+
public static final int ACTION_WRITE = 1;
78+
public static final int ACTION_GET_DIRECTORY = 2;
79+
7980
public static final int WRITE = 3;
8081
public static final int READ = 4;
8182

8283
public static int UNKNOWN_ERR = 1000;
8384

8485
private boolean configured = false;
85-
private String lastRawArgs;
8686

87-
private CallbackContext callback;
87+
private PendingRequests pendingRequests;
88+
89+
8890

8991
/*
9092
* We need both read and write when accessing the storage, I think.
@@ -171,6 +173,7 @@ protected HashMap<String, String> getAvailableFileSystems(Activity activity) {
171173
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
172174
super.initialize(cordova, webView);
173175
this.filesystems = new ArrayList<Filesystem>();
176+
this.pendingRequests = new PendingRequests();
174177

175178
String tempRoot = null;
176179
String persistentRoot = null;
@@ -262,14 +265,12 @@ public Uri remapUri(Uri uri) {
262265
}
263266

264267
public boolean execute(String action, final String rawArgs, final CallbackContext callbackContext) {
265-
this.callback = callbackContext;
266-
lastRawArgs = rawArgs;
267268
if (!configured) {
268269
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "File plugin is not configured. Please see the README.md file for details on how to update config.xml"));
269270
return true;
270271
}
271272
if (action.equals("testSaveLocationExists")) {
272-
threadhelper( new FileOp( ){
273+
threadhelper(new FileOp() {
273274
public void run(JSONArray args) {
274275
boolean b = DirectoryManager.testSaveLocationExists();
275276
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
@@ -356,7 +357,7 @@ public void run(JSONArray args) throws JSONException, FileNotFoundException, IOE
356357
Boolean isBinary=args.getBoolean(3);
357358

358359
if(needPermission(nativeURL, WRITE)) {
359-
getWritePermission();
360+
getWritePermission(rawArgs, ACTION_WRITE, callbackContext);
360361
}
361362
else {
362363
long fileSize = write(fname, data, offset, isBinary);
@@ -440,10 +441,10 @@ public void run(JSONArray args) throws FileExistsException, IOException, TypeMis
440441
boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
441442

442443
if(containsCreate && needPermission(nativeURL, WRITE)) {
443-
getPermissionDir(WRITE);
444+
getWritePermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
444445
}
445446
else if(!containsCreate && needPermission(nativeURL, READ)) {
446-
getPermissionDir(READ);
447+
getReadPermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
447448
}
448449
else {
449450
JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
@@ -461,10 +462,10 @@ public void run(JSONArray args) throws FileExistsException, IOException, TypeMis
461462
boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
462463

463464
if(containsCreate && needPermission(nativeURL, WRITE)) {
464-
getPermissionFile(WRITE);
465+
getWritePermission(rawArgs, ACTION_GET_FILE, callbackContext);
465466
}
466467
else if(!containsCreate && needPermission(nativeURL, READ)) {
467-
getPermissionFile(READ);
468+
getReadPermission(rawArgs, ACTION_GET_FILE, callbackContext);
468469
}
469470
else {
470471
JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
@@ -547,26 +548,14 @@ public void run(JSONArray args) throws FileNotFoundException, JSONException, Mal
547548
return true;
548549
}
549550

550-
private void getPermissionFile(int permissionType) {
551-
if(permissionType == READ) {
552-
PermissionHelper.requestPermission(this, GET_FILE_CALLBACK_CODE, Manifest.permission.READ_EXTERNAL_STORAGE);
553-
}
554-
else {
555-
PermissionHelper.requestPermission(this, GET_FILE_CALLBACK_CODE, Manifest.permission.WRITE_EXTERNAL_STORAGE);
556-
}
551+
private void getReadPermission(String rawArgs, int action, CallbackContext callbackContext) {
552+
int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
553+
PermissionHelper.requestPermission(this, requestCode, Manifest.permission.READ_EXTERNAL_STORAGE);
557554
}
558555

559-
private void getPermissionDir(int permissionType) {
560-
if(permissionType == READ) {
561-
PermissionHelper.requestPermission(this, GET_DIRECTORY_CALLBACK_CODE, Manifest.permission.READ_EXTERNAL_STORAGE);
562-
}
563-
else {
564-
PermissionHelper.requestPermission(this, GET_DIRECTORY_CALLBACK_CODE, Manifest.permission.WRITE_EXTERNAL_STORAGE);
565-
}
566-
}
567-
568-
private void getWritePermission() {
569-
PermissionHelper.requestPermission(this, WRITE_CALLBACK_CODE, Manifest.permission.WRITE_EXTERNAL_STORAGE);
556+
private void getWritePermission(String rawArgs, int action, CallbackContext callbackContext) {
557+
int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
558+
PermissionHelper.requestPermission(this, requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE);
570559
}
571560

572561
private boolean hasReadPermission() {
@@ -1151,51 +1140,56 @@ private long truncateFile(String srcURLstr, long size) throws FileNotFoundExcept
11511140

11521141
public void onRequestPermissionResult(int requestCode, String[] permissions,
11531142
int[] grantResults) throws JSONException {
1154-
for(int r:grantResults)
1155-
{
1156-
if(r == PackageManager.PERMISSION_DENIED)
1143+
1144+
final PendingRequests.Request req = pendingRequests.getAndRemove(requestCode);
1145+
if (req != null) {
1146+
for(int r:grantResults)
11571147
{
1158-
callback.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR));
1148+
if(r == PackageManager.PERMISSION_DENIED)
1149+
{
1150+
req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR));
1151+
return;
1152+
}
11591153
}
1154+
switch(req.getAction())
1155+
{
1156+
case ACTION_GET_FILE:
1157+
threadhelper( new FileOp( ){
1158+
public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
1159+
String dirname = args.getString(0);
1160+
1161+
String path = args.getString(1);
1162+
JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
1163+
req.getCallbackContext().success(obj);
1164+
}
1165+
}, req.getRawArgs(), req.getCallbackContext());
1166+
break;
1167+
case ACTION_GET_DIRECTORY:
1168+
threadhelper( new FileOp( ){
1169+
public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
1170+
String dirname = args.getString(0);
1171+
1172+
String path = args.getString(1);
1173+
JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
1174+
req.getCallbackContext().success(obj);
1175+
}
1176+
}, req.getRawArgs(), req.getCallbackContext());
1177+
break;
1178+
case ACTION_WRITE:
1179+
threadhelper( new FileOp( ){
1180+
public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
1181+
String fname=args.getString(0);
1182+
String data=args.getString(1);
1183+
int offset=args.getInt(2);
1184+
Boolean isBinary=args.getBoolean(3);
1185+
long fileSize = write(fname, data, offset, isBinary);
1186+
req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
1187+
}
1188+
}, req.getRawArgs(), req.getCallbackContext());
1189+
break;
1190+
}
1191+
} else {
1192+
Log.d(LOG_TAG, "Received permission callback for unknown request code");
11601193
}
1161-
switch(requestCode)
1162-
{
1163-
case GET_FILE_CALLBACK_CODE:
1164-
threadhelper( new FileOp( ){
1165-
public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
1166-
String dirname = args.getString(0);
1167-
1168-
String path = args.getString(1);
1169-
JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
1170-
callback.success(obj);
1171-
}
1172-
}, lastRawArgs, callback);
1173-
break;
1174-
case GET_DIRECTORY_CALLBACK_CODE:
1175-
threadhelper( new FileOp( ){
1176-
public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
1177-
String dirname = args.getString(0);
1178-
1179-
String path = args.getString(1);
1180-
JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
1181-
callback.success(obj);
1182-
}
1183-
}, lastRawArgs, callback);
1184-
break;
1185-
case WRITE_CALLBACK_CODE:
1186-
threadhelper( new FileOp( ){
1187-
public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
1188-
String fname=args.getString(0);
1189-
String data=args.getString(1);
1190-
int offset=args.getInt(2);
1191-
Boolean isBinary=args.getBoolean(3);
1192-
long fileSize = write(fname, data, offset, isBinary);
1193-
callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
1194-
}
1195-
}, lastRawArgs, callback);
1196-
break;
1197-
1198-
}
1199-
12001194
}
12011195
}

src/android/PendingRequests.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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 android.util.SparseArray;
22+
23+
import org.apache.cordova.CallbackContext;
24+
25+
/**
26+
* Holds pending runtime permission requests
27+
*/
28+
class PendingRequests {
29+
private int currentReqId = 0;
30+
private SparseArray<Request> requests = new SparseArray<Request>();
31+
32+
/**
33+
* Creates a request and adds it to the array of pending requests. Each created request gets a
34+
* unique result code for use with requestPermission()
35+
* @param rawArgs The raw arguments passed to the plugin
36+
* @param action The action this request corresponds to (get file, etc.)
37+
* @param callbackContext The CallbackContext for this plugin call
38+
* @return The request code that can be used to retrieve the Request object
39+
*/
40+
public synchronized int createRequest(String rawArgs, int action, CallbackContext callbackContext) {
41+
Request req = new Request(rawArgs, action, callbackContext);
42+
requests.put(req.requestCode, req);
43+
return req.requestCode;
44+
}
45+
46+
/**
47+
* Gets the request corresponding to this request code and removes it from the pending requests
48+
* @param requestCode The request code for the desired request
49+
* @return The request corresponding to the given request code or null if such a
50+
* request is not found
51+
*/
52+
public synchronized Request getAndRemove(int requestCode) {
53+
Request result = requests.get(requestCode);
54+
requests.remove(requestCode);
55+
return result;
56+
}
57+
58+
/**
59+
* Holds the options and CallbackContext for a call made to the plugin.
60+
*/
61+
public class Request {
62+
63+
// Unique int used to identify this request in any Android permission callback
64+
private int requestCode;
65+
66+
// Action to be performed after permission request result
67+
private int action;
68+
69+
// Raw arguments passed to plugin
70+
private String rawArgs;
71+
72+
// The callback context for this plugin request
73+
private CallbackContext callbackContext;
74+
75+
private Request(String rawArgs, int action, CallbackContext callbackContext) {
76+
this.rawArgs = rawArgs;
77+
this.action = action;
78+
this.callbackContext = callbackContext;
79+
this.requestCode = currentReqId ++;
80+
}
81+
82+
public int getAction() {
83+
return this.action;
84+
}
85+
86+
public String getRawArgs() {
87+
return rawArgs;
88+
}
89+
90+
public CallbackContext getCallbackContext() {
91+
return callbackContext;
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)