New build #125
64
.gitignore
vendored
|
@ -1,4 +1,66 @@
|
||||||
|
# OSX
|
||||||
|
#
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Xcode
|
||||||
|
#
|
||||||
|
build/
|
||||||
|
*.pbxuser
|
||||||
|
!default.pbxuser
|
||||||
|
*.mode1v3
|
||||||
|
!default.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
!default.mode2v3
|
||||||
|
*.perspectivev3
|
||||||
|
!default.perspectivev3
|
||||||
|
xcuserdata
|
||||||
|
*.xccheckout
|
||||||
|
*.moved-aside
|
||||||
|
DerivedData
|
||||||
|
*.hmap
|
||||||
|
*.ipa
|
||||||
|
*.xcuserstate
|
||||||
|
|
||||||
|
# Android/IntelliJ
|
||||||
|
#
|
||||||
|
build/
|
||||||
|
.idea
|
||||||
|
.gradle
|
||||||
|
local.properties
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# node.js
|
||||||
|
#
|
||||||
node_modules/
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# BUCK
|
||||||
|
buck-out/
|
||||||
|
\.buckd/
|
||||||
|
*.keystore
|
||||||
|
!debug.keystore
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
#
|
||||||
|
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||||
|
# screenshots whenever they are needed.
|
||||||
|
# For more information about the recommended setup visit:
|
||||||
|
# https://docs.fastlane.tools/best-practices/source-control/
|
||||||
|
|
||||||
|
*/fastlane/report.xml
|
||||||
|
*/fastlane/Preview.html
|
||||||
|
*/fastlane/screenshots
|
||||||
|
|
||||||
|
# Bundle artifact
|
||||||
|
*.jsbundle
|
||||||
|
|
||||||
|
# CocoaPods
|
||||||
|
/ios/Pods/
|
||||||
|
|
||||||
|
# Other Files
|
||||||
|
android/app/google-services.json
|
||||||
*.log
|
*.log
|
||||||
.vagrant
|
.vagrant
|
||||||
|
android/app/build/*
|
||||||
|
android/bin
|
||||||
|
|
14
__tests__/App-test.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'react-native';
|
||||||
|
import React from 'react';
|
||||||
|
import App from '../App';
|
||||||
|
|
||||||
|
// Note: test renderer must be required after react-native.
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
|
it('renders correctly', () => {
|
||||||
|
renderer.create(<App />);
|
||||||
|
});
|
67
android/.gitignore
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# OSX
|
||||||
|
#
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Xcode
|
||||||
|
#
|
||||||
|
build/
|
||||||
|
*.pbxuser
|
||||||
|
!default.pbxuser
|
||||||
|
*.mode1v3
|
||||||
|
!default.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
!default.mode2v3
|
||||||
|
*.perspectivev3
|
||||||
|
!default.perspectivev3
|
||||||
|
xcuserdata
|
||||||
|
*.xccheckout
|
||||||
|
*.moved-aside
|
||||||
|
DerivedData
|
||||||
|
*.hmap
|
||||||
|
*.ipa
|
||||||
|
*.xcuserstate
|
||||||
|
|
||||||
|
# Android/IntelliJ
|
||||||
|
#
|
||||||
|
build/
|
||||||
|
.idea
|
||||||
|
.gradle
|
||||||
|
local.properties
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# node.js
|
||||||
|
#
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# BUCK
|
||||||
|
buck-out/
|
||||||
|
\.buckd/
|
||||||
|
*.keystore
|
||||||
|
!debug.keystore
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
#
|
||||||
|
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||||
|
# screenshots whenever they are needed.
|
||||||
|
# For more information about the recommended setup visit:
|
||||||
|
# https://docs.fastlane.tools/best-practices/source-control/
|
||||||
|
|
||||||
|
*/fastlane/report.xml
|
||||||
|
*/fastlane/Preview.html
|
||||||
|
*/fastlane/screenshots
|
||||||
|
|
||||||
|
# Bundle artifact
|
||||||
|
*.jsbundle
|
||||||
|
|
||||||
|
# CocoaPods
|
||||||
|
/ios/Pods/
|
||||||
|
|
||||||
|
# Other Files
|
||||||
|
app/google-services.json
|
||||||
|
*.log
|
||||||
|
.vagrant
|
||||||
|
*.hprof
|
||||||
|
app/build
|
||||||
|
app/bin
|
55
android/app/BUCK
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# To learn about Buck see [Docs](https://buckbuild.com/).
|
||||||
|
# To run your application with Buck:
|
||||||
|
# - install Buck
|
||||||
|
# - `npm start` - to start the packager
|
||||||
|
# - `cd android`
|
||||||
|
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
|
||||||
|
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
|
||||||
|
# - `buck install -r android/app` - compile, install and run application
|
||||||
|
#
|
||||||
|
|
||||||
|
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
|
||||||
|
|
||||||
|
lib_deps = []
|
||||||
|
|
||||||
|
create_aar_targets(glob(["libs/*.aar"]))
|
||||||
|
|
||||||
|
create_jar_targets(glob(["libs/*.jar"]))
|
||||||
|
|
||||||
|
android_library(
|
||||||
|
name = "all-libs",
|
||||||
|
exported_deps = lib_deps,
|
||||||
|
)
|
||||||
|
|
||||||
|
android_library(
|
||||||
|
name = "app-code",
|
||||||
|
srcs = glob([
|
||||||
|
"src/main/java/**/*.java",
|
||||||
|
]),
|
||||||
|
deps = [
|
||||||
|
":all-libs",
|
||||||
|
":build_config",
|
||||||
|
":res",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
android_build_config(
|
||||||
|
name = "build_config",
|
||||||
|
package = "com.lbryandroid",
|
||||||
|
)
|
||||||
|
|
||||||
|
android_resource(
|
||||||
|
name = "res",
|
||||||
|
package = "com.lbryandroid",
|
||||||
|
res = "src/main/res",
|
||||||
|
)
|
||||||
|
|
||||||
|
android_binary(
|
||||||
|
name = "app",
|
||||||
|
keystore = "//android/keystores:debug",
|
||||||
|
manifest = "src/main/AndroidManifest.xml",
|
||||||
|
package_type = "debug",
|
||||||
|
deps = [
|
||||||
|
":app-code",
|
||||||
|
],
|
||||||
|
)
|
248
android/app/build.gradle
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
apply plugin: "com.android.application"
|
||||||
|
|
||||||
|
import com.android.build.OutputFile
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
|
||||||
|
* and bundleReleaseJsAndAssets).
|
||||||
|
* These basically call `react-native bundle` with the correct arguments during the Android build
|
||||||
|
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
|
||||||
|
* bundle directly from the development server. Below you can see all the possible configurations
|
||||||
|
* and their defaults. If you decide to add a configuration block, make sure to add it before the
|
||||||
|
* `apply from: "../../node_modules/react-native/react.gradle"` line.
|
||||||
|
*
|
||||||
|
* project.ext.react = [
|
||||||
|
* // the name of the generated asset file containing your JS bundle
|
||||||
|
* bundleAssetName: "index.android.bundle",
|
||||||
|
*
|
||||||
|
* // the entry file for bundle generation
|
||||||
|
* entryFile: "index.android.js",
|
||||||
|
*
|
||||||
|
* // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format
|
||||||
|
* bundleCommand: "ram-bundle",
|
||||||
|
*
|
||||||
|
* // whether to bundle JS and assets in debug mode
|
||||||
|
* bundleInDebug: false,
|
||||||
|
*
|
||||||
|
* // whether to bundle JS and assets in release mode
|
||||||
|
* bundleInRelease: true,
|
||||||
|
*
|
||||||
|
* // whether to bundle JS and assets in another build variant (if configured).
|
||||||
|
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
|
||||||
|
* // The configuration property can be in the following formats
|
||||||
|
* // 'bundleIn${productFlavor}${buildType}'
|
||||||
|
* // 'bundleIn${buildType}'
|
||||||
|
* // bundleInFreeDebug: true,
|
||||||
|
* // bundleInPaidRelease: true,
|
||||||
|
* // bundleInBeta: true,
|
||||||
|
*
|
||||||
|
* // whether to disable dev mode in custom build variants (by default only disabled in release)
|
||||||
|
* // for example: to disable dev mode in the staging build type (if configured)
|
||||||
|
* devDisabledInStaging: true,
|
||||||
|
* // The configuration property can be in the following formats
|
||||||
|
* // 'devDisabledIn${productFlavor}${buildType}'
|
||||||
|
* // 'devDisabledIn${buildType}'
|
||||||
|
*
|
||||||
|
* // the root of your project, i.e. where "package.json" lives
|
||||||
|
* root: "../../",
|
||||||
|
*
|
||||||
|
* // where to put the JS bundle asset in debug mode
|
||||||
|
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
|
||||||
|
*
|
||||||
|
* // where to put the JS bundle asset in release mode
|
||||||
|
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
|
||||||
|
*
|
||||||
|
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||||
|
* // require('./image.png')), in debug mode
|
||||||
|
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
|
||||||
|
*
|
||||||
|
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||||
|
* // require('./image.png')), in release mode
|
||||||
|
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
|
||||||
|
*
|
||||||
|
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
|
||||||
|
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
|
||||||
|
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
|
||||||
|
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
|
||||||
|
* // for example, you might want to remove it from here.
|
||||||
|
* inputExcludes: ["android/**", "ios/**"],
|
||||||
|
*
|
||||||
|
* // override which node gets called and with what additional arguments
|
||||||
|
* nodeExecutableAndArgs: ["node"],
|
||||||
|
*
|
||||||
|
* // supply additional arguments to the packager
|
||||||
|
* extraPackagerArgs: []
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
|
||||||
|
task buildReactNativeBundle(type:Exec) {
|
||||||
|
println("Building React Native bundle")
|
||||||
|
workingDir new File(rootProject.projectDir, '../')
|
||||||
|
commandLine './bundle-android.sh'
|
||||||
|
}
|
||||||
|
preBuild.dependsOn buildReactNativeBundle
|
||||||
|
|
||||||
|
task printVersionName {
|
||||||
|
doLast {
|
||||||
|
println android.defaultConfig.versionName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.ext.react = [
|
||||||
|
entryFile: "index.js",
|
||||||
|
enableHermes: false, // clean and rebuild if changing
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this to true to create two separate APKs instead of one:
|
||||||
|
* - An APK that only works on ARM devices
|
||||||
|
* - An APK that only works on x86 devices
|
||||||
|
* The advantage is the size of the APK is reduced by about 4MB.
|
||||||
|
* Upload all the APKs to the Play Store and people will download
|
||||||
|
* the correct one based on the CPU architecture of their device.
|
||||||
|
*/
|
||||||
|
def enableSeparateBuildPerCPUArchitecture = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run Proguard to shrink the Java bytecode in release builds.
|
||||||
|
*/
|
||||||
|
def enableProguardInReleaseBuilds = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The preferred build flavor of JavaScriptCore.
|
||||||
|
*
|
||||||
|
* For example, to use the international variant, you can use:
|
||||||
|
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||||
|
*
|
||||||
|
* The international variant includes ICU i18n library and necessary data
|
||||||
|
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||||
|
* give correct results when using with locales other than en-US. Note that
|
||||||
|
* this variant is about 6MiB larger per architecture than default.
|
||||||
|
*/
|
||||||
|
def jscFlavor = 'org.webkit:android-jsc:+'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to enable the Hermes VM.
|
||||||
|
*
|
||||||
|
* This should be set on project.ext.react and mirrored here. If it is not set
|
||||||
|
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
|
||||||
|
* and the benefits of using Hermes will therefore be sharply reduced.
|
||||||
|
*/
|
||||||
|
def enableHermes = project.ext.react.get("enableHermes", false);
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||||
|
flavorDimensions "default"
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "io.lbry.browser"
|
||||||
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
|
versionCode 1302
|
||||||
|
versionName "0.13.2"
|
||||||
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
|
multiDexEnabled true
|
||||||
|
}
|
||||||
|
dexOptions {
|
||||||
|
jumboMode true
|
||||||
|
}
|
||||||
|
productFlavors {
|
||||||
|
__32bit {
|
||||||
|
ndk {
|
||||||
|
abiFilter "armeabi-v7a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__64bit {
|
||||||
|
ndk {
|
||||||
|
abiFilter "arm64-v8a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signingConfigs {
|
||||||
|
debug {
|
||||||
|
storeFile file('debug.keystore')
|
||||||
|
storePassword 'android'
|
||||||
|
keyAlias 'androiddebugkey'
|
||||||
|
keyPassword 'android'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
// Caution! In production, you need to generate your own keystore file.
|
||||||
|
// see https://facebook.github.io/react-native/docs/signed-apk-android.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
minifyEnabled enableProguardInReleaseBuilds
|
||||||
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// applicationVariants are e.g. debug, release
|
||||||
|
applicationVariants.all { variant ->
|
||||||
|
variant.outputs.each { output ->
|
||||||
|
// For each separate APK per architecture, set a unique version code as described here:
|
||||||
|
// https://developer.android.com/studio/build/configure-apk-splits.html
|
||||||
|
def versionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2]
|
||||||
|
def abi = output.getFilter(OutputFile.ABI)
|
||||||
|
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||||
|
output.versionCodeOverride =
|
||||||
|
defaultConfig.versionCode * 10 + versionCodes.get(abi)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':@react-native-community_async-storage')
|
||||||
|
implementation project(':react-native-camera')
|
||||||
|
implementation project(':react-native-exception-handler')
|
||||||
|
implementation project(':react-native-fast-image')
|
||||||
|
implementation project(':react-native-fs')
|
||||||
|
implementation project(':react-native-gesture-handler')
|
||||||
|
implementation project(':react-native-reanimated')
|
||||||
|
implementation project(':react-native-snackbar')
|
||||||
|
implementation project(':react-native-video')
|
||||||
|
implementation project(':react-native-webview')
|
||||||
|
implementation project(':rn-fetch-blob')
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation 'androidx.media:media:1.0.0'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
|
implementation 'com.facebook.react:react-native:0.61.5'
|
||||||
|
implementation 'com.facebook.fresco:fresco:1.9.0'
|
||||||
|
implementation 'com.facebook.fresco:animated-gif:1.9.0'
|
||||||
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
|
implementation 'com.google.firebase:firebase-analytics:17.2.1'
|
||||||
|
implementation 'com.google.android.gms:play-services-base:17.1.0'
|
||||||
|
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
||||||
|
implementation 'com.facebook.fresco:animated-base-support:1.3.0'
|
||||||
|
implementation 'com.facebook.fresco:animated-gif:1.10.0'
|
||||||
|
implementation 'com.google.firebase:firebase-messaging:20.1.0'
|
||||||
|
|
||||||
|
__32bitImplementation files('libs/lbrysdk-0.61.0-release__arm.aar')
|
||||||
|
__64bitImplementation files('libs/lbrysdk-0.61.0-release__arm64.aar')
|
||||||
|
|
||||||
|
if (enableHermes) {
|
||||||
|
def hermesPath = "../../node_modules/hermes-engine/android/";
|
||||||
|
debugImplementation files(hermesPath + "hermes-debug.aar")
|
||||||
|
releaseImplementation files(hermesPath + "hermes-release.aar")
|
||||||
|
} else {
|
||||||
|
implementation jscFlavor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run this once to be able to run the application with BUCK
|
||||||
|
// puts all compile dependencies into folder libs for BUCK to use
|
||||||
|
task copyDownloadableDepsToLibs(type: Copy) {
|
||||||
|
from configurations.compile
|
||||||
|
into 'libs'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
com.google.gms.googleservices.GoogleServicesPlugin.config.disableVersionCheck = true
|
19
android/app/build_defs.bzl
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
"""Helper definitions to glob .aar and .jar targets"""
|
||||||
|
|
||||||
|
def create_aar_targets(aarfiles):
|
||||||
|
for aarfile in aarfiles:
|
||||||
|
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
|
||||||
|
lib_deps.append(":" + name)
|
||||||
|
android_prebuilt_aar(
|
||||||
|
name = name,
|
||||||
|
aar = aarfile,
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_jar_targets(jarfiles):
|
||||||
|
for jarfile in jarfiles:
|
||||||
|
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
|
||||||
|
lib_deps.append(":" + name)
|
||||||
|
prebuilt_jar(
|
||||||
|
name = name,
|
||||||
|
binary_jar = jarfile,
|
||||||
|
)
|
BIN
android/app/debug.keystore
Normal file
BIN
android/app/libs/lbrysdk-0.61.0-release__arm.aar
Normal file
BIN
android/app/libs/lbrysdk-0.61.0-release__arm64.aar
Normal file
10
android/app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
85
android/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="io.lbry.browser"
|
||||||
|
android:installLocation="auto">
|
||||||
|
|
||||||
|
<supports-screens
|
||||||
|
android:smallScreens="true"
|
||||||
|
android:normalScreens="true"
|
||||||
|
android:largeScreens="true"
|
||||||
|
android:anyDensity="true"
|
||||||
|
android:xlargeScreens="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<application android:label="@string/app_name"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:theme="@style/LbryAppTheme"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||||
|
android:resource="@drawable/ic_lbry" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_color"
|
||||||
|
android:resource="@color/lbryGreen" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
|
android:value="@string/default_notification_channel_id"/>
|
||||||
|
|
||||||
|
<meta-data android:name="wakelock" android:value="0"/>
|
||||||
|
|
||||||
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||||
|
|
||||||
|
<activity android:name="io.lbry.browser.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/LbryAppTheme"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="lbry" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<receiver android:name="io.lbry.browser.receivers.NotificationDeletedReceiver" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="io.lbry.browser.LbrynetMessagingService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="io.lbry.browser.LocalFileProvider"
|
||||||
|
android:authorities="io.lbry.browser.fileprovider"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/filepaths" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
BIN
android/app/src/main/assets/fonts/Feather.ttf
Normal file
BIN
android/app/src/main/assets/fonts/FontAwesome.ttf
Normal file
BIN
android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf
Normal file
BIN
android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf
Normal file
BIN
android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Inter-Bold.otf
Normal file
BIN
android/app/src/main/assets/fonts/Inter-Medium.otf
Normal file
BIN
android/app/src/main/assets/fonts/Inter-Regular.otf
Normal file
BIN
android/app/src/main/assets/fonts/Inter-SemiBold.otf
Normal file
1406
android/app/src/main/assets/index.android.bundle
Normal file
419
android/app/src/main/java/io/lbry/browser/DownloadManager.java
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
package io.lbry.browser;
|
||||||
|
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
import io.lbry.browser.receivers.NotificationDeletedReceiver;
|
||||||
|
import io.lbry.lbrysdk.LbrynetService;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class DownloadManager {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
private List<String> activeDownloads = new ArrayList<String>();
|
||||||
|
|
||||||
|
private List<String> completedDownloads = new ArrayList<String>();
|
||||||
|
|
||||||
|
private Map<String, String> downloadIdOutpointsMap = new HashMap<String, String>();
|
||||||
|
|
||||||
|
// maintain a map of uris to writtenBytes, so that we check if it's changed and don't flood RN with update events every 500ms
|
||||||
|
private Map<String, Double> writtenDownloadBytes = new HashMap<String, Double>();
|
||||||
|
|
||||||
|
private HashMap<Integer, NotificationCompat.Builder> builders = new HashMap<Integer, NotificationCompat.Builder>();
|
||||||
|
|
||||||
|
private HashMap<String, Integer> downloadIdNotificationIdMap = new HashMap<String, Integer>();
|
||||||
|
|
||||||
|
private HashMap<String, Boolean> stoppedDownloadsMap = new HashMap<String, Boolean>();
|
||||||
|
|
||||||
|
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#");
|
||||||
|
|
||||||
|
private static final int MAX_FILENAME_LENGTH = 20;
|
||||||
|
|
||||||
|
private static final int MAX_PROGRESS = 100;
|
||||||
|
|
||||||
|
private static final String GROUP_DOWNLOADS = "io.lbry.browser.GROUP_DOWNLOADS";
|
||||||
|
|
||||||
|
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.DOWNLOADS_NOTIFICATION_CHANNEL";
|
||||||
|
|
||||||
|
private static boolean channelCreated = false;
|
||||||
|
|
||||||
|
private static NotificationCompat.Builder groupBuilder = null;
|
||||||
|
|
||||||
|
public static final String NOTIFICATION_ID_KEY = "io.lbry.browser.notificationId";
|
||||||
|
|
||||||
|
public static final String ACTION_DOWNLOAD_EVENT = "io.lbry.browser.ACTION_DOWNLOAD_EVENT";
|
||||||
|
|
||||||
|
public static final String ACTION_START = "start";
|
||||||
|
|
||||||
|
public static final String ACTION_COMPLETE = "complete";
|
||||||
|
|
||||||
|
public static final String ACTION_UPDATE = "update";
|
||||||
|
|
||||||
|
public static final int DOWNLOAD_NOTIFICATION_GROUP_ID = 20;
|
||||||
|
|
||||||
|
public static boolean groupCreated = false;
|
||||||
|
|
||||||
|
public DownloadManager(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int generateNotificationId() {
|
||||||
|
int id = 0;
|
||||||
|
Random random = new Random();
|
||||||
|
do {
|
||||||
|
id = random.nextInt();
|
||||||
|
} while (id < 1000);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNotificationChannel() {
|
||||||
|
// Only applies to Android 8.0 Oreo (API Level 26) or higher
|
||||||
|
if (!channelCreated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_ID, "LBRY Downloads", NotificationManager.IMPORTANCE_LOW);
|
||||||
|
channel.setDescription("LBRY file downloads");
|
||||||
|
channel.setSound(null, null);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNotificationGroup() {
|
||||||
|
if (!groupCreated) {
|
||||||
|
Intent intent = new Intent(context, NotificationDeletedReceiver.class);
|
||||||
|
intent.putExtra(NOTIFICATION_ID_KEY, DOWNLOAD_NOTIFICATION_GROUP_ID);
|
||||||
|
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, DOWNLOAD_NOTIFICATION_GROUP_ID, intent, 0);
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
groupBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
|
groupBuilder.setContentTitle("Active LBRY downloads")
|
||||||
|
// contentText will be displayed if there are no notifications in the group
|
||||||
|
.setContentText("There are no active LBRY downloads.")
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.setGroup(GROUP_DOWNLOADS)
|
||||||
|
.setGroupSummary(true)
|
||||||
|
.setDeleteIntent(pendingIntent);
|
||||||
|
notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build());
|
||||||
|
|
||||||
|
groupCreated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PendingIntent getLaunchPendingIntent(String uri, Context context) {
|
||||||
|
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
|
||||||
|
launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
PendingIntent intent = PendingIntent.getActivity(context, 0, launchIntent, 0);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateWrittenBytesForDownload(String id, double writtenBytes) {
|
||||||
|
if (!writtenDownloadBytes.containsKey(id)) {
|
||||||
|
writtenDownloadBytes.put(id, writtenBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getWrittenBytesForDownload(String id) {
|
||||||
|
if (writtenDownloadBytes.containsKey(id)) {
|
||||||
|
return writtenDownloadBytes.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearWrittenBytesForDownload(String id) {
|
||||||
|
if (writtenDownloadBytes.containsKey(id)) {
|
||||||
|
writtenDownloadBytes.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent getDeleteDownloadIntent(String uri) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(LbrynetService.ACTION_DELETE_DOWNLOAD);
|
||||||
|
intent.putExtra("uri", uri);
|
||||||
|
intent.putExtra("nativeDelete", true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startDownload(String id, String filename, String outpoint) {
|
||||||
|
if (filename == null || filename.trim().length() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
if (!isDownloadActive(id)) {
|
||||||
|
activeDownloads.add(id);
|
||||||
|
downloadIdOutpointsMap.put(id, outpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
createNotificationChannel();
|
||||||
|
createNotificationGroup();
|
||||||
|
|
||||||
|
PendingIntent stopDownloadIntent = PendingIntent.getBroadcast(context, 0, getDeleteDownloadIntent(id), PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
|
// The file URI is used as the unique ID
|
||||||
|
builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
|
||||||
|
.setContentIntent(getLaunchPendingIntent(id, context))
|
||||||
|
.setContentTitle(String.format("Downloading %s", truncateFilename(filename)))
|
||||||
|
.setGroup(GROUP_DOWNLOADS)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.setProgress(MAX_PROGRESS, 0, false)
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
|
.setOngoing(true)
|
||||||
|
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopDownloadIntent);
|
||||||
|
|
||||||
|
int notificationId = getNotificationId(id);
|
||||||
|
downloadIdNotificationIdMap.put(id, notificationId);
|
||||||
|
builders.put(notificationId, builder);
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
|
||||||
|
if (groupCreated && groupBuilder != null) {
|
||||||
|
groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
|
||||||
|
notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateDownload(String id, String filename, double writtenBytes, double totalBytes) {
|
||||||
|
if (filename == null || filename.trim().length() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
createNotificationChannel();
|
||||||
|
createNotificationGroup();
|
||||||
|
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
NotificationCompat.Builder builder = null;
|
||||||
|
int notificationId = getNotificationId(id);
|
||||||
|
if (builders.containsKey(notificationId)) {
|
||||||
|
builder = builders.get(notificationId);
|
||||||
|
} else {
|
||||||
|
PendingIntent stopDownloadIntent = PendingIntent.getBroadcast(context, 0, getDeleteDownloadIntent(id), PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
|
builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
|
||||||
|
.setContentTitle(String.format("Downloading %s", truncateFilename(filename)))
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.setOngoing(true)
|
||||||
|
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopDownloadIntent);
|
||||||
|
builders.put(notificationId, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
double progress = (writtenBytes / totalBytes) * 100;
|
||||||
|
builder.setContentIntent(getLaunchPendingIntent(id, context))
|
||||||
|
.setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes)))
|
||||||
|
.setGroup(GROUP_DOWNLOADS)
|
||||||
|
.setProgress(MAX_PROGRESS, new Double(progress).intValue(), false)
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download);
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
|
||||||
|
if (progress >= MAX_PROGRESS) {
|
||||||
|
builder.setContentTitle(String.format("Downloaded %s", truncateFilename(filename, 30)))
|
||||||
|
.setContentText(String.format("%s", formatBytes(totalBytes)))
|
||||||
|
.setGroup(GROUP_DOWNLOADS)
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
.setOngoing(false);
|
||||||
|
builder.mActions.clear();
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
|
||||||
|
if (downloadIdNotificationIdMap.containsKey(id)) {
|
||||||
|
downloadIdNotificationIdMap.remove(id);
|
||||||
|
}
|
||||||
|
if (builders.containsKey(notificationId)) {
|
||||||
|
builders.remove(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no more downloads and the group exists, set the icon to stop animating
|
||||||
|
if (groupCreated && groupBuilder != null && downloadIdNotificationIdMap.size() == 0) {
|
||||||
|
groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
|
||||||
|
notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
completeDownload(id, filename, totalBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void completeDownload(String id, String filename, double totalBytes) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (isDownloadActive(id)) {
|
||||||
|
activeDownloads.remove(id);
|
||||||
|
}
|
||||||
|
if (!isDownloadCompleted(id)) {
|
||||||
|
completedDownloads.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
NotificationCompat.Builder builder = null;
|
||||||
|
int notificationId = getNotificationId(id);
|
||||||
|
if (builders.containsKey(notificationId)) {
|
||||||
|
builder = builders.get(notificationId);
|
||||||
|
} else {
|
||||||
|
builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
|
builder.setPriority(NotificationCompat.PRIORITY_LOW);
|
||||||
|
builders.put(notificationId, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setContentTitle(String.format("Downloaded %s", truncateFilename(filename, 30)))
|
||||||
|
.setContentText(String.format("%s", formatBytes(totalBytes)))
|
||||||
|
.setGroup(GROUP_DOWNLOADS)
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
.setOngoing(false);
|
||||||
|
builder.mActions.clear();
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
|
||||||
|
// If there are no more downloads and the group exists, set the icon to stop animating
|
||||||
|
checkGroupDownloadIcon(notificationManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void abortDownload(String id) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (downloadIdNotificationIdMap.containsKey(id)) {
|
||||||
|
removeDownloadNotification(id);
|
||||||
|
}
|
||||||
|
activeDownloads.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDownloadActive(String id) {
|
||||||
|
return (activeDownloads.contains(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDownloadCompleted(String id) {
|
||||||
|
return (completedDownloads.contains(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasActiveDownloads() {
|
||||||
|
return activeDownloads.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getActiveDownloads() {
|
||||||
|
return activeDownloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCompletedDownloads() {
|
||||||
|
return completedDownloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOutpointForDownload(String uri) {
|
||||||
|
if (downloadIdOutpointsMap.containsKey(uri)) {
|
||||||
|
return downloadIdOutpointsMap.get(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteDownloadUri(String uri) {
|
||||||
|
synchronized (this) {
|
||||||
|
activeDownloads.remove(uri);
|
||||||
|
completedDownloads.remove(uri);
|
||||||
|
|
||||||
|
if (downloadIdOutpointsMap.containsKey(uri)) {
|
||||||
|
downloadIdOutpointsMap.remove(uri);
|
||||||
|
}
|
||||||
|
if (downloadIdNotificationIdMap.containsKey(uri)) {
|
||||||
|
removeDownloadNotification(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeDownloadNotification(String id) {
|
||||||
|
int notificationId = downloadIdNotificationIdMap.get(id);
|
||||||
|
if (downloadIdNotificationIdMap.containsKey(id)) {
|
||||||
|
downloadIdNotificationIdMap.remove(id);
|
||||||
|
}
|
||||||
|
if (builders.containsKey(notificationId)) {
|
||||||
|
builders.remove(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
NotificationCompat.Builder builder = builders.get(notificationId);
|
||||||
|
notificationManager.cancel(notificationId);
|
||||||
|
|
||||||
|
checkGroupDownloadIcon(notificationManager);
|
||||||
|
if (builders.values().size() == 0) {
|
||||||
|
notificationManager.cancel(DOWNLOAD_NOTIFICATION_GROUP_ID);
|
||||||
|
groupCreated = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNotificationId(String id) {
|
||||||
|
if (downloadIdNotificationIdMap.containsKey(id)) {
|
||||||
|
return downloadIdNotificationIdMap.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
int notificationId = generateNotificationId();
|
||||||
|
if (MainActivity.downloadNotificationIds != null &&
|
||||||
|
!MainActivity.downloadNotificationIds.contains(notificationId)) {
|
||||||
|
MainActivity.downloadNotificationIds.add(notificationId);
|
||||||
|
}
|
||||||
|
downloadIdNotificationIdMap.put(id, notificationId);
|
||||||
|
return notificationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkGroupDownloadIcon(NotificationManagerCompat notificationManager) {
|
||||||
|
if (groupCreated && groupBuilder != null && downloadIdNotificationIdMap.size() == 0) {
|
||||||
|
groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
|
||||||
|
notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatBytes(double bytes)
|
||||||
|
{
|
||||||
|
if (bytes < 1048576) { // < 1MB
|
||||||
|
return String.format("%s KB", DECIMAL_FORMAT.format(bytes / 1024.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes < 1073741824) { // < 1GB
|
||||||
|
return String.format("%s MB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.format("%s GB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0 * 1024.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String truncateFilename(String filename, int alternateMaxLength) {
|
||||||
|
int maxLength = alternateMaxLength > 0 ? alternateMaxLength : MAX_FILENAME_LENGTH;
|
||||||
|
if (filename.length() < maxLength) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the extension
|
||||||
|
int dotIndex = filename.lastIndexOf(".");
|
||||||
|
if (dotIndex > -1) {
|
||||||
|
String extension = filename.substring(dotIndex);
|
||||||
|
return String.format("%s...%s", filename.substring(0, maxLength - extension.length() - 4), extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.format("%s...", filename.substring(0, maxLength - 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String truncateFilename(String filename) {
|
||||||
|
return truncateFilename(filename, 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package io.lbry.browser;
|
||||||
|
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.media.RingtoneManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||||
|
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||||
|
import com.google.firebase.messaging.RemoteMessage;
|
||||||
|
|
||||||
|
import io.lbry.browser.reactmodules.UtilityModule;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class LbrynetMessagingService extends FirebaseMessagingService {
|
||||||
|
|
||||||
|
private static final String TAG = "LbrynetMessagingService";
|
||||||
|
|
||||||
|
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.LBRY_ENGAGEMENT_CHANNEL";
|
||||||
|
|
||||||
|
private static final String TYPE_SUBSCRIPTION = "subscription";
|
||||||
|
|
||||||
|
private static final String TYPE_REWARD = "reward";
|
||||||
|
|
||||||
|
private static final String TYPE_INTERESTS = "interests";
|
||||||
|
|
||||||
|
private static final String TYPE_CREATOR = "creator";
|
||||||
|
|
||||||
|
private FirebaseAnalytics firebaseAnalytics;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(RemoteMessage remoteMessage) {
|
||||||
|
Log.d(TAG, "From: " + remoteMessage.getFrom());
|
||||||
|
if (firebaseAnalytics == null) {
|
||||||
|
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> payload = remoteMessage.getData();
|
||||||
|
if (payload != null) {
|
||||||
|
String type = payload.get("type");
|
||||||
|
String url = payload.get("target");
|
||||||
|
String title = payload.get("title");
|
||||||
|
String body = payload.get("body");
|
||||||
|
String name = payload.get("name"); // notification name
|
||||||
|
if (type != null && getEnabledTypes().indexOf(type) > -1 && body != null && body.trim().length() > 0) {
|
||||||
|
// only log the receive event for valid notifications received
|
||||||
|
if (firebaseAnalytics != null) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString("name", name);
|
||||||
|
firebaseAnalytics.logEvent("lbry_notification_receive", bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNotification(title, body, type, url, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewToken(String token) {
|
||||||
|
Log.d(TAG, "Refreshed token: " + token);
|
||||||
|
|
||||||
|
// If you want to send messages to this application instance or
|
||||||
|
// manage this apps subscriptions on the server side, send the
|
||||||
|
// Instance ID token to your app server.
|
||||||
|
sendRegistrationToServer(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist token to third-party servers.
|
||||||
|
*
|
||||||
|
* Modify this method to associate the user's FCM InstanceID token with any server-side account
|
||||||
|
* maintained by your application.
|
||||||
|
*
|
||||||
|
* @param token The new token.
|
||||||
|
*/
|
||||||
|
private void sendRegistrationToServer(String token) {
|
||||||
|
// TODO: Implement this method to send token to your app server.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and show a simple notification containing the received FCM message.
|
||||||
|
*
|
||||||
|
* @param messageBody FCM message body received.
|
||||||
|
*/
|
||||||
|
private void sendNotification(String title, String messageBody, String type, String url, String name) {
|
||||||
|
//Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
if (url == null) {
|
||||||
|
if (TYPE_REWARD.equals(type)) {
|
||||||
|
url = "lbry://?rewards";
|
||||||
|
} else {
|
||||||
|
// default to home page
|
||||||
|
url = "lbry://?discover";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||||
|
launchIntent.putExtra("notification_name", name);
|
||||||
|
launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, launchIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||||
|
|
||||||
|
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||||
|
NotificationCompat.Builder notificationBuilder =
|
||||||
|
new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||||
|
.setColor(ContextCompat.getColor(this, R.color.lbryGreen))
|
||||||
|
.setSmallIcon(R.drawable.ic_lbry)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(messageBody)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setSound(defaultSoundUri)
|
||||||
|
.setContentIntent(pendingIntent);
|
||||||
|
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
// Since android Oreo notification channel is needed.
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_ID, "LBRY Engagement", NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.notify(9898, notificationBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getEnabledTypes() {
|
||||||
|
SharedPreferences sp = getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
List<String> enabledTypes = new ArrayList<String>();
|
||||||
|
|
||||||
|
if (sp.getBoolean(UtilityModule.RECEIVE_SUBSCRIPTION_NOTIFICATIONS, true)) {
|
||||||
|
enabledTypes.add(TYPE_SUBSCRIPTION);
|
||||||
|
}
|
||||||
|
if (sp.getBoolean(UtilityModule.RECEIVE_REWARD_NOTIFICATIONS, true)) {
|
||||||
|
enabledTypes.add(TYPE_REWARD);
|
||||||
|
}
|
||||||
|
if (sp.getBoolean(UtilityModule.RECEIVE_INTERESTS_NOTIFICATIONS, true)) {
|
||||||
|
enabledTypes.add(TYPE_INTERESTS);
|
||||||
|
}
|
||||||
|
if (sp.getBoolean(UtilityModule.RECEIVE_CREATOR_NOTIFICATIONS, true)) {
|
||||||
|
enabledTypes.add(TYPE_CREATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return enabledTypes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package io.lbry.browser;
|
||||||
|
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
|
public class LocalFileProvider extends FileProvider {
|
||||||
|
|
||||||
|
}
|
877
android/app/src/main/java/io/lbry/browser/MainActivity.java
Normal file
|
@ -0,0 +1,877 @@
|
||||||
|
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;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
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 androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import android.telephony.SmsMessage;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.azendoo.reactnativesnackbar.SnackbarPackage;
|
||||||
|
import com.brentvatne.react.ReactVideoPackage;
|
||||||
|
import com.dylanvann.fastimage.FastImageViewPackage;
|
||||||
|
import com.facebook.react.common.LifecycleState;
|
||||||
|
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||||
|
import com.facebook.react.ReactRootView;
|
||||||
|
import com.facebook.react.ReactInstanceManager;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReactContext;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.bridge.ReadableNativeArray;
|
||||||
|
import com.facebook.react.bridge.ReadableNativeMap;
|
||||||
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
|
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||||
|
import com.facebook.react.modules.core.PermissionListener;
|
||||||
|
import com.facebook.react.shell.MainReactPackage;
|
||||||
|
import com.facebook.react.ReactRootView;
|
||||||
|
import com.facebook.soloader.SoLoader;
|
||||||
|
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||||
|
import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
|
||||||
|
import com.reactnativecommunity.webview.RNCWebViewPackage;
|
||||||
|
import com.rnfs.RNFSPackage;
|
||||||
|
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
||||||
|
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
|
||||||
|
import com.swmansion.reanimated.ReanimatedPackage;
|
||||||
|
import com.RNFetchBlob.RNFetchBlobPackage;
|
||||||
|
|
||||||
|
import io.lbry.browser.reactpackages.LbryReactPackage;
|
||||||
|
import io.lbry.browser.reactmodules.BackgroundMediaModule;
|
||||||
|
import io.lbry.lbrysdk.LbrynetService;
|
||||||
|
import io.lbry.lbrysdk.ServiceHelper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.reactnative.camera.RNCameraPackage;
|
||||||
|
|
||||||
|
public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
|
||||||
|
|
||||||
|
private static Activity currentActivity = null;
|
||||||
|
|
||||||
|
private static final int OVERLAY_PERMISSION_REQ_CODE = 101;
|
||||||
|
|
||||||
|
private static final int STORAGE_PERMISSION_REQ_CODE = 201;
|
||||||
|
|
||||||
|
private static final int PHONE_STATE_PERMISSION_REQ_CODE = 202;
|
||||||
|
|
||||||
|
private static final int RECEIVE_SMS_PERMISSION_REQ_CODE = 203;
|
||||||
|
|
||||||
|
public static final int DOCUMENT_PICKER_RESULT_CODE = 301;
|
||||||
|
|
||||||
|
private BroadcastReceiver notificationsReceiver;
|
||||||
|
|
||||||
|
private BroadcastReceiver smsReceiver;
|
||||||
|
|
||||||
|
private BroadcastReceiver stopServiceReceiver;
|
||||||
|
|
||||||
|
private BroadcastReceiver downloadEventReceiver;
|
||||||
|
|
||||||
|
private FirebaseAnalytics firebaseAnalytics;
|
||||||
|
|
||||||
|
private ReactRootView mReactRootView;
|
||||||
|
|
||||||
|
private ReactInstanceManager mReactInstanceManager;
|
||||||
|
|
||||||
|
public static final String SHARED_PREFERENCES_NAME = "LBRY";
|
||||||
|
|
||||||
|
public static final String SALT_KEY = "salt";
|
||||||
|
|
||||||
|
public static final String DEVICE_ID_KEY = "deviceId";
|
||||||
|
|
||||||
|
public static final String SOURCE_NOTIFICATION_ID_KEY = "sourceNotificationId";
|
||||||
|
|
||||||
|
public static final String SETTING_KEEP_DAEMON_RUNNING = "keepDaemonRunning";
|
||||||
|
|
||||||
|
public static List<Integer> downloadNotificationIds = new ArrayList<Integer>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag which indicates whether or not the service is running. Will be updated in the
|
||||||
|
* onResume method.
|
||||||
|
*/
|
||||||
|
private boolean serviceRunning;
|
||||||
|
|
||||||
|
private boolean receivedStopService;
|
||||||
|
|
||||||
|
private PermissionListener permissionListener;
|
||||||
|
|
||||||
|
protected String getMainComponentName() {
|
||||||
|
return "LBRYApp";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LaunchTiming CurrentLaunchTiming;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
CurrentLaunchTiming = new LaunchTiming(new Date());
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
currentActivity = this;
|
||||||
|
|
||||||
|
SoLoader.init(this, false);
|
||||||
|
|
||||||
|
// Register the stop service receiver (so that we close the activity if the user requests the service to stop)
|
||||||
|
registerStopReceiver();
|
||||||
|
|
||||||
|
// Register SMS receiver for handling verification texts
|
||||||
|
registerSmsReceiver();
|
||||||
|
|
||||||
|
// Register the receiver to emit download events
|
||||||
|
registerDownloadEventReceiver();
|
||||||
|
|
||||||
|
// Start the daemon service if it is not started
|
||||||
|
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||||
|
if (!serviceRunning) {
|
||||||
|
CurrentLaunchTiming.setColdStart(true);
|
||||||
|
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNotificationOpenIntent(getIntent());
|
||||||
|
|
||||||
|
mReactRootView = new RNGestureHandlerEnabledRootView(this);
|
||||||
|
mReactInstanceManager = ReactInstanceManager.builder()
|
||||||
|
.setApplication(getApplication())
|
||||||
|
.setCurrentActivity(this)
|
||||||
|
.setBundleAssetName("index.android.bundle")
|
||||||
|
.setJSMainModulePath("index")
|
||||||
|
.addPackage(new MainReactPackage())
|
||||||
|
.addPackage(new AsyncStoragePackage())
|
||||||
|
.addPackage(new FastImageViewPackage())
|
||||||
|
.addPackage(new RNCWebViewPackage())
|
||||||
|
.addPackage(new ReactVideoPackage())
|
||||||
|
.addPackage(new ReanimatedPackage())
|
||||||
|
.addPackage(new RNCameraPackage())
|
||||||
|
.addPackage(new RNFetchBlobPackage())
|
||||||
|
.addPackage(new RNFSPackage())
|
||||||
|
.addPackage(new RNGestureHandlerPackage())
|
||||||
|
.addPackage(new SnackbarPackage())
|
||||||
|
.addPackage(new LbryReactPackage())
|
||||||
|
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||||
|
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||||
|
.build();
|
||||||
|
mReactRootView.startReactApplication(mReactInstanceManager, "LBRYApp", null);
|
||||||
|
|
||||||
|
registerNotificationsReceiver();
|
||||||
|
|
||||||
|
setContentView(mReactRootView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNotificationOpenIntent(Intent intent) {
|
||||||
|
if (intent != null) {
|
||||||
|
String notificationName = intent.getStringExtra("notification_name");
|
||||||
|
if (notificationName != null) {
|
||||||
|
logNotificationOpen(notificationName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logNotificationOpen(String name) {
|
||||||
|
if (firebaseAnalytics == null) {
|
||||||
|
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString("name", name);
|
||||||
|
firebaseAnalytics.logEvent("lbry_notification_open", bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerDownloadEventReceiver() {
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_EVENT);
|
||||||
|
downloadEventReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String downloadAction = intent.getStringExtra("action");
|
||||||
|
String uri = intent.getStringExtra("uri");
|
||||||
|
String outpoint = intent.getStringExtra("outpoint");
|
||||||
|
String fileInfoJson = intent.getStringExtra("file_info");
|
||||||
|
|
||||||
|
|
||||||
|
if (uri == null || outpoint == null || (fileInfoJson == null && !"abort".equals(downloadAction))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String eventName = null;
|
||||||
|
WritableMap params = Arguments.createMap();
|
||||||
|
params.putString("uri", uri);
|
||||||
|
params.putString("outpoint", outpoint);
|
||||||
|
|
||||||
|
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
|
||||||
|
if ("abort".equals(downloadAction)) {
|
||||||
|
eventName = "onDownloadAborted";
|
||||||
|
if (reactContext != null) {
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(fileInfoJson);
|
||||||
|
WritableMap fileInfo = JSONObjectToMap(json);
|
||||||
|
params.putMap("fileInfo", fileInfo);
|
||||||
|
|
||||||
|
if (DownloadManager.ACTION_UPDATE.equals(downloadAction)) {
|
||||||
|
double progress = intent.getDoubleExtra("progress", 0);
|
||||||
|
params.putDouble("progress", progress);
|
||||||
|
eventName = "onDownloadUpdated";
|
||||||
|
} else {
|
||||||
|
eventName = (DownloadManager.ACTION_START.equals(downloadAction)) ? "onDownloadStarted" : "onDownloadCompleted";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reactContext != null) {
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
|
||||||
|
}
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
registerReceiver(downloadEventReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerStopReceiver() {
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(LbrynetService.ACTION_STOP_SERVICE);
|
||||||
|
stopServiceReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
MainActivity.this.receivedStopService = true;
|
||||||
|
MainActivity.this.finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
registerReceiver(stopServiceReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerNotificationsReceiver() {
|
||||||
|
// Background media receiver
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(BackgroundMediaModule.ACTION_PLAY);
|
||||||
|
filter.addAction(BackgroundMediaModule.ACTION_PAUSE);
|
||||||
|
notificationsReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
|
||||||
|
if (reactContext != null) {
|
||||||
|
if (BackgroundMediaModule.ACTION_PLAY.equals(action)) {
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
.emit("onBackgroundPlayPressed", null);
|
||||||
|
}
|
||||||
|
if (BackgroundMediaModule.ACTION_PAUSE.equals(action)) {
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
.emit("onBackgroundPausePressed", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
registerReceiver(notificationsReceiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerSmsReceiver() {
|
||||||
|
if (!hasPermission(Manifest.permission.RECEIVE_SMS, this)) {
|
||||||
|
// don't create the receiver if we don't have the read sms permission
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntentFilter smsFilter = new IntentFilter();
|
||||||
|
smsFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
|
||||||
|
smsReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
// Get the message
|
||||||
|
Bundle bundle = intent.getExtras();
|
||||||
|
if (bundle != null) {
|
||||||
|
Object[] pdus = (Object[]) bundle.get("pdus");
|
||||||
|
if (pdus != null && pdus.length > 0) {
|
||||||
|
SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdus[0]);
|
||||||
|
String text = sms.getMessageBody();
|
||||||
|
if (text == null || text.trim().length() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve verification code from the text message if it contains
|
||||||
|
// the strings "lbry", "verification code" and the colon (following the expected format)
|
||||||
|
text = text.toLowerCase();
|
||||||
|
if (text.indexOf("lbry") > -1 && text.indexOf("verification code") > -1 && text.indexOf(":") > -1) {
|
||||||
|
String code = text.substring(text.lastIndexOf(":") + 1).trim();
|
||||||
|
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
|
||||||
|
if (reactContext != null) {
|
||||||
|
WritableMap params = Arguments.createMap();
|
||||||
|
params.putString("code", code);
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
.emit("onVerificationCodeReceived", params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
registerReceiver(smsReceiver, smsFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (!Settings.canDrawOverlays(this)) {
|
||||||
|
// SYSTEM_ALERT_WINDOW permission not granted...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestCode == DOCUMENT_PICKER_RESULT_CODE) {
|
||||||
|
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
|
||||||
|
if (reactContext != null) {
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
Uri fileUri = data.getData();
|
||||||
|
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)
|
||||||
|
.emit("onDocumentPickerCanceled", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Activity getActivity() {
|
||||||
|
Activity activity = new Activity();
|
||||||
|
activity = currentActivity;
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
|
||||||
|
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
|
||||||
|
switch (requestCode) {
|
||||||
|
case STORAGE_PERMISSION_REQ_CODE:
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
if (BuildConfig.DEBUG && !Settings.canDrawOverlays(this)) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||||
|
Uri.parse("package:" + getPackageName()));
|
||||||
|
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
|
||||||
|
}
|
||||||
|
if (reactContext != null) {
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
.emit("onStoragePermissionGranted", null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Permission not granted
|
||||||
|
/*Toast.makeText(this,
|
||||||
|
"LBRY requires access to your device storage to be able to download files and media." +
|
||||||
|
" Please enable the storage permission and restart the app.", Toast.LENGTH_LONG).show();*/
|
||||||
|
if (reactContext != null) {
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
.emit("onStoragePermissionRefused", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PHONE_STATE_PERMISSION_REQ_CODE:
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Permission granted. Emit an onPhoneStatePermissionGranted event
|
||||||
|
if (reactContext != null) {
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
.emit("onPhoneStatePermissionGranted", null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Permission not granted. Simply show a message.
|
||||||
|
Toast.makeText(this,
|
||||||
|
"No permission granted to read your device state. Rewards cannot be claimed.", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RECEIVE_SMS_PERMISSION_REQ_CODE:
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Permission granted. Emit an onPhoneStatePermissionGranted event
|
||||||
|
if (reactContext != null) {
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||||
|
.emit("onReceiveSmsPermissionGranted", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the receiver
|
||||||
|
if (smsReceiver == null) {
|
||||||
|
registerSmsReceiver();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Permission not granted. Simply show a message.
|
||||||
|
Toast.makeText(this,
|
||||||
|
"No permission granted to receive your SMS messages. You may have to enter the verification code manually.",
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionListener != null) {
|
||||||
|
permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String acquireDeviceId(Context context) {
|
||||||
|
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||||
|
String id = null;
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
id = telephonyManager.getImei(); // GSM
|
||||||
|
if (id == null) {
|
||||||
|
id = telephonyManager.getMeid(); // CDMA
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = telephonyManager.getDeviceId();
|
||||||
|
}
|
||||||
|
} catch (SecurityException ex) {
|
||||||
|
// Maybe the permission was not granted? Try to acquire permission
|
||||||
|
checkPhoneStatePermission(context);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// id could not be obtained. Display a warning that rewards cannot be claimed.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == null || id.trim().length() == 0) {
|
||||||
|
Toast.makeText(context, "Rewards cannot be claimed because we could not identify your device.", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = sp.edit();
|
||||||
|
editor.putString(DEVICE_ID_KEY, id);
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invokeDefaultOnBackPressed() {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
|
||||||
|
if (mReactInstanceManager != null) {
|
||||||
|
mReactInstanceManager.onHostPause(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
SharedPreferences sp = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||||
|
if (!serviceRunning) {
|
||||||
|
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mReactInstanceManager != null) {
|
||||||
|
mReactInstanceManager.onHostResume(this, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
// check service running setting and end it here
|
||||||
|
SharedPreferences sp = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
boolean shouldKeepDaemonRunning = sp.getBoolean(SETTING_KEEP_DAEMON_RUNNING, true);
|
||||||
|
if (!shouldKeepDaemonRunning) {
|
||||||
|
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||||
|
if (serviceRunning) {
|
||||||
|
ServiceHelper.stop(this, LbrynetService.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notificationsReceiver != null) {
|
||||||
|
unregisterReceiver(notificationsReceiver);
|
||||||
|
notificationsReceiver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smsReceiver != null) {
|
||||||
|
unregisterReceiver(smsReceiver);
|
||||||
|
smsReceiver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadEventReceiver != null) {
|
||||||
|
unregisterReceiver(downloadEventReceiver);
|
||||||
|
downloadEventReceiver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stopServiceReceiver != null) {
|
||||||
|
unregisterReceiver(stopServiceReceiver);
|
||||||
|
stopServiceReceiver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||||
|
notificationManager.cancel(BackgroundMediaModule.NOTIFICATION_ID);
|
||||||
|
notificationManager.cancel(DownloadManager.DOWNLOAD_NOTIFICATION_GROUP_ID);
|
||||||
|
if (downloadNotificationIds != null) {
|
||||||
|
for (int i = 0; i < downloadNotificationIds.size(); i++) {
|
||||||
|
notificationManager.cancel(downloadNotificationIds.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (receivedStopService || !isServiceRunning(LbrynetService.class)) {
|
||||||
|
notificationManager.cancelAll();
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
if (mReactInstanceManager != null) {
|
||||||
|
mReactInstanceManager.onHostDestroy(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (mReactInstanceManager != null) {
|
||||||
|
mReactInstanceManager.onBackPressed();
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
|
||||||
|
permissionListener = listener;
|
||||||
|
ActivityCompat.requestPermissions(this, permissions, requestCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
if (mReactInstanceManager != null) {
|
||||||
|
mReactInstanceManager.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intent != null) {
|
||||||
|
int sourceNotificationId = intent.getIntExtra(SOURCE_NOTIFICATION_ID_KEY, -1);
|
||||||
|
if (sourceNotificationId > -1) {
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||||
|
notificationManager.cancel(sourceNotificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNotificationOpenIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkPermission(String permission, int requestCode, String rationale, Context context, boolean forceRequest) {
|
||||||
|
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Should we show an explanation?
|
||||||
|
if (!forceRequest && ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission)) {
|
||||||
|
Toast.makeText(context, rationale, Toast.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
ActivityCompat.requestPermissions((Activity) context, new String[] { permission }, requestCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkPermission(String permission, int requestCode, String rationale, Context context) {
|
||||||
|
checkPermission(permission, requestCode, rationale, context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasPermission(String permission, Context context) {
|
||||||
|
return (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkPhoneStatePermission(Context context) {
|
||||||
|
// Request read phone state permission
|
||||||
|
checkPermission(Manifest.permission.READ_PHONE_STATE,
|
||||||
|
PHONE_STATE_PERMISSION_REQ_CODE,
|
||||||
|
"LBRY requires optional access to be able to identify your device for rewards. " +
|
||||||
|
"You cannot claim rewards without this permission.",
|
||||||
|
context,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkReceiveSmsPermission(Context context) {
|
||||||
|
// Request read phone state permission
|
||||||
|
checkPermission(Manifest.permission.RECEIVE_SMS,
|
||||||
|
RECEIVE_SMS_PERMISSION_REQ_CODE,
|
||||||
|
"LBRY requires access to be able to read a verification text message for rewards.",
|
||||||
|
context,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkStoragePermission(Context context) {
|
||||||
|
// Request read phone state permission
|
||||||
|
checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
STORAGE_PERMISSION_REQ_CODE,
|
||||||
|
"LBRY requires access to your device storage to be able to download files and media.",
|
||||||
|
context,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isServiceRunning(Class<?> serviceClass) {
|
||||||
|
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||||
|
if (serviceClass.getName().equals(serviceInfo.service.getClassName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WritableMap JSONObjectToMap(JSONObject jsonObject) throws JSONException {
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
Iterator<String> keys = jsonObject.keys();
|
||||||
|
while(keys.hasNext()) {
|
||||||
|
String key = keys.next();
|
||||||
|
Object value = jsonObject.get(key);
|
||||||
|
if (value instanceof JSONArray) {
|
||||||
|
map.putArray(key, JSONArrayToList((JSONArray) value));
|
||||||
|
} else if (value instanceof JSONObject) {
|
||||||
|
map.putMap(key, JSONObjectToMap((JSONObject) value));
|
||||||
|
} else if (value instanceof Boolean) {
|
||||||
|
map.putBoolean(key, (Boolean) value);
|
||||||
|
} else if (value instanceof Integer) {
|
||||||
|
map.putInt(key, (Integer) value);
|
||||||
|
} else if (value instanceof Double) {
|
||||||
|
map.putDouble(key, (Double) value);
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
map.putString(key, (String) value);
|
||||||
|
} else {
|
||||||
|
map.putString(key, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WritableArray JSONArrayToList(JSONArray jsonArray) throws JSONException {
|
||||||
|
WritableArray array = Arguments.createArray();
|
||||||
|
for(int i = 0; i < jsonArray.length(); i++) {
|
||||||
|
Object value = jsonArray.get(i);
|
||||||
|
if (value instanceof JSONArray) {
|
||||||
|
array.pushArray(JSONArrayToList((JSONArray) value));
|
||||||
|
} else if (value instanceof JSONObject) {
|
||||||
|
array.pushMap(JSONObjectToMap((JSONObject) value));
|
||||||
|
} else if (value instanceof Boolean) {
|
||||||
|
array.pushBoolean((Boolean) value);
|
||||||
|
} else if (value instanceof Integer) {
|
||||||
|
array.pushInt((Integer) value);
|
||||||
|
} else if (value instanceof Double) {
|
||||||
|
array.pushDouble((Double) value);
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
array.pushString((String) value);
|
||||||
|
} else {
|
||||||
|
array.pushString(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LaunchTiming {
|
||||||
|
private Date start;
|
||||||
|
private boolean coldStart;
|
||||||
|
|
||||||
|
public LaunchTiming(Date start) {
|
||||||
|
this.start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
public void setStart(Date start) {
|
||||||
|
this.start = start;
|
||||||
|
}
|
||||||
|
public boolean isColdStart() {
|
||||||
|
return coldStart;
|
||||||
|
}
|
||||||
|
public void setColdStart(boolean coldStart) {
|
||||||
|
this.coldStart = coldStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.R;
|
||||||
|
import io.lbry.lbrysdk.LbrynetService;
|
||||||
|
|
||||||
|
public class BackgroundMediaModule extends ReactContextBaseJavaModule {
|
||||||
|
|
||||||
|
public static final int NOTIFICATION_ID = 30;
|
||||||
|
|
||||||
|
public static final String ACTION_PLAY = "io.lbry.browser.ACTION_MEDIA_PLAY";
|
||||||
|
|
||||||
|
public static final String ACTION_PAUSE = "io.lbry.browser.ACTION_MEDIA_PAUSE";
|
||||||
|
|
||||||
|
public static final String ACTION_STOP = "io.lbry.browser.ACTION_MEDIA_STOP";
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public BackgroundMediaModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "BackgroundMedia";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void showPlaybackNotification(String title, String publisher, String uri, boolean paused) {
|
||||||
|
Intent contextIntent = new Intent(context, MainActivity.class);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contextIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
Intent playIntent = new Intent();
|
||||||
|
playIntent.setAction(ACTION_PLAY);
|
||||||
|
PendingIntent playPendingIntent = PendingIntent.getBroadcast(context, 0, playIntent, 0);
|
||||||
|
|
||||||
|
Intent pauseIntent = new Intent();
|
||||||
|
pauseIntent.setAction(ACTION_PAUSE);
|
||||||
|
PendingIntent pausePendingIntent = PendingIntent.getBroadcast(context, 0, pauseIntent, 0);
|
||||||
|
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, LbrynetService.NOTIFICATION_CHANNEL_ID);
|
||||||
|
builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(publisher)
|
||||||
|
.setGroup(LbrynetService.GROUP_SERVICE)
|
||||||
|
.setOngoing(!paused)
|
||||||
|
.setSmallIcon(paused ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play)
|
||||||
|
.setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
|
||||||
|
.setShowActionsInCompactView(0))
|
||||||
|
.addAction(paused ? android.R.drawable.ic_media_play : android.R.drawable.ic_media_pause,
|
||||||
|
paused ? "Play" : "Pause",
|
||||||
|
paused ? playPendingIntent : pausePendingIntent)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void hidePlaybackNotification() {
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
notificationManager.cancel(NOTIFICATION_ID);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.lbrysdk.LbrynetService;
|
||||||
|
import io.lbry.lbrysdk.ServiceHelper;
|
||||||
|
|
||||||
|
public class DaemonServiceControlModule extends ReactContextBaseJavaModule {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public DaemonServiceControlModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "DaemonServiceControl";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void startService() {
|
||||||
|
ServiceHelper.start(context, "", LbrynetService.class, "lbrynetservice");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void stopService() {
|
||||||
|
ServiceHelper.stop(context, LbrynetService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void setKeepDaemonRunning(boolean value) {
|
||||||
|
if (context != null) {
|
||||||
|
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = sp.edit();
|
||||||
|
editor.putBoolean(MainActivity.SETTING_KEEP_DAEMON_RUNNING, value);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||||
|
import com.google.android.gms.tasks.OnCompleteListener;
|
||||||
|
import com.google.android.gms.tasks.Task;
|
||||||
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
|
import com.google.firebase.iid.InstanceIdResult;
|
||||||
|
|
||||||
|
import io.lbry.browser.BuildConfig;
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.lbrysdk.Utils;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
public class FirebaseModule extends ReactContextBaseJavaModule {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
private FirebaseAnalytics firebaseAnalytics;
|
||||||
|
|
||||||
|
public FirebaseModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
this.firebaseAnalytics = FirebaseAnalytics.getInstance(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Firebase";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void setCurrentScreen(String name, final Promise promise) {
|
||||||
|
final Activity activity = getCurrentActivity();
|
||||||
|
if (activity != null && firebaseAnalytics != null) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
firebaseAnalytics.setCurrentScreen(activity, name, Utils.capitalizeAndStrip(name));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void track(String name, ReadableMap payload) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
if (payload != null) {
|
||||||
|
HashMap<String, Object> payloadMap = payload.toHashMap();
|
||||||
|
for (Map.Entry<String, Object> entry : payloadMap.entrySet()) {
|
||||||
|
Object value = entry.getValue();
|
||||||
|
if (value != null) {
|
||||||
|
bundle.putString(entry.getKey(), entry.getValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firebaseAnalytics != null) {
|
||||||
|
firebaseAnalytics.logEvent(name, bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void logException(boolean fatal, String message, String error) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString("message", message);
|
||||||
|
bundle.putString("error", error);
|
||||||
|
if (firebaseAnalytics != null) {
|
||||||
|
firebaseAnalytics.logEvent(fatal ? "reactjs_exception" : "reactjs_warning", bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fatal) {
|
||||||
|
Toast.makeText(context,
|
||||||
|
"An application error occurred which has been automatically logged. " +
|
||||||
|
"If you keep seeing this message, please provide feedback to the LBRY " +
|
||||||
|
"team by emailing hello@lbry.com.",
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getMessagingToken(final Promise promise) {
|
||||||
|
FirebaseInstanceId.getInstance().getInstanceId()
|
||||||
|
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
|
||||||
|
@Override
|
||||||
|
public void onComplete(Task<InstanceIdResult> task) {
|
||||||
|
if (!task.isSuccessful()) {
|
||||||
|
promise.reject("Firebase getInstanceId call failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get new Instance ID token
|
||||||
|
String token = task.getResult().getToken();
|
||||||
|
promise.resolve(token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void logLaunchTiming() {
|
||||||
|
Date end = new Date();
|
||||||
|
MainActivity.LaunchTiming currentTiming = MainActivity.CurrentLaunchTiming;
|
||||||
|
if (currentTiming == null) {
|
||||||
|
// no start timing data, so skip this
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long totalTimeMs = end.getTime() - currentTiming.getStart().getTime();
|
||||||
|
String eventName = currentTiming.isColdStart() ? "app_cold_start" : "app_warm_start";
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putLong("total_ms", totalTimeMs);
|
||||||
|
bundle.putLong("total_seconds", new Double(Math.ceil(totalTimeMs / 1000.0)).longValue());
|
||||||
|
if (firebaseAnalytics != null) {
|
||||||
|
firebaseAnalytics.logEvent(eventName, bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
|
||||||
|
public class FirstRunModule extends ReactContextBaseJavaModule {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
private SharedPreferences sp;
|
||||||
|
|
||||||
|
public FirstRunModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
this.sp = reactContext.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "FirstRun";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void isFirstRun(final Promise promise) {
|
||||||
|
// If firstRun flag does not exist, default to true
|
||||||
|
boolean firstRun = sp.getBoolean("firstRun", true);
|
||||||
|
promise.resolve(firstRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void firstRunCompleted() {
|
||||||
|
SharedPreferences.Editor editor = sp.edit();
|
||||||
|
editor.putBoolean("firstRun", false);
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
|
FirebaseAnalytics firebase = FirebaseAnalytics.getInstance(context);
|
||||||
|
if (firebase != null) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
firebase.logEvent("first_run_completed", bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,313 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.Manifest;
|
||||||
|
import android.media.ThumbnailUtils;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.lbrysdk.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class GalleryModule extends ReactContextBaseJavaModule {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public GalleryModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Gallery";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getVideos(Promise promise) {
|
||||||
|
WritableArray items = Arguments.createArray();
|
||||||
|
List<GalleryItem> videos = loadVideos();
|
||||||
|
for (int i = 0; i < videos.size(); i++) {
|
||||||
|
items.pushMap(videos.get(i).toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getThumbnailPath(Promise promise) {
|
||||||
|
if (context != null) {
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
String thumbnailPath = String.format("%s/thumbnails", cacheDir.getAbsolutePath());
|
||||||
|
promise.resolve(thumbnailPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getUploadsPath(Promise promise) {
|
||||||
|
if (context != null) {
|
||||||
|
String baseFolder = Utils.getExternalStorageDir(context);
|
||||||
|
String uploadsPath = String.format("%s/LBRY/Uploads", baseFolder);
|
||||||
|
File uploadsDir = new File(uploadsPath);
|
||||||
|
if (!uploadsDir.isDirectory()) {
|
||||||
|
uploadsDir.mkdirs();
|
||||||
|
}
|
||||||
|
promise.resolve(uploadsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.reject("The content could not be saved to the device. Please check your storage permissions.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void createVideoThumbnail(String targetId, String filePath, Promise promise) {
|
||||||
|
(new AsyncTask<Void, Void, String>() {
|
||||||
|
protected String doInBackground(Void... param) {
|
||||||
|
String thumbnailPath = null;
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MINI_KIND);
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId);
|
||||||
|
|
||||||
|
File file = new File(thumbnailPath);
|
||||||
|
try (FileOutputStream os = new FileOutputStream(thumbnailPath)) {
|
||||||
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||||
|
os.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
promise.reject("Could not create a thumbnail for the video");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbnailPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostExecute(String thumbnailPath) {
|
||||||
|
if (thumbnailPath != null && thumbnailPath.trim().length() > 0) {
|
||||||
|
promise.resolve(thumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void createImageThumbnail(String targetId, String filePath, Promise promise) {
|
||||||
|
(new AsyncTask<Void, Void, String>() {
|
||||||
|
protected String doInBackground(Void... param) {
|
||||||
|
String thumbnailPath = null;
|
||||||
|
FileOutputStream os = null;
|
||||||
|
try {
|
||||||
|
Bitmap source = BitmapFactory.decodeFile(filePath);
|
||||||
|
// MINI_KIND dimensions
|
||||||
|
Bitmap thumbnail = Bitmap.createScaledBitmap(source, 512, 384, false);
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId);
|
||||||
|
os = new FileOutputStream(thumbnailPath);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||||
|
}
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
promise.reject("Could not create a thumbnail for the image");
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (os != null) {
|
||||||
|
try {
|
||||||
|
os.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
// ignoe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbnailPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostExecute(String thumbnailPath) {
|
||||||
|
if (thumbnailPath != null && thumbnailPath.trim().length() > 0) {
|
||||||
|
promise.resolve(thumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<GalleryItem> loadVideos() {
|
||||||
|
String[] projection = {
|
||||||
|
MediaStore.MediaColumns._ID,
|
||||||
|
MediaStore.MediaColumns.DATA,
|
||||||
|
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||||
|
MediaStore.MediaColumns.MIME_TYPE,
|
||||||
|
MediaStore.Video.Media.DURATION
|
||||||
|
};
|
||||||
|
|
||||||
|
List<String> ids = new ArrayList<String>();
|
||||||
|
List<GalleryItem> items = new ArrayList<GalleryItem>();
|
||||||
|
Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, null, null,
|
||||||
|
String.format("%s DESC", MediaStore.MediaColumns.DATE_MODIFIED));
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
int idColumn = cursor.getColumnIndex(MediaStore.MediaColumns._ID);
|
||||||
|
int nameColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
|
||||||
|
int typeColumn = cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE);
|
||||||
|
int pathColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
|
||||||
|
int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
|
||||||
|
|
||||||
|
String id = cursor.getString(idColumn);
|
||||||
|
GalleryItem item = new GalleryItem();
|
||||||
|
item.setId(id);
|
||||||
|
item.setName(cursor.getString(nameColumn));
|
||||||
|
item.setType(cursor.getString(typeColumn));
|
||||||
|
item.setFilePath(cursor.getString(pathColumn));
|
||||||
|
items.add(item);
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkThumbnails(ids);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkThumbnails(final List<String> ids) {
|
||||||
|
(new AsyncTask<Void, Void, Void>() {
|
||||||
|
protected Void doInBackground(Void... param) {
|
||||||
|
if (context != null) {
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
for (int i = 0; i < ids.size(); i++) {
|
||||||
|
String id = ids.get(i);
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
File thumbnailsDir = new File(String.format("%s/thumbnails", cacheDir.getAbsolutePath()));
|
||||||
|
if (!thumbnailsDir.isDirectory()) {
|
||||||
|
thumbnailsDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
String thumbnailPath = String.format("%s/%s.png", thumbnailsDir.getAbsolutePath(), id);
|
||||||
|
File file = new File(thumbnailPath);
|
||||||
|
if (!file.exists()) {
|
||||||
|
// save the thumbnail to the path
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inSampleSize = 1;
|
||||||
|
Bitmap thumbnail = MediaStore.Video.Thumbnails.getThumbnail(
|
||||||
|
resolver, Long.parseLong(id), MediaStore.Video.Thumbnails.MINI_KIND, options);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
try (FileOutputStream os = new FileOutputStream(thumbnailPath)) {
|
||||||
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
// skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.exists() && file.length() > 0 && GalleryModule.this.context != null) {
|
||||||
|
WritableMap params = Arguments.createMap();
|
||||||
|
params.putString("id", id);
|
||||||
|
((ReactApplicationContext) GalleryModule.this.context).getJSModule(
|
||||||
|
DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onGalleryThumbnailChecked", params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostExecute(Void result) {
|
||||||
|
if (GalleryModule.this.context != null) {
|
||||||
|
((ReactApplicationContext) GalleryModule.this.context).getJSModule(
|
||||||
|
DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onAllGalleryThumbnailsChecked", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GalleryItem {
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private int duration;
|
||||||
|
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(int duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilePath() {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilePath(String filePath) {
|
||||||
|
this.filePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WritableMap toMap() {
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
map.putString("id", id);
|
||||||
|
map.putString("name", name);
|
||||||
|
map.putString("filePath", filePath);
|
||||||
|
map.putString("type", type);
|
||||||
|
map.putInt("duration", duration);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void canUseCamera(final Promise promise) {
|
||||||
|
promise.resolve(MainActivity.hasPermission(Manifest.permission.CAMERA, MainActivity.getActivity()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||||
|
import com.facebook.react.bridge.ReadableType;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.lbrysdk.Utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
public class RequestsModule extends ReactContextBaseJavaModule {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public RequestsModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Requests";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void get(final String url, final Promise promise) {
|
||||||
|
(new AsyncTask<Void, Void, String>() {
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Utils.performRequest(url);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(String response) {
|
||||||
|
if (response == null) {
|
||||||
|
promise.reject(String.format("Request to %s returned null.", url));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(response);
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void lbryioCall(String authToken, final Promise promise) {
|
||||||
|
// get the auth token here, or let the app pass it in?
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void lbryCall(final Promise promise) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
public class ScreenOrientationModule extends ReactContextBaseJavaModule {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public ScreenOrientationModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "ScreenOrientation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void unlockOrientation() {
|
||||||
|
Activity activity = getCurrentActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void lockOrientationLandscape() {
|
||||||
|
Activity activity = getCurrentActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void lockOrientationPortrait() {
|
||||||
|
Activity activity = getCurrentActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||||
|
import com.facebook.react.bridge.ReadableType;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
public class StatePersistorModule extends ReactContextBaseJavaModule {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
private List<ReadableMap> queue;
|
||||||
|
|
||||||
|
private ReadableMap filter;
|
||||||
|
|
||||||
|
private ReadableMap lastState;
|
||||||
|
|
||||||
|
private AsyncTask persistTask;
|
||||||
|
|
||||||
|
public StatePersistorModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
queue = new ArrayList<ReadableMap>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "StatePersistor";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private WritableMap filterState(ReadableMap state) {
|
||||||
|
WritableMap filteredState = Arguments.createMap();
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public boolean hasStateChanged(ReadableMap newState) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void update(ReadableMap state, ReadableMap filter) {
|
||||||
|
if (this.filter == null) {
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
// process state updates from the queue using a background task
|
||||||
|
synchronized (this) {
|
||||||
|
queue.add(state);
|
||||||
|
}
|
||||||
|
persistState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistState() {
|
||||||
|
persistState(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistState(final boolean flush) {
|
||||||
|
if (flush && persistTask != null) {
|
||||||
|
persistTask.cancel(true);
|
||||||
|
persistTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (persistTask == null) {
|
||||||
|
persistTask = (new AsyncTask<Object, Void, Boolean>() {
|
||||||
|
protected Boolean doInBackground(Object... param) {
|
||||||
|
// get the first item in the queue
|
||||||
|
ReadableMap queuedState = null;
|
||||||
|
if (queue.size() > 0) {
|
||||||
|
synchronized (StatePersistorModule.this) {
|
||||||
|
queuedState = queue.remove(flush ? queue.size() - 1 : 0);
|
||||||
|
if (flush) {
|
||||||
|
// we only want the final state in this scenario
|
||||||
|
queue.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queuedState != null) {
|
||||||
|
ReadableMap state = queuedState; //(ReadableMap) filterState(queuedState);
|
||||||
|
// convert to JSON object
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject json = readableMapToJSON(state);
|
||||||
|
|
||||||
|
// save the state file
|
||||||
|
// TODO: explore this option at a later date
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
// normally shouldn't happen, but if it does, reinsert into the queue
|
||||||
|
if (queuedState != null) {
|
||||||
|
synchronized (StatePersistorModule.this) {
|
||||||
|
queue.add(0, queuedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostExecute(Boolean result) {
|
||||||
|
if (queue.size() > 0) {
|
||||||
|
persistState();
|
||||||
|
}
|
||||||
|
|
||||||
|
persistTask = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
persistTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void flush() {
|
||||||
|
persistState(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JSONObject readableMapToJSON(ReadableMap readableMap) throws JSONException {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
|
||||||
|
while (iterator.hasNextKey()) {
|
||||||
|
String key = iterator.nextKey();
|
||||||
|
switch (readableMap.getType(key)) {
|
||||||
|
case Map:
|
||||||
|
json.put(key, readableMapToJSON(readableMap.getMap(key)));
|
||||||
|
break;
|
||||||
|
case Array:
|
||||||
|
json.put(key, readableArrayToJSON(readableMap.getArray(key)));
|
||||||
|
break;
|
||||||
|
case Boolean:
|
||||||
|
json.put(key, readableMap.getBoolean(key));
|
||||||
|
break;
|
||||||
|
case Null:
|
||||||
|
json.put(key, JSONObject.NULL);
|
||||||
|
break;
|
||||||
|
case Number:
|
||||||
|
json.put(key, readableMap.getDouble(key));
|
||||||
|
break;
|
||||||
|
case String:
|
||||||
|
json.put(key, readableMap.getString(key));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JSONArray readableArrayToJSON(ReadableArray readableArray) throws JSONException {
|
||||||
|
JSONArray array = new JSONArray();
|
||||||
|
for (int i = 0; i < readableArray.size(); i++) {
|
||||||
|
switch (readableArray.getType(i)) {
|
||||||
|
case Null:
|
||||||
|
break;
|
||||||
|
case Boolean:
|
||||||
|
array.put(readableArray.getBoolean(i));
|
||||||
|
break;
|
||||||
|
case Number:
|
||||||
|
array.put(readableArray.getDouble(i));
|
||||||
|
break;
|
||||||
|
case String:
|
||||||
|
array.put(readableArray.getString(i));
|
||||||
|
break;
|
||||||
|
case Map:
|
||||||
|
array.put(readableMapToJSON(readableArray.getMap(i)));
|
||||||
|
break;
|
||||||
|
case Array:
|
||||||
|
array.put(readableArrayToJSON(readableArray.getArray(i)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,473 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.Manifest;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Callback;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.common.MapBuilder;
|
||||||
|
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
|
||||||
|
import io.lbry.browser.DownloadManager;
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.R;
|
||||||
|
import io.lbry.lbrysdk.LbrynetService;
|
||||||
|
import io.lbry.lbrysdk.Utils;
|
||||||
|
|
||||||
|
public class UtilityModule extends ReactContextBaseJavaModule {
|
||||||
|
private static final Map<String, Integer> activeNotifications = new HashMap<String, Integer>();
|
||||||
|
|
||||||
|
private static final String FILE_PROVIDER = "io.lbry.browser.fileprovider";
|
||||||
|
|
||||||
|
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.SUBSCRIPTIONS_NOTIFICATION_CHANNEL";
|
||||||
|
|
||||||
|
public static final String ACTION_NOTIFICATION_PLAY = "io.lbry.browser.ACTION_NOTIFICATION_PLAY";
|
||||||
|
|
||||||
|
public static final String ACTION_NOTIFICATION_LATER = "io.lbry.browser.ACTION_NOTIFICATION_LATER";
|
||||||
|
|
||||||
|
public static final String RECEIVE_SUBSCRIPTION_NOTIFICATIONS = "receiveSubscriptionNotifications";
|
||||||
|
|
||||||
|
public static final String RECEIVE_REWARD_NOTIFICATIONS = "receiveRewardNotifications";
|
||||||
|
|
||||||
|
public static final String RECEIVE_INTERESTS_NOTIFICATIONS = "receiveInterestsNotifications";
|
||||||
|
|
||||||
|
public static final String RECEIVE_CREATOR_NOTIFICATIONS = "receiveCreatorNotifications";
|
||||||
|
|
||||||
|
// the last language set to be loaded
|
||||||
|
private static final String LANGUAGE_SETTING_KEY = "language";
|
||||||
|
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
private KeyStore keyStore;
|
||||||
|
|
||||||
|
public UtilityModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
try {
|
||||||
|
this.keyStore = Utils.initKeyStore(context);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// continue without keystore
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
language = sp.getString(LANGUAGE_SETTING_KEY, "en");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getConstants() {
|
||||||
|
final Map<String, Object> constants = MapBuilder.newHashMap();
|
||||||
|
constants.put("language", language);
|
||||||
|
return constants;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "UtilityModule";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void keepAwakeOn() {
|
||||||
|
final Activity activity = getCurrentActivity();
|
||||||
|
|
||||||
|
if (activity != null) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void keepAwakeOff() {
|
||||||
|
final Activity activity = getCurrentActivity();
|
||||||
|
|
||||||
|
if (activity != null) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void hideNavigationBar() {
|
||||||
|
final Activity activity = MainActivity.getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
View decorView = activity.getWindow().getDecorView();
|
||||||
|
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void showNavigationBar() {
|
||||||
|
final Activity activity = MainActivity.getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
View decorView = activity.getWindow().getDecorView();
|
||||||
|
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||||
|
View.SYSTEM_UI_FLAG_VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getDeviceId(boolean requestPermission, final Promise promise) {
|
||||||
|
if (isEmulator()) {
|
||||||
|
promise.reject("Rewards cannot be claimed from an emulator nor virtual device.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||||
|
String id = null;
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
id = telephonyManager.getImei(); // GSM
|
||||||
|
if (id == null) {
|
||||||
|
id = telephonyManager.getMeid(); // CDMA
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = telephonyManager.getDeviceId();
|
||||||
|
}
|
||||||
|
} catch (SecurityException ex) {
|
||||||
|
// Maybe the permission was not granted? Try to acquire permission
|
||||||
|
/*if (requestPermission) {
|
||||||
|
requestPhoneStatePermission();
|
||||||
|
}*/
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// id could not be obtained. Display a warning that rewards cannot be claimed.
|
||||||
|
promise.reject(ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == null || id.trim().length() == 0) {
|
||||||
|
promise.reject("Rewards cannot be claimed because your device could not be identified.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void canReceiveSms(final Promise promise) {
|
||||||
|
promise.resolve(MainActivity.hasPermission(Manifest.permission.RECEIVE_SMS, MainActivity.getActivity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void requestReceiveSmsPermission() {
|
||||||
|
MainActivity activity = (MainActivity) MainActivity.getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
// Request for the RECEIVE_SMS permission
|
||||||
|
MainActivity.checkReceiveSmsPermission(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void canReadWriteStorage(final Promise promise) {
|
||||||
|
promise.resolve(MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, MainActivity.getActivity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void requestStoragePermission() {
|
||||||
|
MainActivity activity = (MainActivity) MainActivity.getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
MainActivity.checkStoragePermission(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void shareLogFile(Callback errorCallback) {
|
||||||
|
String logFileName = "lbrynet.log";
|
||||||
|
File logFile = new File(String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet"), logFileName);
|
||||||
|
if (!logFile.exists()) {
|
||||||
|
errorCallback.invoke("The lbrynet.log file could not be found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Uri fileUri = FileProvider.getUriForFile(context, FILE_PROVIDER, logFile);
|
||||||
|
if (fileUri != null) {
|
||||||
|
Intent shareIntent = new Intent();
|
||||||
|
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
shareIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
shareIntent.setType("text/plain");
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||||
|
|
||||||
|
Intent sendLogIntent = Intent.createChooser(shareIntent, "Send LBRY log");
|
||||||
|
sendLogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(sendLogIntent);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
errorCallback.invoke("The lbrynet.log file cannot be shared due to permission restrictions.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void shareUrl(String url) {
|
||||||
|
Intent shareIntent = new Intent();
|
||||||
|
shareIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
shareIntent.setType("text/plain");
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
|
||||||
|
|
||||||
|
Intent shareUrlIntent = Intent.createChooser(shareIntent, "Share LBRY content");
|
||||||
|
shareUrlIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(shareUrlIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void showNotificationForContent(final String uri, String title, String publisher, final String thumbnail, boolean isPlayable) {
|
||||||
|
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_ID, "LBRY Subscriptions", NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
channel.setDescription("LBRY subscription notifications");
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeNotifications.containsKey(uri)) {
|
||||||
|
// the notification for the specified uri is already present, don't try to create another one
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
Random random = new Random();
|
||||||
|
do {
|
||||||
|
id = random.nextInt();
|
||||||
|
} while (id < 100);
|
||||||
|
final int notificationId = id;
|
||||||
|
|
||||||
|
String uriWithParam = String.format("%s?download=true", uri);
|
||||||
|
Intent playIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriWithParam));
|
||||||
|
playIntent.putExtra(MainActivity.SOURCE_NOTIFICATION_ID_KEY, notificationId);
|
||||||
|
playIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
PendingIntent playPendingIntent = PendingIntent.getActivity(context, 0, playIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
|
boolean hasThumbnail = false;
|
||||||
|
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
|
builder.setAutoCancel(true)
|
||||||
|
.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
|
||||||
|
.setContentIntent(DownloadManager.getLaunchPendingIntent(uri, context))
|
||||||
|
.setContentTitle(publisher)
|
||||||
|
.setContentText(title)
|
||||||
|
.setSmallIcon(R.drawable.ic_lbry)
|
||||||
|
.addAction(android.R.drawable.ic_media_play, (isPlayable ? "Play" : "Open"), playPendingIntent);
|
||||||
|
|
||||||
|
activeNotifications.put(uri, notificationId);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
// attempt to load the thumbnail Bitmap before displaying the notification
|
||||||
|
final Uri thumbnailUri = Uri.parse(thumbnail);
|
||||||
|
if (thumbnailUri != null) {
|
||||||
|
hasThumbnail = true;
|
||||||
|
(new AsyncTask<Void, Void, Bitmap>() {
|
||||||
|
protected Bitmap doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Picasso.get().load(thumbnailUri).get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(Bitmap result) {
|
||||||
|
if (result != null) {
|
||||||
|
builder.setLargeIcon(result)
|
||||||
|
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(result).bigLargeIcon(null));
|
||||||
|
}
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasThumbnail) {
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEmulator() {
|
||||||
|
String buildModel = Build.MODEL.toLowerCase();
|
||||||
|
return (// Check FINGERPRINT
|
||||||
|
Build.FINGERPRINT.startsWith("generic") ||
|
||||||
|
Build.FINGERPRINT.startsWith("unknown") ||
|
||||||
|
Build.FINGERPRINT.contains("test-keys") ||
|
||||||
|
|
||||||
|
// Check MODEL
|
||||||
|
buildModel.contains("google_sdk") ||
|
||||||
|
buildModel.contains("emulator") ||
|
||||||
|
buildModel.contains("android sdk built for x86") ||
|
||||||
|
|
||||||
|
// Check MANUFACTURER
|
||||||
|
Build.MANUFACTURER.contains("Genymotion") ||
|
||||||
|
"unknown".equals(Build.MANUFACTURER) ||
|
||||||
|
|
||||||
|
// Check HARDWARE
|
||||||
|
Build.HARDWARE.contains("goldfish") ||
|
||||||
|
Build.HARDWARE.contains("vbox86") ||
|
||||||
|
|
||||||
|
// Check PRODUCT
|
||||||
|
"google_sdk".equals(Build.PRODUCT) ||
|
||||||
|
"sdk_google_phone_x86".equals(Build.PRODUCT) ||
|
||||||
|
"sdk".equals(Build.PRODUCT) ||
|
||||||
|
"sdk_x86".equals(Build.PRODUCT) ||
|
||||||
|
"vbox86p".equals(Build.PRODUCT) ||
|
||||||
|
|
||||||
|
// Check BRAND and DEVICE
|
||||||
|
(Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void setSecureValue(String key, String value) {
|
||||||
|
if (keyStore != null) {
|
||||||
|
Utils.setSecureValue(key, value, context, keyStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getSecureValue(String key, Promise promise) {
|
||||||
|
if (keyStore == null) {
|
||||||
|
promise.reject("no keyStore found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(Utils.getSecureValue(key, context, keyStore));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void checkDownloads() {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(LbrynetService.ACTION_CHECK_DOWNLOADS);
|
||||||
|
if (context != null) {
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void queueDownload(String outpoint) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(LbrynetService.ACTION_QUEUE_DOWNLOAD);
|
||||||
|
intent.putExtra("outpoint", outpoint);
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void deleteDownload(String uri) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(LbrynetService.ACTION_DELETE_DOWNLOAD);
|
||||||
|
intent.putExtra("uri", uri);
|
||||||
|
if (context != null) {
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void openDocumentPicker(String type) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
|
intent.setType(type);
|
||||||
|
Activity activity = MainActivity.getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.startActivityForResult(
|
||||||
|
Intent.createChooser(intent, "Select a file"), MainActivity.DOCUMENT_PICKER_RESULT_CODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void setNativeBooleanSetting(String key, boolean value) {
|
||||||
|
if (context != null) {
|
||||||
|
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = sp.edit();
|
||||||
|
editor.putBoolean(key, value);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void setNativeStringSetting(String key, String value) {
|
||||||
|
if (context != null) {
|
||||||
|
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = sp.edit();
|
||||||
|
editor.putString(key, value);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getNativeStringSetting(String key, String defaultValue, Promise promise) {
|
||||||
|
if (context != null) {
|
||||||
|
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
promise.resolve(sp.getString(key, defaultValue));
|
||||||
|
} else {
|
||||||
|
promise.resolve(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getNotificationLaunchTarget(Promise promise) {
|
||||||
|
Activity activity = MainActivity.getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
Intent intent = activity.getIntent();
|
||||||
|
if (intent != null) {
|
||||||
|
String target = intent.getStringExtra("target");
|
||||||
|
if (target != null && target.trim().length() > 0) {
|
||||||
|
promise.resolve(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getDownloadDirectory(Promise promise) {
|
||||||
|
// This obtains a public default download directory after the storage permission has been granted
|
||||||
|
promise.resolve(Utils.getConfiguredDownloadDirectory(context));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
public class VersionInfoModule extends ReactContextBaseJavaModule {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public VersionInfoModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "VersionInfo";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getAppVersion(final Promise promise) {
|
||||||
|
PackageManager packageManager = this.context.getPackageManager();
|
||||||
|
String packageName = this.context.getPackageName();
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
||||||
|
promise.resolve(packageInfo.versionName);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
// normally shouldn't happen
|
||||||
|
promise.resolve("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package io.lbry.browser.reactpackages;
|
||||||
|
|
||||||
|
import com.facebook.react.ReactPackage;
|
||||||
|
import com.facebook.react.bridge.NativeModule;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.uimanager.ViewManager;
|
||||||
|
|
||||||
|
import io.lbry.browser.reactmodules.BackgroundMediaModule;
|
||||||
|
import io.lbry.browser.reactmodules.DaemonServiceControlModule;
|
||||||
|
import io.lbry.browser.reactmodules.FirstRunModule;
|
||||||
|
import io.lbry.browser.reactmodules.FirebaseModule;
|
||||||
|
import io.lbry.browser.reactmodules.GalleryModule;
|
||||||
|
import io.lbry.browser.reactmodules.RequestsModule;
|
||||||
|
import io.lbry.browser.reactmodules.ScreenOrientationModule;
|
||||||
|
import io.lbry.browser.reactmodules.StatePersistorModule;
|
||||||
|
import io.lbry.browser.reactmodules.VersionInfoModule;
|
||||||
|
import io.lbry.browser.reactmodules.UtilityModule;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LbryReactPackage implements ReactPackage {
|
||||||
|
@Override
|
||||||
|
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||||
|
List<NativeModule> modules = new ArrayList<>();
|
||||||
|
|
||||||
|
modules.add(new BackgroundMediaModule(reactContext));
|
||||||
|
modules.add(new DaemonServiceControlModule(reactContext));
|
||||||
|
modules.add(new FirstRunModule(reactContext));
|
||||||
|
modules.add(new FirebaseModule(reactContext));
|
||||||
|
modules.add(new GalleryModule(reactContext));
|
||||||
|
modules.add(new RequestsModule(reactContext));
|
||||||
|
modules.add(new ScreenOrientationModule(reactContext));
|
||||||
|
modules.add(new StatePersistorModule(reactContext));
|
||||||
|
modules.add(new UtilityModule(reactContext));
|
||||||
|
modules.add(new VersionInfoModule(reactContext));
|
||||||
|
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package io.lbry.browser.receivers;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import io.lbry.browser.DownloadManager;
|
||||||
|
|
||||||
|
public class NotificationDeletedReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
int notificationId = intent.getExtras().getInt(DownloadManager.NOTIFICATION_ID_KEY);
|
||||||
|
if (DownloadManager.DOWNLOAD_NOTIFICATION_GROUP_ID == notificationId) {
|
||||||
|
DownloadManager.groupCreated = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
android/app/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/drawable-hdpi/ic_lbry.png
Normal file
After Width: | Height: | Size: 620 B |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 134 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/drawable-mdpi/ic_lbry.png
Normal file
After Width: | Height: | Size: 444 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 640 B |
After Width: | Height: | Size: 611 B |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 494 B |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 451 B |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 225 B |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 683 B |
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 879 B |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 332 B |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 265 B |
After Width: | Height: | Size: 437 B |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 523 B |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 370 B |
After Width: | Height: | Size: 540 B |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 560 B |
After Width: | Height: | Size: 261 B |
After Width: | Height: | Size: 5.3 KiB |