Merge pull request #1 from lbryio/android-aar

Android AAR
This commit is contained in:
Akinwale Ariwodola 2020-02-20 12:19:44 +01:00 committed by GitHub
commit 80c0d34083
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
344 changed files with 138 additions and 8293 deletions

4
.gitignore vendored
View file

@ -15,3 +15,7 @@ src/main/assets/index.android.bundle.meta
lbry-android.keystore
p4a/pythonforandroid/bootstraps/lbry/build/templates/google-services.json
.gitsecret/keys/random_seed
p4a/*.apk
p4a/*.aar

View file

@ -4,7 +4,7 @@ stages:
- deploy
- release
build arm64 apk:
build arm64 aar:
stage: build
image: lbry/android-base:latest
before_script:
@ -13,14 +13,11 @@ build arm64 apk:
- git submodule update --init --force --recursive
artifacts:
paths:
- bin/browser-*-release__arm64.apk
- bin/lbrysdk-*-release__arm64.aar
expire_in: 1 week
script:
- export PATH=/usr/bin:$PATH
- echo "$PGP_PRIVATE_KEY" | gpg --batch --import
- cd app
- npm install
- cd ..
- wget -q 'https://eu.crystax.net/download/crystax-ndk-10.3.2-linux-x86_64.tar.xz' -P ~/.buildozer/android/
- tar -xf ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz -C ~/.buildozer/android/
- rm -rf ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9
@ -31,11 +28,11 @@ build arm64 apk:
- git secret reveal
- mv buildozer.spec.arm64.ci buildozer.spec
- mkdir -p $CI_PROJECT_DIR/src/main/assets/blockchain && wget -q https://headers.lbry.io/blockchain_headers_latest -O $CI_PROJECT_DIR/src/main/assets/blockchain/headers
- "./release.sh | grep -Fv -e 'working:' -e 'copy' -e 'Compiling' --line-buffered"
- cp $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release.apk $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release__arm64.apk
- cp $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release.apk /dev/null
- "./build-release.sh | grep -Fv -e 'working:' -e 'copy' -e 'Compiling' --line-buffered"
- cp $CI_PROJECT_DIR/bin/lbrysdk-$BUILD_VERSION-release.aar $CI_PROJECT_DIR/bin/lbrysdk-$BUILD_VERSION-release__arm64.aar
- cp $CI_PROJECT_DIR/bin/lbrysdk-$BUILD_VERSION-release.aar /dev/null
build arm apk:
build arm aar:
stage: build2
image: lbry/android-base:latest
before_script:
@ -44,15 +41,12 @@ build arm apk:
- git submodule update --init --force --recursive
artifacts:
paths:
- bin/browser-*-release__arm.apk
- bin/lbrysdk-*-release__arm.aar
expire_in: 1 week
script:
- export PATH=/usr/bin:$PATH
- echo "$PGP_PRIVATE_KEY" | gpg --batch --import
- cd app
- npm install
- cd ..
- wget -q 'https://eu.crystax.net/download/crystax-ndk-10.3.2-linux-x86_64.tar.xz' -P ~/.buildozer/android/
- wget -q 'https://eu.crystax.net/download/crystax-ndk-10.3.2-linux-x86_64.tar.xz' -P ~/.f.ozer/android/
- tar -xf ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz -C ~/.buildozer/android/
- rm -rf ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9
- ln -s ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21 ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9
@ -63,40 +57,38 @@ build arm apk:
- git secret reveal
- mv buildozer.spec.arm.ci buildozer.spec
- mkdir -p $CI_PROJECT_DIR/src/main/assets/blockchain && wget -q https://headers.lbry.io/blockchain_headers_latest -O $CI_PROJECT_DIR/src/main/assets/blockchain/headers
- "./release.sh | grep -Fv -e 'working:' -e 'copy' -e 'Compiling' --line-buffered"
- cp $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release.apk $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release__arm.apk
- cp $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release.apk /dev/null
- "./build-release.sh | grep -Fv -e 'working:' -e 'copy' -e 'Compiling' --line-buffered"
- cp $CI_PROJECT_DIR/bin/lbrysdk-$BUILD_VERSION-release.aar $CI_PROJECT_DIR/bin/lbrysdk-$BUILD_VERSION-release__arm.aar
- cp $CI_PROJECT_DIR/bin/lbrysdk-$BUILD_VERSION-release.aar /dev/null
deploy build.lbry.io:
image: python:latest
stage: deploy
dependencies:
- build arm apk
- build arm64 apk
- build arm aar
- build arm64 aar
before_script:
- pip install awscli
- export BUILD_VERSION=$(cat $CI_PROJECT_DIR/src/main/python/main.py | grep --color=never -oP '([0-9]+\.?)+')
- export BUILD_APK_FILENAME__32=browser-$BUILD_VERSION-release__arm.apk
- export BUILD_APK_FILENAME__64=browser-$BUILD_VERSION-release__arm64.apk
- export BUILD_APK_FILENAME__32=lbrysdk-$BUILD_VERSION-release__arm.aar
- export BUILD_APK_FILENAME__64=lbrysdk-$BUILD_VERSION-release__arm64.aar
script:
- aws s3 cp bin/$BUILD_APK_FILENAME__64 s3://build.lbry.io/android/build-${CI_PIPELINE_IID}_commit-${CI_COMMIT_SHA:0:7}/$BUILD_APK_FILENAME__64
- aws s3 cp bin/$BUILD_APK_FILENAME__32 s3://build.lbry.io/android/build-${CI_PIPELINE_IID}_commit-${CI_COMMIT_SHA:0:7}/$BUILD_APK_FILENAME__32
- aws s3 cp bin/$BUILD_APK_FILENAME__64 s3://build.lbry.io/android/push.apk
release apk:
release aar:
image: python:latest
stage: release
only:
- tags
dependencies:
- build arm apk
- build arm64 apk
- build arm aar
- build arm64 aar
before_script:
- pip install awscli githubrelease
- export BUILD_VERSION=$(cat $CI_PROJECT_DIR/src/main/python/main.py | grep --color=never -oP '([0-9]+\.?)+')
- export BUILD_APK_FILENAME__32=browser-$BUILD_VERSION-release__arm.apk
- export BUILD_APK_FILENAME__64=browser-$BUILD_VERSION-release__arm64.apk
- export BUILD_APK_FILENAME__32=lbrysdk-$BUILD_VERSION-release__arm.aar
- export BUILD_APK_FILENAME__64=lbrysdk-$BUILD_VERSION-release__arm64.aar
script:
- githubrelease release lbryio/lbry-android create $CI_COMMIT_TAG --publish bin/$BUILD_APK_FILENAME__64 bin/$BUILD_APK_FILENAME__32
- githubrelease release lbryio/lbry-android edit $CI_COMMIT_TAG --draft
- aws s3 cp bin/$BUILD_APK_FILENAME__64 s3://build.lbry.io/android/latest.apk
- githubrelease release lbryio/lbry-android-sdk create $CI_COMMIT_TAG --publish bin/$BUILD_APK_FILENAME__64 bin/$BUILD_APK_FILENAME__32
- githubrelease release lbryio/lbry-android-sdk edit $CI_COMMIT_TAG --draft

View file

@ -11,14 +11,10 @@ RUN dpkg --add-architecture i386 && \
autoconf autogen automake libtool libffi-dev build-essential \
ccache git libncurses5:i386 libstdc++6:i386 \
libgtk2.0-0:i386 libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 libidn11:i386
RUN npm install -g npm@latest
RUN npm install -g yarn react-native-cli && \
pip2 install --upgrade cython setuptools && \
RUN pip2 install --upgrade cython setuptools && \
pip2 install git+https://github.com/lbryio/buildozer.git@master && \
ln -s /src/scripts/build-docker.sh /usr/local/bin/build && \
adduser lbry-android --gecos GECOS --shell /bin/bash --disabled-password --home /home/lbry-android && \
mkdir /home/lbry-android/.npm-packages && \
echo "prefix=/home/lbry-android/.npm-packages" > /home/lbry-android/.npmrc && \
chown -R lbry-android:lbry-android /home/lbry-android && \
mkdir /src && \
chown lbry-android:lbry-android /src && \

2
build-debug.sh Normal file
View file

@ -0,0 +1,2 @@
#!/bin/sh
buildozer android debug

3
build-release.sh Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
buildozer android release

View file

@ -1,5 +0,0 @@
#!/bin/sh
cd app
react-native bundle --platform android --dev false --entry-file src/index.js --bundle-output ../src/main/assets/index.android.bundle --assets-dest ../src/main/res/
cd ..
buildozer android debug

View file

@ -9,6 +9,9 @@ package.name = browser
# (str) Package domain (needed for android/ios packaging)
package.domain = io.lbry
# Package type for Android ('application' for APK or 'library' for AAR)
package.type = library
# (str) Source code where the main.py live
source.dir = ./src/main/python
@ -148,7 +151,7 @@ android.react_src = ./app
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
android.gradle_dependencies = androidx.legacy:legacy-support-v4:1.0.0, androidx.media:media:1.0.0, androidx.appcompat:appcompat:1.0.0, com.facebook.react:react-native:0.61.5, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-analytics:17.2.1, com.google.android.gms:play-services-base:17.1.0, androidx.exifinterface:exifinterface:1.0.0, com.facebook.fresco:animated-base-support:1.3.0, com.facebook.fresco:animated-gif:1.10.0, com.google.firebase:firebase-messaging:20.1.0
android.gradle_dependencies = androidx.appcompat:appcompat:1.0.2
# (str) python-for-android branch to use, defaults to master
#p4a.branch = stable

View file

@ -9,6 +9,9 @@ package.name = browser
# (str) Package domain (needed for android/ios packaging)
package.domain = io.lbry
# Package type for Android ('application' for APK or 'library' for AAR)
package.type = library
# (str) Source code where the main.py live
source.dir = ./src/main/python
@ -148,7 +151,7 @@ android.react_src = ./app
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
android.gradle_dependencies = androidx.legacy:legacy-support-v4:1.0.0, androidx.media:media:1.0.0, androidx.appcompat:appcompat:1.0.0, com.facebook.react:react-native:0.61.5, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-analytics:17.2.1, com.google.android.gms:play-services-base:17.1.0, androidx.exifinterface:exifinterface:1.0.0, com.facebook.fresco:animated-base-support:1.3.0, com.facebook.fresco:animated-gif:1.10.0, com.google.firebase:firebase-messaging:20.1.0
android.gradle_dependencies = androidx.appcompat:appcompat:1.0.2
# (str) python-for-android branch to use, defaults to master
#p4a.branch = stable

View file

@ -9,6 +9,9 @@ package.name = browser
# (str) Package domain (needed for android/ios packaging)
package.domain = io.lbry
# Package type for Android ('application' for APK or 'library' for AAR)
package.type = library
# (str) Source code where the main.py live
source.dir = ./src/main/python
@ -148,7 +151,7 @@ android.react_src = ./app
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
android.gradle_dependencies = androidx.legacy:legacy-support-v4:1.0.0, androidx.media:media:1.0.0, androidx.appcompat:appcompat:1.0.0, com.facebook.react:react-native:0.61.5, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-analytics:17.2.1, com.google.android.gms:play-services-base:17.1.0, androidx.exifinterface:exifinterface:1.0.0, com.facebook.fresco:animated-base-support:1.3.0, com.facebook.fresco:animated-gif:1.10.0, com.google.firebase:firebase-messaging:20.1.0
android.gradle_dependencies = androidx.appcompat:appcompat:1.0.2
# (str) python-for-android branch to use, defaults to master
#p4a.branch = stable

View file

@ -9,6 +9,9 @@ package.name = browser
# (str) Package domain (needed for android/ios packaging)
package.domain = io.lbry
# Package type for Android ('application' for APK or 'library' for AAR)
package.type = library
# (str) Source code where the main.py live
source.dir = ./src/main/python
@ -148,7 +151,7 @@ android.react_src = ./app
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
android.gradle_dependencies = androidx.legacy:legacy-support-v4:1.0.0, androidx.media:media:1.0.0, androidx.appcompat:appcompat:1.0.0, com.facebook.react:react-native:0.61.5, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-analytics:17.2.1, com.google.android.gms:play-services-base:17.1.0, androidx.exifinterface:exifinterface:1.0.0, com.facebook.fresco:animated-base-support:1.3.0, com.facebook.fresco:animated-gif:1.10.0, com.google.firebase:firebase-messaging:20.1.0
android.gradle_dependencies = androidx.appcompat:appcompat:1.0.2
# (str) python-for-android branch to use, defaults to master
#p4a.branch = stable

View file

@ -1,6 +0,0 @@
#!/bin/sh
cd app
react-native bundle --platform android --dev true --entry-file src/index.js --bundle-output ../src/main/assets/index.android.bundle --assets-dest ../src/main/res/
cd ..
buildozer android debug deploy

View file

@ -404,13 +404,8 @@ main.py that loads it.''')
android_api=android_api,
build_tools_version=build_tools_version)
render('settings.gradle', 'settings.gradle')
render('gradle.properties', 'gradle.properties')
## google-services.json for firebase
render('google-services.json', 'google-services.json')
# copy icon drawables
for folder in ('drawable-hdpi', 'drawable-mdpi', 'drawable-xhdpi', 'drawable-xxhdpi', 'drawable-xxxhdpi'):
shutil.copy(

View file

@ -1,177 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Replace org.libsdl.app with the identifier of your game below, e.g.
com.gamemaker.game
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="{{ args.package }}"
android:versionCode="{{ args.numeric_version }}"
android:versionName="{{ args.version }}"
android:installLocation="auto">
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true"
{% if args.min_sdk_version >= 9 %}
android:xlargeScreens="true"
{% endif %}
/>
android:versionName="{{ args.version }}">
<!-- Android 2.3.3 -->
<uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ android_api }}" />
<!-- OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
{% for perm in args.permissions %}
{% if '.' in perm %}
<uses-permission android:name="{{ perm }}" />
{% else %}
<uses-permission android:name="android.permission.{{ perm }}" />
{% endif %}
{% endfor %}
{% if args.wakelock %}
<uses-permission android:name="android.permission.WAKE_LOCK" />
{% endif %}
{% if args.billing_pubkey %}
<uses-permission android:name="com.android.vending.BILLING" />
{% endif %}
<!-- Create a Java class extending SDLActivity and place it in a
directory under src matching the package, e.g.
src/com/gamemaker/game/MyGame.java
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
in the XML below.
An example Java class can be found in README-android.txt
-->
<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"/>
{% for m in args.meta_data %}
<meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
<meta-data android:name="wakelock" android:value="{% if args.wakelock %}1{% else %}0{% endif %}"/>
<!--activity android:name="io.lbry.lbrynet.ServiceControlActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
android:screenOrientation="{{ args.orientation }}"
-->
<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{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
android:screenOrientation="{{ args.orientation }}"
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>
{% if args.launcher %}
<intent-filter>
<action android:name="org.kivy.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="{{ url_scheme }}" />
</intent-filter>
{% else %}
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
{% endif %}
{%- if args.intent_filters -%}
{{- args.intent_filters -}}
{%- endif -%}
</activity>
<receiver android:name="io.lbry.browser.receivers.NotificationDeletedReceiver" />
{% if args.launcher %}
<activity android:name="org.kivy.android.launcher.ProjectChooser"
android:icon="@drawable/icon"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
{% endif %}
{% if service or args.launcher %}
<service android:name="org.kivy.android.PythonService"
android:process=":pythonservice" />
{% endif %}
{% for name in service_names %}
<service android:name="{{ args.package }}.Service{{ name|capitalize }}"
android:process=":service_{{ name }}" />
{% endfor %}
<application>
<service android:name="{{ args.package }}.LbrynetService"
android:process=":service_lbrynet" />
<service android:name="{{ args.package }}.LbrynetTestRunnerService"
android:process=":service_lbrynet_testrunner" />
<service
android:name="{{ args.package }}.LbrynetMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
{% if args.billing_pubkey %}
<service android:name="org.kivy.android.billing.BillingReceiver"
android:process=":pythonbilling" />
<receiver android:name="org.kivy.android.billing.BillingReceiver"
android:process=":pythonbillingreceiver">
<intent-filter>
<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
<action android:name="com.android.vending.billing.RESPONSE_CODE" />
<action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
</intent-filter>
</receiver>
{% endif %}
<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>
android:process=":service_lbrynet"
android:exported="true" />
</application>
</manifest>

View file

@ -7,7 +7,6 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.google.gms:google-services:4.2.0'
}
}
@ -17,29 +16,13 @@ allprojects {
maven {
url 'https://maven.google.com'
}
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/react/node_modules/react-native/android"
}
maven {
// Android JSC is installed from npm
url("$rootDir/react/node_modules/jsc-android/dist")
}
flatDir {
dirs 'libs'
}
}
}
apply plugin: 'com.android.application'
project.ext.react = [
enableHermes: false,
entryFile: "index.js"
]
def enableHermes = project.ext.react.get("enableHermes", false);
def jscFlavor = 'org.webkit:android-jsc:+'
apply plugin: 'com.android.library'
android {
compileSdkVersion {{ android_api }}
@ -49,7 +32,6 @@ android {
targetSdkVersion {{ android_api }}
versionCode {{ args.numeric_version }} * 10 + 2
versionName '{{ args.version }}'
missingDimensionStrategy 'react-native-camera', 'general'
multiDexEnabled true
ndk {
@ -99,9 +81,6 @@ ext {
buildToolsVersion = '{{ build_tools_version }}'
minSdkVersion = {{ args.min_sdk_version }}
targetSdkVersion = {{ android_api }}
supportLibVersion = '28.0.0'
googlePlayServicesVersion = '16.1.0'
googlePlayServicesVisionVersion = '17.0.2'
}
subprojects {
@ -116,17 +95,6 @@ subprojects {
}
dependencies {
compile project(':@react-native-community_async-storage')
compile project(':react-native-camera')
compile project(':react-native-exception-handler')
compile project(':react-native-fast-image')
compile project(':react-native-fs')
compile project(':react-native-gesture-handler')
compile project(':react-native-reanimated')
compile project(':react-native-snackbar')
compile project(':react-native-video')
compile project(':react-native-webview')
compile project(':rn-fetch-blob')
{%- for aar in aars %}
compile(name: '{{ aar }}', ext: 'aar')
{%- endfor -%}
@ -135,16 +103,4 @@ dependencies {
compile '{{ depend }}'
{%- endfor %}
{%- endif %}
if (enableHermes) {
def hermesPath = "$rootDir/react/node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
apply plugin: 'com.google.gms.google-services'
com.google.gms.googleservices.GoogleServicesPlugin.config.disableVersionCheck = true

View file

@ -7,7 +7,6 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.google.gms:google-services:4.2.0'
}
}
@ -17,29 +16,13 @@ allprojects {
maven {
url 'https://maven.google.com'
}
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/react/node_modules/react-native/android"
}
maven {
// Android JSC is installed from npm
url("$rootDir/react/node_modules/jsc-android/dist")
}
flatDir {
dirs 'libs'
}
}
}
apply plugin: 'com.android.application'
project.ext.react = [
enableHermes: false,
entryFile: "index.js"
]
def enableHermes = project.ext.react.get("enableHermes", false);
def jscFlavor = 'org.webkit:android-jsc:+'
apply plugin: 'com.android.library'
android {
compileSdkVersion {{ android_api }}
@ -47,9 +30,8 @@ android {
defaultConfig {
minSdkVersion {{ args.min_sdk_version }}
targetSdkVersion {{ android_api }}
versionCode {{ args.numeric_version }} * 10 + 1
versionCode {{ args.numeric_version }} * 10 + 2
versionName '{{ args.version }}'
missingDimensionStrategy 'react-native-camera', 'general'
multiDexEnabled true
ndk {
@ -99,9 +81,6 @@ ext {
buildToolsVersion = '{{ build_tools_version }}'
minSdkVersion = {{ args.min_sdk_version }}
targetSdkVersion = {{ android_api }}
supportLibVersion = '28.0.0'
googlePlayServicesVersion = '16.1.0'
googlePlayServicesVisionVersion = '17.0.2'
}
subprojects {
@ -116,17 +95,6 @@ subprojects {
}
dependencies {
compile project(':@react-native-community_async-storage')
compile project(':react-native-camera')
compile project(':react-native-exception-handler')
compile project(':react-native-fast-image')
compile project(':react-native-fs')
compile project(':react-native-gesture-handler')
compile project(':react-native-reanimated')
compile project(':react-native-snackbar')
compile project(':react-native-video')
compile project(':react-native-webview')
compile project(':rn-fetch-blob')
{%- for aar in aars %}
compile(name: '{{ aar }}', ext: 'aar')
{%- endfor -%}
@ -135,16 +103,4 @@ dependencies {
compile '{{ depend }}'
{%- endfor %}
{%- endif %}
if (enableHermes) {
def hermesPath = "$rootDir/react/node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
apply plugin: 'com.google.gms.google-services'
com.google.gms.googleservices.GoogleServicesPlugin.config.disableVersionCheck = true

View file

@ -1,40 +0,0 @@
{
"project_info": {
"project_number": "861521963586",
"firebase_url": "https://lbry-mobile-builds-debug.firebaseio.com",
"project_id": "lbry-mobile-builds-debug",
"storage_bucket": "lbry-mobile-builds-debug.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:861521963586:android:592958d248940ab2",
"android_client_info": {
"package_name": "io.lbry.browser"
}
},
"oauth_client": [
{
"client_id": "861521963586-60cmvg5nmnrqkrc11a7bpmpv5ra2d50q.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyC7A3BYcIdZP9-Q-VNHoexYJWgZA7WzsPI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "861521963586-60cmvg5nmnrqkrc11a7bpmpv5ra2d50q.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View file

@ -1,23 +0,0 @@
rootProject.name = 'browser'
include ':@react-native-community_async-storage'
project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, './react/node_modules/@react-native-community/async-storage/android')
include ':react-native-camera'
project(':react-native-camera').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-camera/android')
include ':react-native-exception-handler'
project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-exception-handler/android')
include ':react-native-fast-image'
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-fast-image/android')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(settingsDir, './react/node_modules/react-native-fs/android')
include ':react-native-gesture-handler'
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-gesture-handler/android')
include ':react-native-reanimated'
project(':react-native-reanimated').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-reanimated/android')
include ':react-native-snackbar'
project(':react-native-snackbar').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-snackbar/android')
include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-video/android-exoplayer')
include ':react-native-webview'
project(':react-native-webview').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-webview/android')
include ':rn-fetch-blob'
project(':rn-fetch-blob').projectDir = new File(rootProject.projectDir, './react/node_modules/rn-fetch-blob/android')

View file

@ -868,9 +868,8 @@ class ToolchainCL(object):
# gradle output apks somewhere else
# and don't have version in file
apk_dir = join(dist.dist_dir,
"build", "outputs", "apk",
args.build_mode)
apk_glob = "*-{}.apk"
"build", "outputs", "aar")
apk_glob = "*-{}.aar"
apk_add_version = True
else:
@ -884,14 +883,14 @@ class ToolchainCL(object):
output = shprint(ant, args.build_mode, _tail=20,
_critical=True, _env=env)
apk_dir = join(dist.dist_dir, "bin")
apk_glob = "*-*-{}.apk"
apk_glob = "*-*-{}.aar"
apk_add_version = False
self.hook("after_apk_assemble")
info_main('# Copying APK to current directory')
info_main('# Copying android package to current directory')
apk_re = re.compile(r'.*Package: (.*\.apk)$')
apk_re = re.compile(r'.*Package: (.*\.aar)$')
apk_file = None
for line in reversed(output.splitlines()):
m = apk_re.match(line)
@ -900,7 +899,7 @@ class ToolchainCL(object):
break
if not apk_file:
info_main('# APK filename not found in build output. Guessing...')
info_main('# AAR not found in build output. Guessing...')
if args.build_mode == "release":
suffixes = ("release", "release-unsigned")
else:
@ -909,20 +908,20 @@ class ToolchainCL(object):
apks = glob.glob(join(apk_dir, apk_glob.format(suffix)))
if apks:
if len(apks) > 1:
info('More than one built APK found... guessing you '
info('More than one built AAR found... guessing you '
'just built {}'.format(apks[-1]))
apk_file = apks[-1]
break
else:
raise BuildInterruptingException('Couldn\'t find the built APK')
raise BuildInterruptingException('Couldn\'t find the built AAR')
info_main('# Found APK file: {}'.format(apk_file))
info_main('# Found AAR file: {}'.format(apk_file))
if apk_add_version:
info('# Add version number to APK')
info('# Add version number to AAR')
apk_name, apk_suffix = basename(apk_file).split("-", 1)
apk_file_dest = "{}-{}-{}".format(
apk_name, build_args.version, apk_suffix)
info('# APK renamed to {}'.format(apk_file_dest))
info('# AAR renamed to {}'.format(apk_file_dest))
shprint(sh.cp, apk_file, apk_file_dest)
else:
shprint(sh.cp, apk_file, './')

View file

@ -1,16 +0,0 @@
#!/bin/bash
cd app
react-native bundle --platform android --dev false --entry-file src/index.js --bundle-output ../src/main/assets/index.android.bundle --assets-dest ../src/main/res/
cd ..
cp src/main/assets/index.android.bundle /dev/null
version=$(cat src/main/python/main.py | grep --color=never -oP '([0-9]+\.?)+')
buildozer android release <<< y
jarsigner -verbose -sigalg SHA1withRSA \
-digestalg SHA1 \
-keystore lbry-android.keystore \
-storepass $KEYSTORE_PASSWORD \
bin/browser-$version-release-unsigned.apk lbry-android > /dev/null \
&& mv bin/browser-$version-release-unsigned.apk bin/browser-$version-release-signed.apk
~/.buildozer/android/platform/android-sdk-23/build-tools/28.0.3/zipalign -v 4 \
bin/browser-$version-release-signed.apk bin/browser-$version-release.apk > /dev/null \
&& rm bin/browser-$version-release-signed.apk

View file

@ -3,7 +3,7 @@ set -e
exe() { ( echo "## $*"; $*; ) }
ANDROID_SDK_LICENSE=/home/lbry-android/.buildozer/android/platform/android-sdk-23/licenses/android-sdk-license
ANDROID_SDK_LICENSE=/home/lbry-android-sdk/.buildozer/android/platform/android-sdk-23/licenses/android-sdk-license
## VERSION and REPO variables are optional:
## Use 'none' as a way to detect that none was provided by the user:
VERSION=${VERSION:-none}
@ -49,7 +49,7 @@ if [ $VERSION != "none" ] || [ $REPO != "none" ]; then
VERSION=master
fi
if [ $REPO == "none" ]; then
REPO="https://github.com/lbryio/lbry-android.git"
REPO="https://github.com/lbryio/lbry-android-sdk.git"
fi
## Clone from $REPO and checkout $VERSION:

View file

@ -2,10 +2,10 @@
(
ANDROID_STUDIO_SDK=${ANDROID_STUDIO_SDK:-$HOME/Android/Sdk}
LBRY_ANDROID_HOME=${LBRY_ANDROID_HOME:-$HOME/git/vendor/lbryio/lbry-android}
LBRY_ANDROID_HOME=${LBRY_ANDROID_HOME:-$HOME/git/vendor/lbryio/lbry-android-sdk}
LBRY_ANDROID_BUILDOZER_HOME=${LBRY_ANDROID_BUILDOZER_HOME:-$LBRY_ANDROID_HOME/.buildozer}
LBRY_ANDROID_BUILDOZER_DOWNLOADS=${LBRY_ANDROID_BUILDOZER_DOWNLOADS:-$LBRY_ANDROID_HOME/.buildozer-downloads}
LBRY_ANDROID_REPO=${LBRY_ANDROID_REPO:-https://www.github.com/lbryio/lbry-android}
LBRY_ANDROID_REPO=${LBRY_ANDROID_REPO:-https://www.github.com/lbryio/lbry-android-sdk}
LBRY_ANDROID_IMAGE=${LBRY_ANDROID_IMAGE:-lbry-android:local}
## Logger utility:
@ -65,10 +65,10 @@
mkdir -p $LBRY_ANDROID_BUILDOZER_DOWNLOADS
exe sudo docker run --rm -it \
-v $LBRY_ANDROID_HOME:/src \
-v $LBRY_ANDROID_BUILDOZER_HOME:/home/lbry-android/.buildozer/ \
-v $LBRY_ANDROID_BUILDOZER_DOWNLOADS:/home/lbry-android/.buildozer-downloads/ \
-v $LBRY_ANDROID_BUILDOZER_HOME:/home/lbry-android-sdk/.buildozer/ \
-v $LBRY_ANDROID_BUILDOZER_DOWNLOADS:/home/lbry-android-sdk/.buildozer-downloads/ \
$LBRY_ANDROID_IMAGE \
/home/lbry-android/bin/setup
/home/lbry-android-sdk/bin/setup
fi
}
@ -96,9 +96,9 @@
mkdir -p $LBRY_ANDROID_HOME/.gradle
exe sudo docker run --rm -it \
-v $LBRY_ANDROID_HOME:/src \
-v $LBRY_ANDROID_BUILDOZER_HOME:/home/lbry-android/.buildozer/ \
-v $LBRY_ANDROID_HOME/.gradle:/home/lbry-android/.gradle/ \
-v $ANDROID_SDK_LICENSE:/home/lbry-android/.buildozer/android/platform/android-sdk-23/licenses/android-sdk-license \
-v $LBRY_ANDROID_BUILDOZER_HOME:/home/lbry-android-sdk/.buildozer/ \
-v $LBRY_ANDROID_HOME/.gradle:/home/lbry-android-sdk/.gradle/ \
-v $ANDROID_SDK_LICENSE:/home/lbry-android-sdk/.buildozer/android/platform/android-sdk-23/licenses/android-sdk-license \
$LBRY_ANDROID_IMAGE
}
@ -122,9 +122,9 @@
clean() {
exe sudo docker run --rm -it \
-v $LBRY_ANDROID_HOME:/src \
-v $LBRY_ANDROID_BUILDOZER_HOME:/home/lbry-android/.buildozer/ \
-v $LBRY_ANDROID_HOME/.gradle:/home/lbry-android/.gradle/ \
-v $ANDROID_SDK_LICENSE:/home/lbry-android/.buildozer/android/platform/android-sdk-23/licenses/android-sdk-license \
-v $LBRY_ANDROID_BUILDOZER_HOME:/home/lbry-android-sdk/.buildozer/ \
-v $LBRY_ANDROID_HOME/.gradle:/home/lbry-android-sdk/.gradle/ \
-v $ANDROID_SDK_LICENSE:/home/lbry-android-sdk/.buildozer/android/platform/android-sdk-23/licenses/android-sdk-license \
$LBRY_ANDROID_IMAGE /bin/bash -c "cd /src && buildozer android clean"
}
@ -164,4 +164,3 @@
echo "## - Builds the lbry-android apk"
fi
)

Binary file not shown.

Binary file not shown.

View file

@ -1,157 +0,0 @@
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;
}
}

View file

@ -1,123 +0,0 @@
package io.lbry.browser;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import org.kivy.android.PythonService;
import org.renpy.android.AssetExtract;
import org.renpy.android.ResourceManager;
public class LbrynetTestRunnerService extends PythonService {
public static String TAG = "LbrynetTestRunnerService";
public static LbrynetTestRunnerService serviceInstance;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Extract files
File app_root_file = new File(getAppRoot());
unpackData("private", app_root_file);
if (intent == null) {
intent = ServiceHelper.buildIntent(
getApplicationContext(), "", LbrynetTestRunnerService.class, "testrunnerservice");
}
serviceInstance = this;
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
serviceInstance = null;
super.onDestroy();
}
public void broadcastTestRunnerOutput(String output) {
Intent intent = new Intent();
intent.setAction(ServiceControlActivity.TEST_RUNNER_OUTPUT);
intent.putExtra("output", output);
sendBroadcast(intent);
}
public void unpackData(final String resource, File target) {
Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName());
// The version of data in memory and on disk.
ResourceManager resourceManager = new ResourceManager(getApplicationContext());
String data_version = resourceManager.getString(resource + "_version");
String disk_version = null;
Log.v(TAG, "Data version is " + data_version);
// If no version, no unpacking is necessary.
if (data_version == null) {
return;
}
// Check the current disk version, if any.
String filesDir = target.getAbsolutePath();
String disk_version_fn = filesDir + "/" + resource + ".version";
try {
byte buf[] = new byte[64];
InputStream is = new FileInputStream(disk_version_fn);
int len = is.read(buf);
disk_version = new String(buf, 0, len);
is.close();
} catch (Exception e) {
disk_version = "";
}
// If the disk data is out of date, extract it and write the
// version file.
// if (! data_version.equals(disk_version)) {
if (! data_version.equals(disk_version)) {
Log.v(TAG, "Extracting " + resource + " assets.");
recursiveDelete(target);
target.mkdirs();
AssetExtract ae = new AssetExtract(getApplicationContext());
if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) {
//toastError("Could not extract " + resource + " data.");
Log.e(TAG, "Could not extract " + resource + " data.");
}
try {
// Write .nomedia.
new File(target, ".nomedia").createNewFile();
// Write version file.
FileOutputStream os = new FileOutputStream(disk_version_fn);
os.write(data_version.getBytes());
os.close();
} catch (Exception e) {
Log.w("python", e);
}
}
}
public void recursiveDelete(File f) {
if (f.isDirectory()) {
for (File r : f.listFiles()) {
recursiveDelete(r);
}
}
f.delete();
}
public String getAppRoot() {
String app_root = getApplicationContext().getFilesDir().getAbsolutePath() + "/app";
return app_root;
}
}

View file

@ -1,7 +0,0 @@
package io.lbry.browser;
import androidx.core.content.FileProvider;
public class LocalFileProvider extends FileProvider {
}

View file

@ -1,875 +0,0 @@
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 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;
}
}
}

View file

@ -1,158 +0,0 @@
package io.lbry.browser;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.Html;
import android.text.Spanned;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
public class ServiceControlActivity extends Activity {
public static ServiceControlActivity activityInstance;
private IntentFilter intentFilter;
public static String TEST_RUNNER_OUTPUT = "io.lbry.browser.TEST_RUNNER_OUTPUT";
/**
* Flag which indicates whether or not the service is running. Will be updated in the
* onResume method.
*/
private boolean serviceRunning;
/**
* Button which will start or stop the service.
*/
private Button startStopButton;
private Button runTestsButton;
private TextView testRunnerOutput;
/**
* Service status text.
*/
private TextView serviceStatusText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_service_control);
intentFilter = new IntentFilter();
intentFilter.addAction(TEST_RUNNER_OUTPUT);
startStopButton = (Button) findViewById(R.id.btn_start_stop);
runTestsButton = (Button) findViewById(R.id.btn_run_tests);
serviceStatusText = (TextView) findViewById(R.id.text_service_status);
testRunnerOutput = (TextView) findViewById(R.id.test_runner_output);
startStopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (serviceRunning) {
ServiceHelper.stop(ServiceControlActivity.this, LbrynetService.class);
} else {
ServiceHelper.start(ServiceControlActivity.this, "", LbrynetService.class, "lbrynetservice");
}
serviceRunning = isServiceRunning(LbrynetService.class);
updateServiceStatus();
}
});
runTestsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean testsRunning = isServiceRunning(LbrynetTestRunnerService.class);
if (!testsRunning) {
ServiceHelper.start(
ServiceControlActivity.this, "", LbrynetTestRunnerService.class, "testrunnerservice");
}
}
});
}
@Override
public void onResume() {
super.onResume();
registerReceiver(testRunnerOutputReceiver, intentFilter);
activityInstance = this;
serviceRunning = isServiceRunning(LbrynetService.class);
updateServiceStatus();
}
private BroadcastReceiver testRunnerOutputReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (TEST_RUNNER_OUTPUT.equals(intent.getAction())) {
String output = intent.getStringExtra("output");
updateTestRunnerOutput(output);
}
}
};
@Override
public void onPause() {
unregisterReceiver(testRunnerOutputReceiver);
// set the activity instance to null on pause in order to prevent NullPointerException
// if the activity shuts down prematurely, for example
activityInstance = null;
super.onPause();
}
public void updateServiceStatus() {
new Handler().post(new Runnable() {
@Override
public void run() {
if (serviceRunning) {
startStopButton.setText(R.string.stop);
serviceStatusText.setText(R.string.running);
serviceStatusText.setTextColor(getResources().getColor(R.color.green));
} else {
startStopButton.setText(R.string.start);
serviceStatusText.setText(R.string.stopped);
serviceStatusText.setTextColor(getResources().getColor(R.color.red));
}
}
});
}
public void updateTestRunnerOutput(String output) {
testRunnerOutput.setText(formatTestRunnerOutput(output));
}
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;
}
public static Spanned formatTestRunnerOutput(String output) {
output = output.replace("[OK]", "<font color=\"#008000\">[OK]</font>");
output = output.replace("[ERROR]", "<font color=\"#ff0000\">[ERROR]</font>");
output = output.replace("[FAILURE]", "<font color=\"#cc0000\">[FAILURE]</font>");
return Html.fromHtml(output);
}
}

View file

@ -1,84 +0,0 @@
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.LbrynetService;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
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);
}
}

View file

@ -1,50 +0,0 @@
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.LbrynetService;
import io.lbry.browser.MainActivity;
import io.lbry.browser.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();
}
}
}

View file

@ -1,131 +0,0 @@
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.browser.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);
}
}
}

View file

@ -1,53 +0,0 @@
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);
}
}
}

View file

@ -1,313 +0,0 @@
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.browser.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()));
}
}

View file

@ -1,74 +0,0 @@
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.browser.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) {
}
}

View file

@ -1,47 +0,0 @@
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);
}
}
}

View file

@ -1,193 +0,0 @@
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;
}
}

View file

@ -1,473 +0,0 @@
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.LbrynetService;
import io.lbry.browser.R;
import io.lbry.browser.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));
}
}

View file

@ -1,37 +0,0 @@
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");
}
}
}

View file

@ -1,46 +0,0 @@
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;
}
}

View file

@ -1,17 +0,0 @@
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;
}
}
}

View file

@ -1,4 +1,4 @@
package io.lbry.browser;
package io.lbry.lbrysdk;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@ -12,12 +12,6 @@ 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 java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
@ -25,7 +19,7 @@ import java.util.Map;
import java.util.List;
import java.util.Random;
public class DownloadManager {
public final class DownloadManager {
private Context context;
private List<String> activeDownloads = new ArrayList<String>();
@ -56,9 +50,11 @@ public class DownloadManager {
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_NOTIFICATION_DELETED = "io.lbry.browser.ACTION_NOTIFICATION_DELETED";
public static final String ACTION_DOWNLOAD_EVENT = "io.lbry.browser.ACTION_DOWNLOAD_EVENT";
public static final String ACTION_START = "start";
@ -100,7 +96,7 @@ public class DownloadManager {
private void createNotificationGroup() {
if (!groupCreated) {
Intent intent = new Intent(context, NotificationDeletedReceiver.class);
Intent intent = new Intent(ACTION_NOTIFICATION_DELETED);
intent.putExtra(NOTIFICATION_ID_KEY, DOWNLOAD_NOTIFICATION_GROUP_ID);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, DOWNLOAD_NOTIFICATION_GROUP_ID, intent, 0);
@ -368,10 +364,6 @@ public class DownloadManager {
}
int notificationId = generateNotificationId();
if (MainActivity.downloadNotificationIds != null &&
!MainActivity.downloadNotificationIds.contains(notificationId)) {
MainActivity.downloadNotificationIds.add(notificationId);
}
downloadIdNotificationIdMap.put(id, notificationId);
return notificationId;
}

View file

@ -1,4 +1,4 @@
package io.lbry.browser;
package io.lbry.lbrysdk;
import android.app.Activity;
import android.app.Notification;
@ -54,7 +54,7 @@ import org.renpy.android.ResourceManager;
* @author akinwale
* @version 0.1
*/
public class LbrynetService extends PythonService {
public final class LbrynetService extends PythonService {
public static final int SERVICE_NOTIFICATION_GROUP_ID = 5;
@ -76,6 +76,8 @@ public class LbrynetService extends PythonService {
private static final int SDK_POLL_INTERVAL = 1000; // 1 second
private PendingIntent pendingContextIntent;
private BroadcastReceiver stopServiceReceiver;
private BroadcastReceiver downloadReceiver;
@ -132,16 +134,44 @@ public class LbrynetService extends PythonService {
};
registerReceiver(downloadReceiver, downloadFilter);
}
public void setPendingContextIntent(PendingIntent pendingIntent) {
this.pendingContextIntent = pendingIntent;
// update the notification with the context intent
Notification notification = buildNotification();
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);
}
private Notification buildNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
if (pendingContextIntent != null) {
builder.setContentIntent(pendingContextIntent);
}
Intent stopIntent = new Intent(ACTION_STOP_SERVICE);
PendingIntent stopPendingIntent = PendingIntent.getBroadcast(this, 0, stopIntent, 0);
String serviceDescription = "The LBRY service is running in the background.";
Notification notification = builder.setColor(ContextCompat.getColor(this, R.color.lbryGreen))
.setContentText(serviceDescription)
.setGroup(GROUP_SERVICE)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_lbry)
.setOngoing(true)
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopPendingIntent)
.build();
return notification;
}
@Override
protected void doStartForeground(Bundle extras) {
String serviceTitle = extras.getString("serviceTitle");
String serviceDescription = "The LBRY service is running in the background.";
Context context = getApplicationContext();
downloadManager = new DownloadManager(context);
downloadManager = new DownloadManager(this);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "LBRY Browser", NotificationManager.IMPORTANCE_LOW);
@ -150,33 +180,17 @@ public class LbrynetService extends PythonService {
notificationManager.createNotificationChannel(channel);
}
Intent contextIntent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contextIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent stopIntent = new Intent(ACTION_STOP_SERVICE);
PendingIntent stopPendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent, 0);
// Create the notification group
NotificationCompat.Builder groupBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
NotificationCompat.Builder groupBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
groupBuilder.setContentTitle("LBRY Browser")
.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
.setColor(ContextCompat.getColor(this, R.color.lbryGreen))
.setSmallIcon(R.drawable.ic_lbry)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setGroup(GROUP_SERVICE)
.setGroupSummary(true);
notificationManager.notify(SERVICE_NOTIFICATION_GROUP_ID, groupBuilder.build());
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
Notification notification = builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
.setContentIntent(pendingIntent)
.setContentTitle(serviceTitle)
.setContentText(serviceDescription)
.setGroup(GROUP_SERVICE)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_lbry)
.setOngoing(true)
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopPendingIntent)
.build();
Notification notification = buildNotification();
startForeground(1, notification);
}

View file

@ -1,4 +1,4 @@
package io.lbry.browser;
package io.lbry.lbrysdk;
import android.content.Intent;
import android.content.Context;
@ -12,12 +12,14 @@ import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ServiceHelper {
public final class ServiceHelper {
private static final String TAG = "io.lbry.browser.ServiceHelper";
private static final String HEADERS_ASSET_KEY = "headersAssetInitialized";
public static final String SHARED_PREFERENCES_NAME = "LBRY";
public static Intent buildIntent(Context ctx, String pythonServiceArgument, Class serviceClass, String pythonName) {
Intent intent = new Intent(ctx, serviceClass);
String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
@ -34,7 +36,7 @@ public class ServiceHelper {
public static void start(final Context ctx, String pythonServiceArgument, Class serviceClass, String pythonName) {
// always check initial headers status before starting the service
final SharedPreferences sp = ctx.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final SharedPreferences sp = ctx.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
boolean initHeaders = sp.getBoolean(HEADERS_ASSET_KEY, false);
if (initHeaders) {
// initial headers asset copy already done. simply start the service

View file

@ -1,4 +1,4 @@
package io.lbry.browser;
package io.lbry.lbrysdk;
import android.content.Context;
import android.content.SharedPreferences;
@ -7,7 +7,7 @@ import android.security.KeyPairGeneratorSpec;
import android.util.Base64;
import android.util.Log;
import io.lbry.browser.BuildConfig;
import io.lbry.lbrysdk.BuildConfig;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

View file

@ -18,8 +18,8 @@ from lbry.extras.daemon.loggly_handler import get_loggly_handler
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
lbrynet_android_utils = autoclass('io.lbry.browser.Utils')
service = autoclass('io.lbry.browser.LbrynetService').serviceInstance
lbrynet_android_utils = autoclass('io.lbry.lbrysdk.Utils')
service = autoclass('io.lbry.lbrysdk.LbrynetService').serviceInstance
platform.platform = lambda: 'Android %s (API %s)' % (lbrynet_android_utils.getAndroidRelease(), lbrynet_android_utils.getAndroidSdk())
build_info.BUILD = 'dev' if lbrynet_android_utils.isDebug() else 'release'

View file

@ -1,11 +1,12 @@
__version__ = "0.13.0"
# going forward, this should match sdk version
__version__ = "0.58.0"
class ServiceApp(App):
def build(self):
from jnius import autoclass
Intent = autoclass('android.content.Intent')
LbrynetService = autoclass('io.lbry.browser.LbrynetService')
LbrynetService = autoclass('io.lbry.lbrysdk.LbrynetService')
if __name__ == '__main__':
ServiceApp().run()

View file

@ -1,76 +0,0 @@
import sys
import StringIO
from twisted.trial.runner import (
TestLoader,
TrialRunner
)
from twisted.trial.reporter import TreeReporter
from twisted.plugin import getPlugins, IPlugin
from jnius import autoclass
import lbrynet.tests
from os import listdir
str_stream = StringIO.StringIO()
serviceClass = autoclass('io.lbry.browser.LbrynetTestRunnerService')
def update_output_in_activity(str):
service = serviceClass.serviceInstance
if service is not None:
service.broadcastTestRunnerOutput(str)
class AndroidTestReporter(TreeReporter):
def addSuccess(self, test):
super(TreeReporter, self).addSuccess(test)
self.endLine('[OK]', self.SUCCESS)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addError(self, *args):
super(TreeReporter, self).addError(*args)
self.endLine('[ERROR]', self.ERROR)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addFailure(self, *args):
super(TreeReporter, self).addFailure(*args)
self.endLine('[FAIL]', self.FAILURE)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addSkip(self, *args):
super(TreeReporter, self).addSkip(*args)
self.endLine('[SKIPPED]', self.SKIP)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addExpectedFailure(self, *args):
super(TreeReporter, self).addExpectedFailure(*args)
self.endLine('[TODO]', self.TODO)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addUnexpectedSuccess(self, *args):
super(TreeReporter, self).addUnexpectedSuccess(*args)
self.endLine('[SUCCESS!?!]', self.TODONE)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def startTest(self, test):
super(AndroidTestReporter, self).startTest(test)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def endLine(self, message, color):
super(AndroidTestReporter, self).endLine(message, color)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def str_to_basic_html(value):
return value.replace("\n", "<br>").replace(" ", '&nbsp;')
def run():
loader = TestLoader();
suite = loader.loadPackage(lbrynet.tests, True)
runner = TrialRunner(AndroidTestReporter)
runner.stream = str_stream
passFail = not runner.run(suite).wasSuccessful()
print str_stream.getvalue()
sys.exit(passFail)
if __name__ == '__main__':
run()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Some files were not shown because too many files have changed in this diff Show more