├── www └── FilePath.js ├── package.json ├── README.md ├── .gitignore ├── CHANGELOG.md ├── plugin.xml ├── LICENSE.md └── src └── android └── FilePath.java /www/FilePath.js: -------------------------------------------------------------------------------- 1 | var exec = require('cordova/exec'); 2 | 3 | 4 | module.exports = { 5 | /** 6 | * Resolve native path for given content URL/path. 7 | * @param {String} path Content URL/path. 8 | * @param successCallback invoked with a native filesystem path string 9 | * @param errorCallback invoked if error occurs 10 | */ 11 | resolveNativePath: function(path, successCallback, errorCallback) { 12 | exec(successCallback, errorCallback, "FilePath", "resolveNativePath", [path]); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-filepath", 3 | "version": "1.6.0", 4 | "description": "Resolve native file paths from content URLs for Cordova platforms", 5 | "cordova": { 6 | "id": "cordova-plugin-filepath", 7 | "platforms": [ 8 | "android" 9 | ] 10 | }, 11 | "scripts": { 12 | "version": "sync-cordova-xml package.json plugin.xml --output plugin.xml && git add plugin.xml" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/hiddentao/cordova-plugin-filepath.git" 17 | }, 18 | "keywords": [ 19 | "cordova", 20 | "file", 21 | "ecosystem:cordova", 22 | "cordova-android" 23 | ], 24 | "author": "Ramesh Nair ", 25 | "license": "Apache 2.0", 26 | "devDependencies": { 27 | "sync-cordova-xml": "^0.4.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cordova-plugin-filepath 2 | 3 | **PLEASE NOTE: This plugin is no longer actively maintained.** 4 | 5 | This plugin allows you to resolve the native filesystem path for Android content 6 | URIs and is based on code in the [aFileChooser](https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java) library. 7 | 8 | Original inspiration [from StackOverflow](http://stackoverflow.com/questions/20067508/get-real-path-from-uri-android-kitkat-new-storage-access-framework). 9 | 10 | ## Installation 11 | 12 | ```bash 13 | $ cordova plugin add cordova-plugin-filepath 14 | ``` 15 | 16 | ## Supported Platforms 17 | 18 | * Android 19 | 20 | ## Usage 21 | 22 | Once installed the plugin defines the `window.FilePath` object. To resolve a 23 | file path: 24 | 25 | ```js 26 | window.FilePath.resolveNativePath('content://...', successCallback, errorCallback); 27 | ``` 28 | 29 | ##### successCallback 30 | Returns the ``file://`` file path. 31 | 32 | ##### errorCallback 33 | Returns the following object: 34 | ```js 35 | { code: , message: } 36 | ``` 37 | Possible error codes are: 38 | * ``-1`` - describes an invalid action 39 | * ``0`` - ``file://`` path could not be resolved 40 | * ``1`` - the native path links to a cloud file (e.g: from Google Drive app) 41 | 42 | ## LICENSE 43 | 44 | Apache (see LICENSE.md) 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless 74 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.6.0 2 | 3 | * [#70](https://github.com/hiddentao/cordova-plugin-filepath/pull/70) 4 | * [#71](https://github.com/hiddentao/cordova-plugin-filepath/pull/71) 5 | 6 | # 1.5.8 7 | 8 | * Added "no longer actively maintained" message to README 9 | 10 | # 1.5.7 11 | 12 | * [Fixes issue #60](https://github.com/hiddentao/cordova-plugin-filepath/issues/60) 13 | 14 | # 1.5.6 15 | 16 | * [Fixes issue #50](https://github.com/hiddentao/cordova-plugin-filepath/issues/50) 17 | 18 | # 1.5.5 19 | 20 | * [Remove unneeded permission](https://github.com/hiddentao/cordova-plugin-filepath/pull/51) 21 | 22 | # 1.5.4 23 | 24 | * [Remove unneeded permission](https://github.com/hiddentao/cordova-plugin-filepath/pull/42) 25 | 26 | # 1.0.2 27 | 28 | Release notes: 29 | 30 | * [da0e1c6](https://github.com/hiddentao/cordova-plugin-filepath/commit/da0e1c68e422caac9c196e41d2580460a6da6d67): Rename plugin to ``cordova-plugin-filepath`` 31 | 32 | # 1.0.1 33 | 34 | Merged PR#4 ([e571105](https://github.com/hiddentao/cordova-plugin-filepath/commit/e571105e0ffa2bfa09b27a13613778755e017961)) with the following changes: 35 | 36 | * Added detection for Google Drive content uri. Better error description. 37 | * Fix resolving native path from external storage. 38 | * Fix resolving native path from Google Photos. Append "file://" to final result 39 | * Added new uri authority for Google Photos 40 | 41 | # 1.0.0 42 | 43 | Initial version. 44 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | cordova-plugin-filepath 21 | Resolve native file paths from content URLs for Cordova platforms 22 | Apache 2.0 23 | cordova,file,ecosystem:cordova,cordova-android 24 | https://github.com/hiddentao/cordova-plugin-filepath.git 25 | https://github.com/hiddentao/cordova-plugin-filepath/issues 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Ramesh Nair 51 | 52 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015 [Ramesh Nair](https://hiddentao.com) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /src/android/FilePath.java: -------------------------------------------------------------------------------- 1 | package com.hiddentao.cordova.filepath; 2 | 3 | import android.text.TextUtils; 4 | import android.Manifest; 5 | import android.content.ContentUris; 6 | import android.content.Context; 7 | import android.content.pm.PackageManager; 8 | import android.net.Uri; 9 | import android.provider.OpenableColumns; 10 | import android.util.Log; 11 | import android.database.Cursor; 12 | import android.os.Build; 13 | import android.os.Environment; 14 | import android.provider.DocumentsContract; 15 | import android.provider.MediaStore; 16 | 17 | import org.apache.cordova.CallbackContext; 18 | import org.apache.cordova.CordovaInterface; 19 | import org.apache.cordova.CordovaPlugin; 20 | import org.apache.cordova.CordovaWebView; 21 | import org.apache.cordova.PermissionHelper; 22 | import org.json.JSONArray; 23 | import org.json.JSONObject; 24 | import org.json.JSONException; 25 | 26 | import java.io.FileOutputStream; 27 | import java.io.InputStream; 28 | import java.util.List; 29 | import java.io.File; 30 | 31 | public class FilePath extends CordovaPlugin { 32 | 33 | private static final String TAG = "[FilePath plugin]: "; 34 | 35 | private static final int INVALID_ACTION_ERROR_CODE = -1; 36 | 37 | private static final int GET_PATH_ERROR_CODE = 0; 38 | private static final String GET_PATH_ERROR_ID = null; 39 | 40 | private static final int GET_CLOUD_PATH_ERROR_CODE = 1; 41 | private static final String GET_CLOUD_PATH_ERROR_ID = "cloud"; 42 | 43 | private static final int RC_READ_EXTERNAL_STORAGE = 5; 44 | 45 | private static CallbackContext callback; 46 | private static String uriStr; 47 | 48 | public static final int READ_REQ_CODE = 0; 49 | 50 | public static final String READ = Manifest.permission.READ_EXTERNAL_STORAGE; 51 | 52 | protected void getReadPermission(int requestCode) { 53 | PermissionHelper.requestPermission(this, requestCode, READ); 54 | } 55 | 56 | public void initialize(CordovaInterface cordova, final CordovaWebView webView) { 57 | super.initialize(cordova, webView); 58 | } 59 | 60 | /** 61 | * Executes the request and returns PluginResult. 62 | * 63 | * @param action The action to execute. 64 | * @param args JSONArry of arguments for the plugin. 65 | * @param callbackContext The callback context through which to return stuff to caller. 66 | * @return A PluginResult object with a status and message. 67 | */ 68 | @Override 69 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 70 | this.callback = callbackContext; 71 | this.uriStr = args.getString(0); 72 | 73 | if (action.equals("resolveNativePath")) { 74 | if (PermissionHelper.hasPermission(this, READ)) { 75 | resolveNativePath(); 76 | } 77 | else { 78 | getReadPermission(READ_REQ_CODE); 79 | } 80 | 81 | return true; 82 | } 83 | else { 84 | JSONObject resultObj = new JSONObject(); 85 | 86 | resultObj.put("code", INVALID_ACTION_ERROR_CODE); 87 | resultObj.put("message", "Invalid action."); 88 | 89 | callbackContext.error(resultObj); 90 | } 91 | 92 | return false; 93 | } 94 | 95 | public void resolveNativePath() throws JSONException { 96 | JSONObject resultObj = new JSONObject(); 97 | /* content:///... */ 98 | Uri pvUrl = Uri.parse(this.uriStr); 99 | 100 | Log.d(TAG, "URI: " + this.uriStr); 101 | 102 | Context appContext = this.cordova.getActivity().getApplicationContext(); 103 | String filePath = getPath(appContext, pvUrl); 104 | 105 | //check result; send error/success callback 106 | if (filePath == GET_PATH_ERROR_ID) { 107 | resultObj.put("code", GET_PATH_ERROR_CODE); 108 | resultObj.put("message", "Unable to resolve filesystem path."); 109 | 110 | this.callback.error(resultObj); 111 | } 112 | else if (filePath.equals(GET_CLOUD_PATH_ERROR_ID)) { 113 | resultObj.put("code", GET_CLOUD_PATH_ERROR_CODE); 114 | resultObj.put("message", "Files from cloud cannot be resolved to filesystem, download is required."); 115 | 116 | this.callback.error(resultObj); 117 | } 118 | else { 119 | Log.d(TAG, "Filepath: " + filePath); 120 | 121 | this.callback.success("file://" + filePath); 122 | } 123 | } 124 | 125 | 126 | public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { 127 | for (int r : grantResults) { 128 | if (r == PackageManager.PERMISSION_DENIED) { 129 | JSONObject resultObj = new JSONObject(); 130 | resultObj.put("code", 3); 131 | resultObj.put("message", "Filesystem permission was denied."); 132 | 133 | this.callback.error(resultObj); 134 | return; 135 | } 136 | } 137 | 138 | if (requestCode == READ_REQ_CODE) { 139 | resolveNativePath(); 140 | } 141 | } 142 | 143 | 144 | /** 145 | * @param uri The Uri to check. 146 | * @return Whether the Uri authority is ExternalStorageProvider. 147 | */ 148 | private static boolean isExternalStorageDocument(Uri uri) { 149 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 150 | } 151 | 152 | /** 153 | * @param uri The Uri to check. 154 | * @return Whether the Uri authority is DownloadsProvider. 155 | */ 156 | private static boolean isDownloadsDocument(Uri uri) { 157 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 158 | } 159 | 160 | /** 161 | * @param uri The Uri to check. 162 | * @return Whether the Uri authority is MediaProvider. 163 | */ 164 | private static boolean isMediaDocument(Uri uri) { 165 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 166 | } 167 | 168 | /** 169 | * @param uri The Uri to check. 170 | * @return Whether the Uri authority is Google Photos. 171 | */ 172 | private static boolean isGooglePhotosUri(Uri uri) { 173 | return ("com.google.android.apps.photos.content".equals(uri.getAuthority()) 174 | || "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority())); 175 | } 176 | 177 | /** 178 | * @param uri The Uri to check. 179 | * @return Whether the Uri authority is Google Drive. 180 | */ 181 | private static boolean isGoogleDriveUri(Uri uri) { 182 | return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority()); 183 | } 184 | 185 | /** 186 | * @param uri The Uri to check. 187 | * @return Whether the Uri authority is One Drive. 188 | */ 189 | private static boolean isOneDriveUri(Uri uri) { 190 | return "com.microsoft.skydrive.content.external".equals(uri.getAuthority()); 191 | } 192 | 193 | /** 194 | * Get the value of the data column for this Uri. This is useful for 195 | * MediaStore Uris, and other file-based ContentProviders. 196 | * 197 | * @param context The context. 198 | * @param uri The Uri to query. 199 | * @param selection (Optional) Filter used in the query. 200 | * @param selectionArgs (Optional) Selection arguments used in the query. 201 | * @return The value of the _data column, which is typically a file path. 202 | */ 203 | private static String getDataColumn(Context context, Uri uri, String selection, 204 | String[] selectionArgs) { 205 | 206 | Cursor cursor = null; 207 | final String column = "_data"; 208 | final String[] projection = { 209 | column 210 | }; 211 | 212 | try { 213 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 214 | null); 215 | if (cursor != null && cursor.moveToFirst()) { 216 | final int column_index = cursor.getColumnIndexOrThrow(column); 217 | return cursor.getString(column_index); 218 | } 219 | } finally { 220 | if (cursor != null) 221 | cursor.close(); 222 | } 223 | return null; 224 | } 225 | 226 | /** 227 | * Get content:// from segment list 228 | * In the new Uri Authority of Google Photos, the last segment is not the content:// anymore 229 | * So let's iterate through all segments and find the content uri! 230 | * 231 | * @param segments The list of segment 232 | */ 233 | private static String getContentFromSegments(List segments) { 234 | String contentPath = ""; 235 | 236 | for (String item : segments) { 237 | if (item.startsWith("content://")) { 238 | contentPath = item; 239 | break; 240 | } 241 | } 242 | 243 | return contentPath; 244 | } 245 | 246 | /** 247 | * Check if a file exists on device 248 | * 249 | * @param filePath The absolute file path 250 | */ 251 | private static boolean fileExists(String filePath) { 252 | File file = new File(filePath); 253 | 254 | return file.exists(); 255 | } 256 | 257 | /** 258 | * Get full file path from external storage 259 | * 260 | * @param pathData The storage type and the relative path 261 | */ 262 | private static String getPathFromExtSD(String[] pathData) { 263 | final String type = pathData[0]; 264 | final String relativePath = "/" + pathData[1]; 265 | String fullPath = ""; 266 | 267 | // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string 268 | // something like "71F8-2C0A", some kind of unique id per storage 269 | // don't know any API that can get the root path of that storage based on its id. 270 | // 271 | // so no "primary" type, but let the check here for other devices 272 | if ("primary".equalsIgnoreCase(type)) { 273 | fullPath = Environment.getExternalStorageDirectory() + relativePath; 274 | if (fileExists(fullPath)) { 275 | return fullPath; 276 | } 277 | } 278 | 279 | //fix some devices(Android Q),'type' like "71F8-2C0A" 280 | //but "primary".equalsIgnoreCase(type) is false 281 | fullPath = "/storage/" + type + "/" + relativePath; 282 | if (fileExists(fullPath)) { 283 | return fullPath; 284 | } 285 | 286 | // Environment.isExternalStorageRemovable() is `true` for external and internal storage 287 | // so we cannot relay on it. 288 | // 289 | // instead, for each possible path, check if file exists 290 | // we'll start with secondary storage as this could be our (physically) removable sd card 291 | fullPath = System.getenv("SECONDARY_STORAGE") + relativePath; 292 | if (fileExists(fullPath)) { 293 | return fullPath; 294 | } 295 | 296 | fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath; 297 | if (fileExists(fullPath)) { 298 | return fullPath; 299 | } 300 | 301 | return ""; 302 | } 303 | 304 | /** 305 | * sometimes in raw type, the second part is a valid filepath 306 | * 307 | * @param rawPath The raw path 308 | */ 309 | private static String getRawFilepath(String rawPath) { 310 | final String[] split = rawPath.split(":"); 311 | if (fileExists(split[1])) { 312 | return split[1]; 313 | } 314 | 315 | return ""; 316 | } 317 | 318 | /** 319 | * Get a file path from a Uri. This will get the the path for Storage Access 320 | * Framework Documents, as well as the _data field for the MediaStore and 321 | * other file-based ContentProviders.
322 | *
323 | * Callers should check whether the path is local before assuming it 324 | * represents a local file. 325 | * 326 | * @param context The context. 327 | * @param uri The Uri to query. 328 | */ 329 | private static String getPath(final Context context, final Uri uri) { 330 | 331 | Log.d(TAG, "File - " + 332 | "Authority: " + uri.getAuthority() + 333 | ", Fragment: " + uri.getFragment() + 334 | ", Port: " + uri.getPort() + 335 | ", Query: " + uri.getQuery() + 336 | ", Scheme: " + uri.getScheme() + 337 | ", Host: " + uri.getHost() + 338 | ", Segments: " + uri.getPathSegments().toString() 339 | ); 340 | 341 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 342 | 343 | // DocumentProvider 344 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 345 | // ExternalStorageProvider 346 | if (isExternalStorageDocument(uri)) { 347 | final String docId = DocumentsContract.getDocumentId(uri); 348 | final String[] split = docId.split(":"); 349 | final String type = split[0]; 350 | 351 | String fullPath = getPathFromExtSD(split); 352 | if (fullPath != "") { 353 | return fullPath; 354 | } 355 | else { 356 | return null; 357 | } 358 | } 359 | // DownloadsProvider 360 | else if (isDownloadsDocument(uri)) { 361 | // thanks to https://github.com/hiddentao/cordova-plugin-filepath/issues/34#issuecomment-430129959 362 | Cursor cursor = null; 363 | try { 364 | cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null); 365 | if (cursor != null && cursor.moveToFirst()) { 366 | String fileName = cursor.getString(0); 367 | String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; 368 | if (fileExists(path)) { 369 | return path; 370 | } 371 | } 372 | } finally { 373 | if (cursor != null) 374 | cursor.close(); 375 | } 376 | // 377 | final String id = DocumentsContract.getDocumentId(uri); 378 | 379 | // sometimes in raw type, the second part is a valid filepath 380 | final String rawFilepath = getRawFilepath(id); 381 | if (rawFilepath != "") { 382 | return rawFilepath; 383 | } 384 | 385 | String[] contentUriPrefixesToTry = new String[]{ 386 | "content://downloads/public_downloads", 387 | "content://downloads/my_downloads" 388 | }; 389 | 390 | for (String contentUriPrefix : contentUriPrefixesToTry) { 391 | Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)); 392 | try { 393 | String path = getDataColumn(context, contentUri, null, null); 394 | if (path != null) { 395 | return path; 396 | } 397 | } catch (Exception e) { 398 | } 399 | } 400 | 401 | try { 402 | return getDriveFilePath(uri, context); 403 | } catch (Exception e) { 404 | return uri.getPath(); 405 | } 406 | 407 | } 408 | // MediaProvider 409 | else if (isMediaDocument(uri)) { 410 | final String docId = DocumentsContract.getDocumentId(uri); 411 | final String[] split = docId.split(":"); 412 | final String type = split[0]; 413 | 414 | Uri contentUri = null; 415 | if ("image".equals(type)) { 416 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 417 | } else if ("video".equals(type)) { 418 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 419 | } else if ("audio".equals(type)) { 420 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 421 | } else { 422 | contentUri = MediaStore.Files.getContentUri("external"); 423 | } 424 | 425 | final String selection = "_id=?"; 426 | final String[] selectionArgs = new String[]{ 427 | split[1] 428 | }; 429 | 430 | return getDataColumn(context, contentUri, selection, selectionArgs); 431 | } else if (isGoogleDriveUri(uri)) { 432 | return getDriveFilePath(uri, context); 433 | } 434 | } 435 | // MediaStore (and general) 436 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 437 | 438 | // Return the remote address 439 | if (isGooglePhotosUri(uri)) { 440 | if (uri.toString().contains("mediakey")) { 441 | return getDriveFilePath(uri, context); 442 | } else { 443 | String contentPath = getContentFromSegments(uri.getPathSegments()); 444 | if (contentPath != "") { 445 | return getPath(context, Uri.parse(contentPath)); 446 | } else { 447 | return null; 448 | } 449 | } 450 | } 451 | 452 | if (isGoogleDriveUri(uri) || isOneDriveUri(uri)) { 453 | return getDriveFilePath(uri, context); 454 | } 455 | 456 | return getDataColumn(context, uri, null, null); 457 | } 458 | // File 459 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 460 | return uri.getPath(); 461 | } 462 | 463 | return null; 464 | } 465 | 466 | private static String getDriveFilePath(Uri uri, Context context) { 467 | Uri returnUri = uri; 468 | Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null); 469 | /* 470 | * Get the column indexes of the data in the Cursor, 471 | * * move to the first row in the Cursor, get the data, 472 | * * and display it. 473 | * */ 474 | int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); 475 | int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); 476 | returnCursor.moveToFirst(); 477 | String name = (returnCursor.getString(nameIndex)); 478 | String size = (Long.toString(returnCursor.getLong(sizeIndex))); 479 | File file = new File(context.getCacheDir(), name); 480 | try { 481 | InputStream inputStream = context.getContentResolver().openInputStream(uri); 482 | FileOutputStream outputStream = new FileOutputStream(file); 483 | int read = 0; 484 | int maxBufferSize = 1 * 1024 * 1024; 485 | int bytesAvailable = inputStream.available(); 486 | 487 | //int bufferSize = 1024; 488 | int bufferSize = Math.min(bytesAvailable, maxBufferSize); 489 | 490 | final byte[] buffers = new byte[bufferSize]; 491 | while ((read = inputStream.read(buffers)) != -1) { 492 | outputStream.write(buffers, 0, read); 493 | } 494 | Log.e("File Size", "Size " + file.length()); 495 | inputStream.close(); 496 | outputStream.close(); 497 | Log.e("File Path", "Path " + file.getPath()); 498 | Log.e("File Size", "Size " + file.length()); 499 | } catch (Exception e) { 500 | Log.e("Exception", e.getMessage()); 501 | } 502 | return file.getPath(); 503 | } 504 | } 505 | --------------------------------------------------------------------------------