diff --git a/app b/app index 44856e1..ad810d5 160000 --- a/app +++ b/app @@ -1 +1 @@ -Subproject commit 44856e1ba16dd621e521c30263176692f73b8453 +Subproject commit ad810d5c9ece47b23603aea827846ae5a0aecb14 diff --git a/buildozer.spec.arm.ci b/buildozer.spec.arm.ci index ba8f52a..a138266 100644 --- a/buildozer.spec.arm.ci +++ b/buildozer.spec.arm.ci @@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy -requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, defusedxml, git+https://github.com/lbryio/aioupnp.git@ab7ef0048bbce6404e463d20e8a15046ea6941f0#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.39.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.39.0#egg=torba&subdirectory=torba" +requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, defusedxml, git+https://github.com/lbryio/aioupnp.git@ab7ef0048bbce6404e463d20e8a15046ea6941f0#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.40.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.40.0#egg=torba&subdirectory=torba" # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes diff --git a/buildozer.spec.arm64.ci b/buildozer.spec.arm64.ci index cdeb0a2..f9917ed 100644 --- a/buildozer.spec.arm64.ci +++ b/buildozer.spec.arm64.ci @@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy -requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, defusedxml, git+https://github.com/lbryio/aioupnp.git@ab7ef0048bbce6404e463d20e8a15046ea6941f0#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.39.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.39.0#egg=torba&subdirectory=torba" +requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, defusedxml, git+https://github.com/lbryio/aioupnp.git@ab7ef0048bbce6404e463d20e8a15046ea6941f0#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.40.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.40.0#egg=torba&subdirectory=torba" # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes diff --git a/buildozer.spec.sample b/buildozer.spec.sample index 1cf6af1..5dcc2e2 100644 --- a/buildozer.spec.sample +++ b/buildozer.spec.sample @@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy -requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, defusedxml, git+https://github.com/lbryio/aioupnp.git@ab7ef0048bbce6404e463d20e8a15046ea6941f0#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.39.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.39.0#egg=torba&subdirectory=torba" +requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, defusedxml, git+https://github.com/lbryio/aioupnp.git@ab7ef0048bbce6404e463d20e8a15046ea6941f0#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.40.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.40.0#egg=torba&subdirectory=torba" # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes diff --git a/buildozer.spec.vagrant b/buildozer.spec.vagrant index b904ace..6170ab4 100644 --- a/buildozer.spec.vagrant +++ b/buildozer.spec.vagrant @@ -272,4 +272,4 @@ warn_on_root = 1 # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug -requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, defusedxml, git+https://github.com/lbryio/aioupnp.git@ab7ef0048bbce6404e463d20e8a15046ea6941f0#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.39.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.39.0#egg=torba&subdirectory=torba" \ No newline at end of file +requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, defusedxml, git+https://github.com/lbryio/aioupnp.git@ab7ef0048bbce6404e463d20e8a15046ea6941f0#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.40.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.40.0#egg=torba&subdirectory=torba" \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.gradle index d874c60..b201096 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.gradle +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.gradle @@ -3,8 +3,6 @@ include ':@react-native-community_async-storage' project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, './react/node_modules/@react-native-community/async-storage/android') include ':react-native-camera' project(':react-native-camera').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-camera/android') -include ':react-native-document-picker' -project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-document-picker/android') include ':react-native-exception-handler' project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-exception-handler/android') include ':react-native-fast-image' diff --git a/src/main/java/io/lbry/browser/MainActivity.java b/src/main/java/io/lbry/browser/MainActivity.java index 1e97d89..2a03407 100644 --- a/src/main/java/io/lbry/browser/MainActivity.java +++ b/src/main/java/io/lbry/browser/MainActivity.java @@ -1,11 +1,14 @@ package io.lbry.browser; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.app.Activity; import android.app.ActivityManager; import android.content.BroadcastReceiver; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -14,6 +17,7 @@ import android.content.SharedPreferences; import android.database.Cursor; import android.Manifest; import android.net.Uri; +import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.Settings; import android.support.v4.app.ActivityCompat; @@ -294,33 +298,6 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand registerReceiver(smsReceiver, smsFilter); } - public static String getUriPath(Context context, Uri uri) throws URISyntaxException { - if ("content".equalsIgnoreCase(uri.getScheme())) { - String[] projection = { MediaStore.MediaColumns.DATA }; - Cursor cursor = null; - - try { - cursor = context.getContentResolver().query(uri, projection, null, null, null); - int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); - if (cursor.moveToFirst()) { - return cursor.getString(index); - } - } catch (Exception e) { - // pass - android.util.Log.e("ReactNativeJS", e.getMessage(), e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); - } - - return null; - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == OVERLAY_PERMISSION_REQ_CODE) { @@ -336,19 +313,11 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand if (reactContext != null) { if (resultCode == RESULT_OK) { Uri fileUri = data.getData(); - try { - String filePath = getUriPath(this, fileUri); - android.util.Log.d("ReactNativeJS", "fileUri=" + filePath); - - WritableMap params = Arguments.createMap(); - params.putString("path", filePath); - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onDocumentPickerFilePicked", params); - } catch (URISyntaxException ex) { - // failed to get a file path - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onDocumentPickerCanceled", null); - } + String filePath = getRealPathFromURI_API19(this, fileUri); + WritableMap params = Arguments.createMap(); + params.putString("path", filePath); + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("onDocumentPickerFilePicked", params); } else if (resultCode == RESULT_CANCELED) { // user canceled or request failed reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -668,4 +637,161 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand return array; } + + /** + * https://gist.github.com/HBiSoft/15899990b8cd0723c3a894c1636550a8 + */ + @SuppressLint("NewApi") + public static String getRealPathFromURI_API19(final Context context, final Uri uri) { + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + // This is for checking Main Memory + if ("primary".equalsIgnoreCase(type)) { + if (split.length > 1) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else { + return Environment.getExternalStorageDirectory() + "/"; + } + // This is for checking SD Card + } else { + return "storage" + "/" + docId.replace(":", "/"); + } + + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + String fileName = getFilePath(context, uri); + if (fileName != null) { + return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; + } + + String id = DocumentsContract.getDocumentId(uri); + if (id.startsWith("raw:")) { + id = id.replaceFirst("raw:", ""); + File file = new File(id); + if (file.exists()) + return id; + } + + final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + + public static String getFilePath(Context context, Uri uri) { + Cursor cursor = null; + final String[] projection = { MediaStore.MediaColumns.DISPLAY_NAME }; + + try { + cursor = context.getContentResolver().query(uri, projection, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } }