New build #125

Merged
akinwale merged 4 commits from new-build into master 2020-02-27 09:05:01 +01:00
382 changed files with 20064 additions and 83 deletions

64
.gitignore vendored
View file

@ -1,4 +1,66 @@
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifact
*.jsbundle
# CocoaPods
/ios/Pods/
# Other Files
android/app/google-services.json
*.log
.vagrant
android/app/build/*
android/bin

14
__tests__/App-test.js Normal file
View file

@ -0,0 +1,14 @@
/**
* @format
*/
import 'react-native';
import React from 'react';
import App from '../App';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
renderer.create(<App />);
});

67
android/.gitignore vendored Normal file
View file

@ -0,0 +1,67 @@
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifact
*.jsbundle
# CocoaPods
/ios/Pods/
# Other Files
app/google-services.json
*.log
.vagrant
*.hprof
app/build
app/bin

55
android/app/BUCK Normal file
View file

@ -0,0 +1,55 @@
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
lib_deps = []
create_aar_targets(glob(["libs/*.aar"]))
create_jar_targets(glob(["libs/*.jar"]))
android_library(
name = "all-libs",
exported_deps = lib_deps,
)
android_library(
name = "app-code",
srcs = glob([
"src/main/java/**/*.java",
]),
deps = [
":all-libs",
":build_config",
":res",
],
)
android_build_config(
name = "build_config",
package = "com.lbryandroid",
)
android_resource(
name = "res",
package = "com.lbryandroid",
res = "src/main/res",
)
android_binary(
name = "app",
keystore = "//android/keystores:debug",
manifest = "src/main/AndroidManifest.xml",
package_type = "debug",
deps = [
":app-code",
],
)

248
android/app/build.gradle Normal file
View file

@ -0,0 +1,248 @@
apply plugin: "com.android.application"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation
* entryFile: "index.android.js",
*
* // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
task buildReactNativeBundle(type:Exec) {
println("Building React Native bundle")
workingDir new File(rootProject.projectDir, '../')
commandLine './bundle-android.sh'
}
preBuild.dependsOn buildReactNativeBundle
task printVersionName {
doLast {
println android.defaultConfig.versionName
}
}
project.ext.react = [
entryFile: "index.js",
enableHermes: false, // clean and rebuild if changing
]
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore.
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
/**
* Whether to enable the Hermes VM.
*
* This should be set on project.ext.react and mirrored here. If it is not set
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/
def enableHermes = project.ext.react.get("enableHermes", false);
android {
compileSdkVersion rootProject.ext.compileSdkVersion
flavorDimensions "default"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "io.lbry.browser"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1302
versionName "0.13.2"
missingDimensionStrategy 'react-native-camera', 'general'
multiDexEnabled true
}
dexOptions {
jumboMode true
}
productFlavors {
__32bit {
ndk {
abiFilter "armeabi-v7a"
}
}
__64bit {
ndk {
abiFilter "arm64-v8a"
}
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://facebook.github.io/react-native/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
def versionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
defaultConfig.versionCode * 10 + versionCodes.get(abi)
}
}
}
}
dependencies {
implementation project(':@react-native-community_async-storage')
implementation project(':react-native-camera')
implementation project(':react-native-exception-handler')
implementation project(':react-native-fast-image')
implementation project(':react-native-fs')
implementation project(':react-native-gesture-handler')
implementation project(':react-native-reanimated')
implementation project(':react-native-snackbar')
implementation project(':react-native-video')
implementation project(':react-native-webview')
implementation project(':rn-fetch-blob')
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.media:media:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'com.facebook.react:react-native:0.61.5'
implementation 'com.facebook.fresco:fresco:1.9.0'
implementation 'com.facebook.fresco:animated-gif:1.9.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.google.firebase:firebase-analytics:17.2.1'
implementation 'com.google.android.gms:play-services-base:17.1.0'
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation 'com.facebook.fresco:animated-base-support:1.3.0'
implementation 'com.facebook.fresco:animated-gif:1.10.0'
implementation 'com.google.firebase:firebase-messaging:20.1.0'
__32bitImplementation files('libs/lbrysdk-0.61.0-release__arm.aar')
__64bitImplementation files('libs/lbrysdk-0.61.0-release__arm64.aar')
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply plugin: 'com.google.gms.google-services'
com.google.gms.googleservices.GoogleServicesPlugin.config.disableVersionCheck = true

View file

@ -0,0 +1,19 @@
"""Helper definitions to glob .aar and .jar targets"""
def create_aar_targets(aarfiles):
for aarfile in aarfiles:
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
lib_deps.append(":" + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
def create_jar_targets(jarfiles):
for jarfile in jarfiles:
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
lib_deps.append(":" + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)

BIN
android/app/debug.keystore Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

10
android/app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,10 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:

View file

@ -0,0 +1,85 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.lbry.browser"
android:installLocation="auto">
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true"
android:xlargeScreens="true"
/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:label="@string/app_name"
android:icon="@drawable/icon"
android:allowBackup="true"
android:theme="@style/LbryAppTheme"
android:hardwareAccelerated="true"
android:usesCleartextTraffic="true">
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_lbry" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/lbryGreen" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id"/>
<meta-data android:name="wakelock" android:value="0"/>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<activity android:name="io.lbry.browser.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/LbryAppTheme"
android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="portrait"
android:launchMode="singleInstance"
>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lbry" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="io.lbry.browser.receivers.NotificationDeletedReceiver" />
<service
android:name="io.lbry.browser.LbrynetMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<provider
android:name="io.lbry.browser.LocalFileProvider"
android:authorities="io.lbry.browser.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,419 @@
package io.lbry.browser;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import io.lbry.browser.receivers.NotificationDeletedReceiver;
import io.lbry.lbrysdk.LbrynetService;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Random;
public class DownloadManager {
private Context context;
private List<String> activeDownloads = new ArrayList<String>();
private List<String> completedDownloads = new ArrayList<String>();
private Map<String, String> downloadIdOutpointsMap = new HashMap<String, String>();
// maintain a map of uris to writtenBytes, so that we check if it's changed and don't flood RN with update events every 500ms
private Map<String, Double> writtenDownloadBytes = new HashMap<String, Double>();
private HashMap<Integer, NotificationCompat.Builder> builders = new HashMap<Integer, NotificationCompat.Builder>();
private HashMap<String, Integer> downloadIdNotificationIdMap = new HashMap<String, Integer>();
private HashMap<String, Boolean> stoppedDownloadsMap = new HashMap<String, Boolean>();
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#");
private static final int MAX_FILENAME_LENGTH = 20;
private static final int MAX_PROGRESS = 100;
private static final String GROUP_DOWNLOADS = "io.lbry.browser.GROUP_DOWNLOADS";
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.DOWNLOADS_NOTIFICATION_CHANNEL";
private static boolean channelCreated = false;
private static NotificationCompat.Builder groupBuilder = null;
public static final String NOTIFICATION_ID_KEY = "io.lbry.browser.notificationId";
public static final String ACTION_DOWNLOAD_EVENT = "io.lbry.browser.ACTION_DOWNLOAD_EVENT";
public static final String ACTION_START = "start";
public static final String ACTION_COMPLETE = "complete";
public static final String ACTION_UPDATE = "update";
public static final int DOWNLOAD_NOTIFICATION_GROUP_ID = 20;
public static boolean groupCreated = false;
public DownloadManager(Context context) {
this.context = context;
}
private int generateNotificationId() {
int id = 0;
Random random = new Random();
do {
id = random.nextInt();
} while (id < 1000);
return id;
}
private void createNotificationChannel() {
// Only applies to Android 8.0 Oreo (API Level 26) or higher
if (!channelCreated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "LBRY Downloads", NotificationManager.IMPORTANCE_LOW);
channel.setDescription("LBRY file downloads");
channel.setSound(null, null);
notificationManager.createNotificationChannel(channel);
}
}
private void createNotificationGroup() {
if (!groupCreated) {
Intent intent = new Intent(context, NotificationDeletedReceiver.class);
intent.putExtra(NOTIFICATION_ID_KEY, DOWNLOAD_NOTIFICATION_GROUP_ID);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, DOWNLOAD_NOTIFICATION_GROUP_ID, intent, 0);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
groupBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
groupBuilder.setContentTitle("Active LBRY downloads")
// contentText will be displayed if there are no notifications in the group
.setContentText("There are no active LBRY downloads.")
.setSmallIcon(android.R.drawable.stat_sys_download)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setGroup(GROUP_DOWNLOADS)
.setGroupSummary(true)
.setDeleteIntent(pendingIntent);
notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build());
groupCreated = true;
}
}
public static PendingIntent getLaunchPendingIntent(String uri, Context context) {
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent intent = PendingIntent.getActivity(context, 0, launchIntent, 0);
return intent;
}
public void updateWrittenBytesForDownload(String id, double writtenBytes) {
if (!writtenDownloadBytes.containsKey(id)) {
writtenDownloadBytes.put(id, writtenBytes);
}
}
public double getWrittenBytesForDownload(String id) {
if (writtenDownloadBytes.containsKey(id)) {
return writtenDownloadBytes.get(id);
}
return -1;
}
public void clearWrittenBytesForDownload(String id) {
if (writtenDownloadBytes.containsKey(id)) {
writtenDownloadBytes.remove(id);
}
}
private Intent getDeleteDownloadIntent(String uri) {
Intent intent = new Intent();
intent.setAction(LbrynetService.ACTION_DELETE_DOWNLOAD);
intent.putExtra("uri", uri);
intent.putExtra("nativeDelete", true);
return intent;
}
public void startDownload(String id, String filename, String outpoint) {
if (filename == null || filename.trim().length() == 0) {
return;
}
synchronized (this) {
if (!isDownloadActive(id)) {
activeDownloads.add(id);
downloadIdOutpointsMap.put(id, outpoint);
}
createNotificationChannel();
createNotificationGroup();
PendingIntent stopDownloadIntent = PendingIntent.getBroadcast(context, 0, getDeleteDownloadIntent(id), PendingIntent.FLAG_CANCEL_CURRENT);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
// The file URI is used as the unique ID
builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
.setContentIntent(getLaunchPendingIntent(id, context))
.setContentTitle(String.format("Downloading %s", truncateFilename(filename)))
.setGroup(GROUP_DOWNLOADS)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setProgress(MAX_PROGRESS, 0, false)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setOngoing(true)
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopDownloadIntent);
int notificationId = getNotificationId(id);
downloadIdNotificationIdMap.put(id, notificationId);
builders.put(notificationId, builder);
notificationManager.notify(notificationId, builder.build());
if (groupCreated && groupBuilder != null) {
groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build());
}
}
}
public void updateDownload(String id, String filename, double writtenBytes, double totalBytes) {
if (filename == null || filename.trim().length() == 0) {
return;
}
synchronized (this) {
createNotificationChannel();
createNotificationGroup();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
NotificationCompat.Builder builder = null;
int notificationId = getNotificationId(id);
if (builders.containsKey(notificationId)) {
builder = builders.get(notificationId);
} else {
PendingIntent stopDownloadIntent = PendingIntent.getBroadcast(context, 0, getDeleteDownloadIntent(id), PendingIntent.FLAG_CANCEL_CURRENT);
builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
.setContentTitle(String.format("Downloading %s", truncateFilename(filename)))
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopDownloadIntent);
builders.put(notificationId, builder);
}
double progress = (writtenBytes / totalBytes) * 100;
builder.setContentIntent(getLaunchPendingIntent(id, context))
.setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes)))
.setGroup(GROUP_DOWNLOADS)
.setProgress(MAX_PROGRESS, new Double(progress).intValue(), false)
.setSmallIcon(android.R.drawable.stat_sys_download);
notificationManager.notify(notificationId, builder.build());
if (progress >= MAX_PROGRESS) {
builder.setContentTitle(String.format("Downloaded %s", truncateFilename(filename, 30)))
.setContentText(String.format("%s", formatBytes(totalBytes)))
.setGroup(GROUP_DOWNLOADS)
.setProgress(0, 0, false)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setOngoing(false);
builder.mActions.clear();
notificationManager.notify(notificationId, builder.build());
if (downloadIdNotificationIdMap.containsKey(id)) {
downloadIdNotificationIdMap.remove(id);
}
if (builders.containsKey(notificationId)) {
builders.remove(notificationId);
}
// If there are no more downloads and the group exists, set the icon to stop animating
if (groupCreated && groupBuilder != null && downloadIdNotificationIdMap.size() == 0) {
groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build());
}
completeDownload(id, filename, totalBytes);
}
}
}
public void completeDownload(String id, String filename, double totalBytes) {
synchronized (this) {
if (isDownloadActive(id)) {
activeDownloads.remove(id);
}
if (!isDownloadCompleted(id)) {
completedDownloads.add(id);
}
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
NotificationCompat.Builder builder = null;
int notificationId = getNotificationId(id);
if (builders.containsKey(notificationId)) {
builder = builders.get(notificationId);
} else {
builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setPriority(NotificationCompat.PRIORITY_LOW);
builders.put(notificationId, builder);
}
builder.setContentTitle(String.format("Downloaded %s", truncateFilename(filename, 30)))
.setContentText(String.format("%s", formatBytes(totalBytes)))
.setGroup(GROUP_DOWNLOADS)
.setProgress(0, 0, false)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setOngoing(false);
builder.mActions.clear();
notificationManager.notify(notificationId, builder.build());
// If there are no more downloads and the group exists, set the icon to stop animating
checkGroupDownloadIcon(notificationManager);
}
}
public void abortDownload(String id) {
synchronized (this) {
if (downloadIdNotificationIdMap.containsKey(id)) {
removeDownloadNotification(id);
}
activeDownloads.remove(id);
}
}
public boolean isDownloadActive(String id) {
return (activeDownloads.contains(id));
}
public boolean isDownloadCompleted(String id) {
return (completedDownloads.contains(id));
}
public boolean hasActiveDownloads() {
return activeDownloads.size() > 0;
}
public List<String> getActiveDownloads() {
return activeDownloads;
}
public List<String> getCompletedDownloads() {
return completedDownloads;
}
public String getOutpointForDownload(String uri) {
if (downloadIdOutpointsMap.containsKey(uri)) {
return downloadIdOutpointsMap.get(uri);
}
return null;
}
public void deleteDownloadUri(String uri) {
synchronized (this) {
activeDownloads.remove(uri);
completedDownloads.remove(uri);
if (downloadIdOutpointsMap.containsKey(uri)) {
downloadIdOutpointsMap.remove(uri);
}
if (downloadIdNotificationIdMap.containsKey(uri)) {
removeDownloadNotification(uri);
}
}
}
private void removeDownloadNotification(String id) {
int notificationId = downloadIdNotificationIdMap.get(id);
if (downloadIdNotificationIdMap.containsKey(id)) {
downloadIdNotificationIdMap.remove(id);
}
if (builders.containsKey(notificationId)) {
builders.remove(notificationId);
}
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
NotificationCompat.Builder builder = builders.get(notificationId);
notificationManager.cancel(notificationId);
checkGroupDownloadIcon(notificationManager);
if (builders.values().size() == 0) {
notificationManager.cancel(DOWNLOAD_NOTIFICATION_GROUP_ID);
groupCreated = false;
}
}
private int getNotificationId(String id) {
if (downloadIdNotificationIdMap.containsKey(id)) {
return downloadIdNotificationIdMap.get(id);
}
int notificationId = generateNotificationId();
if (MainActivity.downloadNotificationIds != null &&
!MainActivity.downloadNotificationIds.contains(notificationId)) {
MainActivity.downloadNotificationIds.add(notificationId);
}
downloadIdNotificationIdMap.put(id, notificationId);
return notificationId;
}
private void checkGroupDownloadIcon(NotificationManagerCompat notificationManager) {
if (groupCreated && groupBuilder != null && downloadIdNotificationIdMap.size() == 0) {
groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build());
}
}
private static String formatBytes(double bytes)
{
if (bytes < 1048576) { // < 1MB
return String.format("%s KB", DECIMAL_FORMAT.format(bytes / 1024.0));
}
if (bytes < 1073741824) { // < 1GB
return String.format("%s MB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0)));
}
return String.format("%s GB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0 * 1024.0)));
}
private static String truncateFilename(String filename, int alternateMaxLength) {
int maxLength = alternateMaxLength > 0 ? alternateMaxLength : MAX_FILENAME_LENGTH;
if (filename.length() < maxLength) {
return filename;
}
// Get the extension
int dotIndex = filename.lastIndexOf(".");
if (dotIndex > -1) {
String extension = filename.substring(dotIndex);
return String.format("%s...%s", filename.substring(0, maxLength - extension.length() - 4), extension);
}
return String.format("%s...", filename.substring(0, maxLength - 3));
}
private static String truncateFilename(String filename) {
return truncateFilename(filename, 0);
}
}

View file

@ -0,0 +1,157 @@
package io.lbry.browser;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import android.util.Log;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import io.lbry.browser.reactmodules.UtilityModule;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class LbrynetMessagingService extends FirebaseMessagingService {
private static final String TAG = "LbrynetMessagingService";
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.LBRY_ENGAGEMENT_CHANNEL";
private static final String TYPE_SUBSCRIPTION = "subscription";
private static final String TYPE_REWARD = "reward";
private static final String TYPE_INTERESTS = "interests";
private static final String TYPE_CREATOR = "creator";
private FirebaseAnalytics firebaseAnalytics;
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
if (firebaseAnalytics == null) {
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
}
Map<String, String> payload = remoteMessage.getData();
if (payload != null) {
String type = payload.get("type");
String url = payload.get("target");
String title = payload.get("title");
String body = payload.get("body");
String name = payload.get("name"); // notification name
if (type != null && getEnabledTypes().indexOf(type) > -1 && body != null && body.trim().length() > 0) {
// only log the receive event for valid notifications received
if (firebaseAnalytics != null) {
Bundle bundle = new Bundle();
bundle.putString("name", name);
firebaseAnalytics.logEvent("lbry_notification_receive", bundle);
}
sendNotification(title, body, type, url, name);
}
}
}
@Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(token);
}
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM InstanceID token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private void sendRegistrationToServer(String token) {
// TODO: Implement this method to send token to your app server.
}
/**
* Create and show a simple notification containing the received FCM message.
*
* @param messageBody FCM message body received.
*/
private void sendNotification(String title, String messageBody, String type, String url, String name) {
//Intent intent = new Intent(this, MainActivity.class);
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (url == null) {
if (TYPE_REWARD.equals(type)) {
url = "lbry://?rewards";
} else {
// default to home page
url = "lbry://?discover";
}
}
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
launchIntent.putExtra("notification_name", name);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, launchIntent, PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setColor(ContextCompat.getColor(this, R.color.lbryGreen))
.setSmallIcon(R.drawable.ic_lbry)
.setContentTitle(title)
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "LBRY Engagement", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
notificationManager.notify(9898, notificationBuilder.build());
}
public List<String> getEnabledTypes() {
SharedPreferences sp = getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
List<String> enabledTypes = new ArrayList<String>();
if (sp.getBoolean(UtilityModule.RECEIVE_SUBSCRIPTION_NOTIFICATIONS, true)) {
enabledTypes.add(TYPE_SUBSCRIPTION);
}
if (sp.getBoolean(UtilityModule.RECEIVE_REWARD_NOTIFICATIONS, true)) {
enabledTypes.add(TYPE_REWARD);
}
if (sp.getBoolean(UtilityModule.RECEIVE_INTERESTS_NOTIFICATIONS, true)) {
enabledTypes.add(TYPE_INTERESTS);
}
if (sp.getBoolean(UtilityModule.RECEIVE_CREATOR_NOTIFICATIONS, true)) {
enabledTypes.add(TYPE_CREATOR);
}
return enabledTypes;
}
}

View file

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

View file

@ -0,0 +1,877 @@
package io.lbry.browser;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.Manifest;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.Settings;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.widget.Toast;
import com.azendoo.reactnativesnackbar.SnackbarPackage;
import com.brentvatne.react.ReactVideoPackage;
import com.dylanvann.fastimage.FastImageViewPackage;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.ReactRootView;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableNativeArray;
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.soloader.SoLoader;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.rnfs.RNFSPackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.swmansion.reanimated.ReanimatedPackage;
import com.RNFetchBlob.RNFetchBlobPackage;
import io.lbry.browser.reactpackages.LbryReactPackage;
import io.lbry.browser.reactmodules.BackgroundMediaModule;
import io.lbry.lbrysdk.LbrynetService;
import io.lbry.lbrysdk.ServiceHelper;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;
import org.reactnative.camera.RNCameraPackage;
public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private static Activity currentActivity = null;
private static final int OVERLAY_PERMISSION_REQ_CODE = 101;
private static final int STORAGE_PERMISSION_REQ_CODE = 201;
private static final int PHONE_STATE_PERMISSION_REQ_CODE = 202;
private static final int RECEIVE_SMS_PERMISSION_REQ_CODE = 203;
public static final int DOCUMENT_PICKER_RESULT_CODE = 301;
private BroadcastReceiver notificationsReceiver;
private BroadcastReceiver smsReceiver;
private BroadcastReceiver stopServiceReceiver;
private BroadcastReceiver downloadEventReceiver;
private FirebaseAnalytics firebaseAnalytics;
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
public static final String SHARED_PREFERENCES_NAME = "LBRY";
public static final String SALT_KEY = "salt";
public static final String DEVICE_ID_KEY = "deviceId";
public static final String SOURCE_NOTIFICATION_ID_KEY = "sourceNotificationId";
public static final String SETTING_KEEP_DAEMON_RUNNING = "keepDaemonRunning";
public static List<Integer> downloadNotificationIds = new ArrayList<Integer>();
/**
* Flag which indicates whether or not the service is running. Will be updated in the
* onResume method.
*/
private boolean serviceRunning;
private boolean receivedStopService;
private PermissionListener permissionListener;
protected String getMainComponentName() {
return "LBRYApp";
}
public static LaunchTiming CurrentLaunchTiming;
@Override
protected void onCreate(Bundle savedInstanceState) {
CurrentLaunchTiming = new LaunchTiming(new Date());
super.onCreate(savedInstanceState);
currentActivity = this;
SoLoader.init(this, false);
// Register the stop service receiver (so that we close the activity if the user requests the service to stop)
registerStopReceiver();
// Register SMS receiver for handling verification texts
registerSmsReceiver();
// Register the receiver to emit download events
registerDownloadEventReceiver();
// Start the daemon service if it is not started
serviceRunning = isServiceRunning(LbrynetService.class);
if (!serviceRunning) {
CurrentLaunchTiming.setColdStart(true);
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
}
checkNotificationOpenIntent(getIntent());
mReactRootView = new RNGestureHandlerEnabledRootView(this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackage(new MainReactPackage())
.addPackage(new AsyncStoragePackage())
.addPackage(new FastImageViewPackage())
.addPackage(new RNCWebViewPackage())
.addPackage(new ReactVideoPackage())
.addPackage(new ReanimatedPackage())
.addPackage(new RNCameraPackage())
.addPackage(new RNFetchBlobPackage())
.addPackage(new RNFSPackage())
.addPackage(new RNGestureHandlerPackage())
.addPackage(new SnackbarPackage())
.addPackage(new LbryReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
mReactRootView.startReactApplication(mReactInstanceManager, "LBRYApp", null);
registerNotificationsReceiver();
setContentView(mReactRootView);
}
private void checkNotificationOpenIntent(Intent intent) {
if (intent != null) {
String notificationName = intent.getStringExtra("notification_name");
if (notificationName != null) {
logNotificationOpen(notificationName);
}
}
}
private void logNotificationOpen(String name) {
if (firebaseAnalytics == null) {
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
}
Bundle bundle = new Bundle();
bundle.putString("name", name);
firebaseAnalytics.logEvent("lbry_notification_open", bundle);
}
private void registerDownloadEventReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_EVENT);
downloadEventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String downloadAction = intent.getStringExtra("action");
String uri = intent.getStringExtra("uri");
String outpoint = intent.getStringExtra("outpoint");
String fileInfoJson = intent.getStringExtra("file_info");
if (uri == null || outpoint == null || (fileInfoJson == null && !"abort".equals(downloadAction))) {
return;
}
String eventName = null;
WritableMap params = Arguments.createMap();
params.putString("uri", uri);
params.putString("outpoint", outpoint);
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if ("abort".equals(downloadAction)) {
eventName = "onDownloadAborted";
if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
}
return;
}
try {
JSONObject json = new JSONObject(fileInfoJson);
WritableMap fileInfo = JSONObjectToMap(json);
params.putMap("fileInfo", fileInfo);
if (DownloadManager.ACTION_UPDATE.equals(downloadAction)) {
double progress = intent.getDoubleExtra("progress", 0);
params.putDouble("progress", progress);
eventName = "onDownloadUpdated";
} else {
eventName = (DownloadManager.ACTION_START.equals(downloadAction)) ? "onDownloadStarted" : "onDownloadCompleted";
}
if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
}
} catch (JSONException ex) {
// pass
}
}
};
registerReceiver(downloadEventReceiver, intentFilter);
}
private void registerStopReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(LbrynetService.ACTION_STOP_SERVICE);
stopServiceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
MainActivity.this.receivedStopService = true;
MainActivity.this.finish();
}
};
registerReceiver(stopServiceReceiver, intentFilter);
}
private void registerNotificationsReceiver() {
// Background media receiver
IntentFilter filter = new IntentFilter();
filter.addAction(BackgroundMediaModule.ACTION_PLAY);
filter.addAction(BackgroundMediaModule.ACTION_PAUSE);
notificationsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
if (BackgroundMediaModule.ACTION_PLAY.equals(action)) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onBackgroundPlayPressed", null);
}
if (BackgroundMediaModule.ACTION_PAUSE.equals(action)) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onBackgroundPausePressed", null);
}
}
}
};
registerReceiver(notificationsReceiver, filter);
}
public void registerSmsReceiver() {
if (!hasPermission(Manifest.permission.RECEIVE_SMS, this)) {
// don't create the receiver if we don't have the read sms permission
return;
}
IntentFilter smsFilter = new IntentFilter();
smsFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
smsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Get the message
Bundle bundle = intent.getExtras();
if (bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
if (pdus != null && pdus.length > 0) {
SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdus[0]);
String text = sms.getMessageBody();
if (text == null || text.trim().length() == 0) {
return;
}
// Retrieve verification code from the text message if it contains
// the strings "lbry", "verification code" and the colon (following the expected format)
text = text.toLowerCase();
if (text.indexOf("lbry") > -1 && text.indexOf("verification code") > -1 && text.indexOf(":") > -1) {
String code = text.substring(text.lastIndexOf(":") + 1).trim();
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
WritableMap params = Arguments.createMap();
params.putString("code", code);
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onVerificationCodeReceived", params);
}
}
}
}
}
};
registerReceiver(smsReceiver, smsFilter);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// SYSTEM_ALERT_WINDOW permission not granted...
}
}
}
if (requestCode == DOCUMENT_PICKER_RESULT_CODE) {
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
if (resultCode == RESULT_OK) {
Uri fileUri = data.getData();
String filePath = getRealPathFromURI_API19(this, fileUri);
WritableMap params = Arguments.createMap();
params.putString("path", filePath);
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onDocumentPickerFilePicked", params);
} else if (resultCode == RESULT_CANCELED) {
// user canceled or request failed
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onDocumentPickerCanceled", null);
}
}
}
}
public static Activity getActivity() {
Activity activity = new Activity();
activity = currentActivity;
return activity;
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
switch (requestCode) {
case STORAGE_PERMISSION_REQ_CODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (BuildConfig.DEBUG && !Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
}
if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onStoragePermissionGranted", null);
}
} else {
// Permission not granted
/*Toast.makeText(this,
"LBRY requires access to your device storage to be able to download files and media." +
" Please enable the storage permission and restart the app.", Toast.LENGTH_LONG).show();*/
if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onStoragePermissionRefused", null);
}
}
break;
case PHONE_STATE_PERMISSION_REQ_CODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted. Emit an onPhoneStatePermissionGranted event
if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onPhoneStatePermissionGranted", null);
}
} else {
// Permission not granted. Simply show a message.
Toast.makeText(this,
"No permission granted to read your device state. Rewards cannot be claimed.", Toast.LENGTH_LONG).show();
}
break;
case RECEIVE_SMS_PERMISSION_REQ_CODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted. Emit an onPhoneStatePermissionGranted event
if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onReceiveSmsPermissionGranted", null);
}
// register the receiver
if (smsReceiver == null) {
registerSmsReceiver();
}
} else {
// Permission not granted. Simply show a message.
Toast.makeText(this,
"No permission granted to receive your SMS messages. You may have to enter the verification code manually.",
Toast.LENGTH_LONG).show();
}
break;
}
if (permissionListener != null) {
permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public static String acquireDeviceId(Context context) {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String id = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
id = telephonyManager.getImei(); // GSM
if (id == null) {
id = telephonyManager.getMeid(); // CDMA
}
} else {
id = telephonyManager.getDeviceId();
}
} catch (SecurityException ex) {
// Maybe the permission was not granted? Try to acquire permission
checkPhoneStatePermission(context);
} catch (Exception ex) {
// id could not be obtained. Display a warning that rewards cannot be claimed.
}
if (id == null || id.trim().length() == 0) {
Toast.makeText(context, "Rewards cannot be claimed because we could not identify your device.", Toast.LENGTH_LONG).show();
}
SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(DEVICE_ID_KEY, id);
editor.commit();
return id;
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
SharedPreferences sp = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
serviceRunning = isServiceRunning(LbrynetService.class);
if (!serviceRunning) {
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
}
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
// check service running setting and end it here
SharedPreferences sp = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
boolean shouldKeepDaemonRunning = sp.getBoolean(SETTING_KEEP_DAEMON_RUNNING, true);
if (!shouldKeepDaemonRunning) {
serviceRunning = isServiceRunning(LbrynetService.class);
if (serviceRunning) {
ServiceHelper.stop(this, LbrynetService.class);
}
}
if (notificationsReceiver != null) {
unregisterReceiver(notificationsReceiver);
notificationsReceiver = null;
}
if (smsReceiver != null) {
unregisterReceiver(smsReceiver);
smsReceiver = null;
}
if (downloadEventReceiver != null) {
unregisterReceiver(downloadEventReceiver);
downloadEventReceiver = null;
}
if (stopServiceReceiver != null) {
unregisterReceiver(stopServiceReceiver);
stopServiceReceiver = null;
}
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.cancel(BackgroundMediaModule.NOTIFICATION_ID);
notificationManager.cancel(DownloadManager.DOWNLOAD_NOTIFICATION_GROUP_ID);
if (downloadNotificationIds != null) {
for (int i = 0; i < downloadNotificationIds.size(); i++) {
notificationManager.cancel(downloadNotificationIds.get(i));
}
}
if (receivedStopService || !isServiceRunning(LbrynetService.class)) {
notificationManager.cancelAll();
}
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@TargetApi(Build.VERSION_CODES.M)
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
permissionListener = listener;
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
@Override
public void onNewIntent(Intent intent) {
if (mReactInstanceManager != null) {
mReactInstanceManager.onNewIntent(intent);
}
if (intent != null) {
int sourceNotificationId = intent.getIntExtra(SOURCE_NOTIFICATION_ID_KEY, -1);
if (sourceNotificationId > -1) {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.cancel(sourceNotificationId);
}
checkNotificationOpenIntent(intent);
}
super.onNewIntent(intent);
}
private static void checkPermission(String permission, int requestCode, String rationale, Context context, boolean forceRequest) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (!forceRequest && ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission)) {
Toast.makeText(context, rationale, Toast.LENGTH_LONG).show();
} else {
ActivityCompat.requestPermissions((Activity) context, new String[] { permission }, requestCode);
}
}
}
private static void checkPermission(String permission, int requestCode, String rationale, Context context) {
checkPermission(permission, requestCode, rationale, context, false);
}
public static boolean hasPermission(String permission, Context context) {
return (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
}
public static void checkPhoneStatePermission(Context context) {
// Request read phone state permission
checkPermission(Manifest.permission.READ_PHONE_STATE,
PHONE_STATE_PERMISSION_REQ_CODE,
"LBRY requires optional access to be able to identify your device for rewards. " +
"You cannot claim rewards without this permission.",
context,
true);
}
public static void checkReceiveSmsPermission(Context context) {
// Request read phone state permission
checkPermission(Manifest.permission.RECEIVE_SMS,
RECEIVE_SMS_PERMISSION_REQ_CODE,
"LBRY requires access to be able to read a verification text message for rewards.",
context,
true);
}
public static void checkStoragePermission(Context context) {
// Request read phone state permission
checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
STORAGE_PERMISSION_REQ_CODE,
"LBRY requires access to your device storage to be able to download files and media.",
context,
true);
}
private boolean isServiceRunning(Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(serviceInfo.service.getClassName())) {
return true;
}
}
return false;
}
private static WritableMap JSONObjectToMap(JSONObject jsonObject) throws JSONException {
WritableMap map = Arguments.createMap();
Iterator<String> keys = jsonObject.keys();
while(keys.hasNext()) {
String key = keys.next();
Object value = jsonObject.get(key);
if (value instanceof JSONArray) {
map.putArray(key, JSONArrayToList((JSONArray) value));
} else if (value instanceof JSONObject) {
map.putMap(key, JSONObjectToMap((JSONObject) value));
} else if (value instanceof Boolean) {
map.putBoolean(key, (Boolean) value);
} else if (value instanceof Integer) {
map.putInt(key, (Integer) value);
} else if (value instanceof Double) {
map.putDouble(key, (Double) value);
} else if (value instanceof String) {
map.putString(key, (String) value);
} else {
map.putString(key, value.toString());
}
}
return map;
}
private static WritableArray JSONArrayToList(JSONArray jsonArray) throws JSONException {
WritableArray array = Arguments.createArray();
for(int i = 0; i < jsonArray.length(); i++) {
Object value = jsonArray.get(i);
if (value instanceof JSONArray) {
array.pushArray(JSONArrayToList((JSONArray) value));
} else if (value instanceof JSONObject) {
array.pushMap(JSONObjectToMap((JSONObject) value));
} else if (value instanceof Boolean) {
array.pushBoolean((Boolean) value);
} else if (value instanceof Integer) {
array.pushInt((Integer) value);
} else if (value instanceof Double) {
array.pushDouble((Double) value);
} else if (value instanceof String) {
array.pushString((String) value);
} else {
array.pushString(value.toString());
}
}
return array;
}
/**
* https://gist.github.com/HBiSoft/15899990b8cd0723c3a894c1636550a8
*/
@SuppressLint("NewApi")
public static String getRealPathFromURI_API19(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
// This is for checking Main Memory
if ("primary".equalsIgnoreCase(type)) {
if (split.length > 1) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
} else {
return Environment.getExternalStorageDirectory() + "/";
}
// This is for checking SD Card
} else {
return "storage" + "/" + docId.replace(":", "/");
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
String fileName = getFilePath(context, uri);
if (fileName != null) {
return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;
}
String id = DocumentsContract.getDocumentId(uri);
if (id.startsWith("raw:")) {
id = id.replaceFirst("raw:", "");
File file = new File(id);
if (file.exists())
return id;
}
final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public static String getFilePath(Context context, Uri uri) {
Cursor cursor = null;
final String[] projection = { MediaStore.MediaColumns.DISPLAY_NAME };
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
public static class LaunchTiming {
private Date start;
private boolean coldStart;
public LaunchTiming(Date start) {
this.start = start;
}
public Date getStart() {
return start;
}
public void setStart(Date start) {
this.start = start;
}
public boolean isColdStart() {
return coldStart;
}
public void setColdStart(boolean coldStart) {
this.coldStart = coldStart;
}
}
}

View file

@ -0,0 +1,84 @@
package io.lbry.browser.reactmodules;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
import io.lbry.lbrysdk.LbrynetService;
public class BackgroundMediaModule extends ReactContextBaseJavaModule {
public static final int NOTIFICATION_ID = 30;
public static final String ACTION_PLAY = "io.lbry.browser.ACTION_MEDIA_PLAY";
public static final String ACTION_PAUSE = "io.lbry.browser.ACTION_MEDIA_PAUSE";
public static final String ACTION_STOP = "io.lbry.browser.ACTION_MEDIA_STOP";
private Context context;
public BackgroundMediaModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
}
@Override
public String getName() {
return "BackgroundMedia";
}
@ReactMethod
public void showPlaybackNotification(String title, String publisher, String uri, boolean paused) {
Intent contextIntent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contextIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent playIntent = new Intent();
playIntent.setAction(ACTION_PLAY);
PendingIntent playPendingIntent = PendingIntent.getBroadcast(context, 0, playIntent, 0);
Intent pauseIntent = new Intent();
pauseIntent.setAction(ACTION_PAUSE);
PendingIntent pausePendingIntent = PendingIntent.getBroadcast(context, 0, pauseIntent, 0);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, LbrynetService.NOTIFICATION_CHANNEL_ID);
builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
.setContentIntent(pendingIntent)
.setContentTitle(title)
.setContentText(publisher)
.setGroup(LbrynetService.GROUP_SERVICE)
.setOngoing(!paused)
.setSmallIcon(paused ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play)
.setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0))
.addAction(paused ? android.R.drawable.ic_media_play : android.R.drawable.ic_media_pause,
paused ? "Play" : "Pause",
paused ? playPendingIntent : pausePendingIntent)
.build();
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
@ReactMethod
public void hidePlaybackNotification() {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(NOTIFICATION_ID);
}
}

View file

@ -0,0 +1,50 @@
package io.lbry.browser.reactmodules;
import android.app.Activity;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.SharedPreferences;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import io.lbry.browser.MainActivity;
import io.lbry.lbrysdk.LbrynetService;
import io.lbry.lbrysdk.ServiceHelper;
public class DaemonServiceControlModule extends ReactContextBaseJavaModule {
private Context context;
public DaemonServiceControlModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
}
@Override
public String getName() {
return "DaemonServiceControl";
}
@ReactMethod
public void startService() {
ServiceHelper.start(context, "", LbrynetService.class, "lbrynetservice");
}
@ReactMethod
public void stopService() {
ServiceHelper.stop(context, LbrynetService.class);
}
@ReactMethod
public void setKeepDaemonRunning(boolean value) {
if (context != null) {
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean(MainActivity.SETTING_KEEP_DAEMON_RUNNING, value);
editor.commit();
}
}
}

View file

@ -0,0 +1,131 @@
package io.lbry.browser.reactmodules;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import io.lbry.browser.BuildConfig;
import io.lbry.browser.MainActivity;
import io.lbry.lbrysdk.Utils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;
import org.json.JSONException;
public class FirebaseModule extends ReactContextBaseJavaModule {
private Context context;
private FirebaseAnalytics firebaseAnalytics;
public FirebaseModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
this.firebaseAnalytics = FirebaseAnalytics.getInstance(context);
}
@Override
public String getName() {
return "Firebase";
}
@ReactMethod
public void setCurrentScreen(String name, final Promise promise) {
final Activity activity = getCurrentActivity();
if (activity != null && firebaseAnalytics != null) {
activity.runOnUiThread(new Runnable() {
public void run() {
firebaseAnalytics.setCurrentScreen(activity, name, Utils.capitalizeAndStrip(name));
}
});
}
promise.resolve(true);
}
@ReactMethod
public void track(String name, ReadableMap payload) {
Bundle bundle = new Bundle();
if (payload != null) {
HashMap<String, Object> payloadMap = payload.toHashMap();
for (Map.Entry<String, Object> entry : payloadMap.entrySet()) {
Object value = entry.getValue();
if (value != null) {
bundle.putString(entry.getKey(), entry.getValue().toString());
}
}
}
if (firebaseAnalytics != null) {
firebaseAnalytics.logEvent(name, bundle);
}
}
@ReactMethod
public void logException(boolean fatal, String message, String error) {
Bundle bundle = new Bundle();
bundle.putString("message", message);
bundle.putString("error", error);
if (firebaseAnalytics != null) {
firebaseAnalytics.logEvent(fatal ? "reactjs_exception" : "reactjs_warning", bundle);
}
if (fatal) {
Toast.makeText(context,
"An application error occurred which has been automatically logged. " +
"If you keep seeing this message, please provide feedback to the LBRY " +
"team by emailing hello@lbry.com.",
Toast.LENGTH_LONG).show();
}
}
@ReactMethod
public void getMessagingToken(final Promise promise) {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
promise.reject("Firebase getInstanceId call failed");
return;
}
// Get new Instance ID token
String token = task.getResult().getToken();
promise.resolve(token);
}
});
}
@ReactMethod
public void logLaunchTiming() {
Date end = new Date();
MainActivity.LaunchTiming currentTiming = MainActivity.CurrentLaunchTiming;
if (currentTiming == null) {
// no start timing data, so skip this
return;
}
long totalTimeMs = end.getTime() - currentTiming.getStart().getTime();
String eventName = currentTiming.isColdStart() ? "app_cold_start" : "app_warm_start";
Bundle bundle = new Bundle();
bundle.putLong("total_ms", totalTimeMs);
bundle.putLong("total_seconds", new Double(Math.ceil(totalTimeMs / 1000.0)).longValue());
if (firebaseAnalytics != null) {
firebaseAnalytics.logEvent(eventName, bundle);
}
}
}

View file

@ -0,0 +1,53 @@
package io.lbry.browser.reactmodules;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.SharedPreferences;
import android.os.Bundle;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.google.firebase.analytics.FirebaseAnalytics;
import io.lbry.browser.MainActivity;
public class FirstRunModule extends ReactContextBaseJavaModule {
private Context context;
private SharedPreferences sp;
public FirstRunModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
this.sp = reactContext.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
@Override
public String getName() {
return "FirstRun";
}
@ReactMethod
public void isFirstRun(final Promise promise) {
// If firstRun flag does not exist, default to true
boolean firstRun = sp.getBoolean("firstRun", true);
promise.resolve(firstRun);
}
@ReactMethod
public void firstRunCompleted() {
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("firstRun", false);
editor.commit();
FirebaseAnalytics firebase = FirebaseAnalytics.getInstance(context);
if (firebase != null) {
Bundle bundle = new Bundle();
firebase.logEvent("first_run_completed", bundle);
}
}
}

View file

@ -0,0 +1,313 @@
package io.lbry.browser.reactmodules;
import android.content.Context;
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.Manifest;
import android.media.ThumbnailUtils;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import io.lbry.browser.MainActivity;
import io.lbry.lbrysdk.Utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
public class GalleryModule extends ReactContextBaseJavaModule {
private Context context;
public GalleryModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
}
@Override
public String getName() {
return "Gallery";
}
@ReactMethod
public void getVideos(Promise promise) {
WritableArray items = Arguments.createArray();
List<GalleryItem> videos = loadVideos();
for (int i = 0; i < videos.size(); i++) {
items.pushMap(videos.get(i).toMap());
}
promise.resolve(items);
}
@ReactMethod
public void getThumbnailPath(Promise promise) {
if (context != null) {
File cacheDir = context.getExternalCacheDir();
String thumbnailPath = String.format("%s/thumbnails", cacheDir.getAbsolutePath());
promise.resolve(thumbnailPath);
return;
}
promise.resolve(null);
}
@ReactMethod
public void getUploadsPath(Promise promise) {
if (context != null) {
String baseFolder = Utils.getExternalStorageDir(context);
String uploadsPath = String.format("%s/LBRY/Uploads", baseFolder);
File uploadsDir = new File(uploadsPath);
if (!uploadsDir.isDirectory()) {
uploadsDir.mkdirs();
}
promise.resolve(uploadsPath);
}
promise.reject("The content could not be saved to the device. Please check your storage permissions.");
}
@ReactMethod
public void createVideoThumbnail(String targetId, String filePath, Promise promise) {
(new AsyncTask<Void, Void, String>() {
protected String doInBackground(Void... param) {
String thumbnailPath = null;
if (context != null) {
Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MINI_KIND);
File cacheDir = context.getExternalCacheDir();
thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId);
File file = new File(thumbnailPath);
try (FileOutputStream os = new FileOutputStream(thumbnailPath)) {
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
os.close();
} catch (IOException ex) {
promise.reject("Could not create a thumbnail for the video");
return null;
}
}
return thumbnailPath;
}
public void onPostExecute(String thumbnailPath) {
if (thumbnailPath != null && thumbnailPath.trim().length() > 0) {
promise.resolve(thumbnailPath);
}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ReactMethod
public void createImageThumbnail(String targetId, String filePath, Promise promise) {
(new AsyncTask<Void, Void, String>() {
protected String doInBackground(Void... param) {
String thumbnailPath = null;
FileOutputStream os = null;
try {
Bitmap source = BitmapFactory.decodeFile(filePath);
// MINI_KIND dimensions
Bitmap thumbnail = Bitmap.createScaledBitmap(source, 512, 384, false);
if (context != null) {
File cacheDir = context.getExternalCacheDir();
thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId);
os = new FileOutputStream(thumbnailPath);
if (thumbnail != null) {
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
}
os.close();
}
} catch (IOException ex) {
promise.reject("Could not create a thumbnail for the image");
return null;
} finally {
if (os != null) {
try {
os.close();
} catch (IOException ex) {
// ignoe
}
}
}
return thumbnailPath;
}
public void onPostExecute(String thumbnailPath) {
if (thumbnailPath != null && thumbnailPath.trim().length() > 0) {
promise.resolve(thumbnailPath);
}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private List<GalleryItem> loadVideos() {
String[] projection = {
MediaStore.MediaColumns._ID,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.Video.Media.DURATION
};
List<String> ids = new ArrayList<String>();
List<GalleryItem> items = new ArrayList<GalleryItem>();
Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, null, null,
String.format("%s DESC", MediaStore.MediaColumns.DATE_MODIFIED));
while (cursor.moveToNext()) {
int idColumn = cursor.getColumnIndex(MediaStore.MediaColumns._ID);
int nameColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
int typeColumn = cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE);
int pathColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
String id = cursor.getString(idColumn);
GalleryItem item = new GalleryItem();
item.setId(id);
item.setName(cursor.getString(nameColumn));
item.setType(cursor.getString(typeColumn));
item.setFilePath(cursor.getString(pathColumn));
items.add(item);
ids.add(id);
}
checkThumbnails(ids);
return items;
}
private void checkThumbnails(final List<String> ids) {
(new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... param) {
if (context != null) {
ContentResolver resolver = context.getContentResolver();
for (int i = 0; i < ids.size(); i++) {
String id = ids.get(i);
File cacheDir = context.getExternalCacheDir();
File thumbnailsDir = new File(String.format("%s/thumbnails", cacheDir.getAbsolutePath()));
if (!thumbnailsDir.isDirectory()) {
thumbnailsDir.mkdirs();
}
String thumbnailPath = String.format("%s/%s.png", thumbnailsDir.getAbsolutePath(), id);
File file = new File(thumbnailPath);
if (!file.exists()) {
// save the thumbnail to the path
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
Bitmap thumbnail = MediaStore.Video.Thumbnails.getThumbnail(
resolver, Long.parseLong(id), MediaStore.Video.Thumbnails.MINI_KIND, options);
if (thumbnail != null) {
try (FileOutputStream os = new FileOutputStream(thumbnailPath)) {
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
} catch (IOException ex) {
// skip
}
}
}
if (file.exists() && file.length() > 0 && GalleryModule.this.context != null) {
WritableMap params = Arguments.createMap();
params.putString("id", id);
((ReactApplicationContext) GalleryModule.this.context).getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onGalleryThumbnailChecked", params);
}
}
}
return null;
}
public void onPostExecute(Void result) {
if (GalleryModule.this.context != null) {
((ReactApplicationContext) GalleryModule.this.context).getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onAllGalleryThumbnailsChecked", null);
}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private static class GalleryItem {
private String id;
private int duration;
private String filePath;
private String name;
private String type;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public WritableMap toMap() {
WritableMap map = Arguments.createMap();
map.putString("id", id);
map.putString("name", name);
map.putString("filePath", filePath);
map.putString("type", type);
map.putInt("duration", duration);
return map;
}
}
@ReactMethod
public void canUseCamera(final Promise promise) {
promise.resolve(MainActivity.hasPermission(Manifest.permission.CAMERA, MainActivity.getActivity()));
}
}

View file

@ -0,0 +1,74 @@
package io.lbry.browser.reactmodules;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableMap;
import io.lbry.browser.MainActivity;
import io.lbry.lbrysdk.Utils;
import java.util.List;
import java.util.ArrayList;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;
public class RequestsModule extends ReactContextBaseJavaModule {
private Context context;
public RequestsModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
}
@Override
public String getName() {
return "Requests";
}
@ReactMethod
public void get(final String url, final Promise promise) {
(new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... params) {
try {
return Utils.performRequest(url);
} catch (Exception ex) {
return null;
}
}
protected void onPostExecute(String response) {
if (response == null) {
promise.reject(String.format("Request to %s returned null.", url));
return;
}
promise.resolve(response);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ReactMethod
public void lbryioCall(String authToken, final Promise promise) {
// get the auth token here, or let the app pass it in?
}
@ReactMethod
public void lbryCall(final Promise promise) {
}
}

View file

@ -0,0 +1,47 @@
package io.lbry.browser.reactmodules;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class ScreenOrientationModule extends ReactContextBaseJavaModule {
private Context context;
public ScreenOrientationModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
}
@Override
public String getName() {
return "ScreenOrientation";
}
@ReactMethod
public void unlockOrientation() {
Activity activity = getCurrentActivity();
if (activity != null) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
}
}
@ReactMethod
public void lockOrientationLandscape() {
Activity activity = getCurrentActivity();
if (activity != null) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
@ReactMethod
public void lockOrientationPortrait() {
Activity activity = getCurrentActivity();
if (activity != null) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
}

View file

@ -0,0 +1,193 @@
package io.lbry.browser.reactmodules;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableMap;
import io.lbry.browser.MainActivity;
import java.util.List;
import java.util.ArrayList;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;
public class StatePersistorModule extends ReactContextBaseJavaModule {
private Context context;
private List<ReadableMap> queue;
private ReadableMap filter;
private ReadableMap lastState;
private AsyncTask persistTask;
public StatePersistorModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
queue = new ArrayList<ReadableMap>();
}
@Override
public String getName() {
return "StatePersistor";
}
/*private WritableMap filterState(ReadableMap state) {
WritableMap filteredState = Arguments.createMap();
return state;
}*/
public boolean hasStateChanged(ReadableMap newState) {
return false;
}
@ReactMethod
public void update(ReadableMap state, ReadableMap filter) {
if (this.filter == null) {
this.filter = filter;
}
// process state updates from the queue using a background task
synchronized (this) {
queue.add(state);
}
persistState();
}
private void persistState() {
persistState(false);
}
private void persistState(final boolean flush) {
if (flush && persistTask != null) {
persistTask.cancel(true);
persistTask = null;
}
if (persistTask == null) {
persistTask = (new AsyncTask<Object, Void, Boolean>() {
protected Boolean doInBackground(Object... param) {
// get the first item in the queue
ReadableMap queuedState = null;
if (queue.size() > 0) {
synchronized (StatePersistorModule.this) {
queuedState = queue.remove(flush ? queue.size() - 1 : 0);
if (flush) {
// we only want the final state in this scenario
queue.clear();
}
}
}
if (queuedState != null) {
ReadableMap state = queuedState; //(ReadableMap) filterState(queuedState);
// convert to JSON object
try {
JSONObject json = readableMapToJSON(state);
// save the state file
// TODO: explore this option at a later date
throw new UnsupportedOperationException();
} catch (JSONException ex) {
// normally shouldn't happen, but if it does, reinsert into the queue
if (queuedState != null) {
synchronized (StatePersistorModule.this) {
queue.add(0, queuedState);
}
}
return false;
}
}
return false;
}
public void onPostExecute(Boolean result) {
if (queue.size() > 0) {
persistState();
}
persistTask = null;
}
});
persistTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
@ReactMethod
public void flush() {
persistState(true);
}
private static JSONObject readableMapToJSON(ReadableMap readableMap) throws JSONException {
JSONObject json = new JSONObject();
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
switch (readableMap.getType(key)) {
case Map:
json.put(key, readableMapToJSON(readableMap.getMap(key)));
break;
case Array:
json.put(key, readableArrayToJSON(readableMap.getArray(key)));
break;
case Boolean:
json.put(key, readableMap.getBoolean(key));
break;
case Null:
json.put(key, JSONObject.NULL);
break;
case Number:
json.put(key, readableMap.getDouble(key));
break;
case String:
json.put(key, readableMap.getString(key));
break;
}
}
return json;
}
private static JSONArray readableArrayToJSON(ReadableArray readableArray) throws JSONException {
JSONArray array = new JSONArray();
for (int i = 0; i < readableArray.size(); i++) {
switch (readableArray.getType(i)) {
case Null:
break;
case Boolean:
array.put(readableArray.getBoolean(i));
break;
case Number:
array.put(readableArray.getDouble(i));
break;
case String:
array.put(readableArray.getString(i));
break;
case Map:
array.put(readableMapToJSON(readableArray.getMap(i)));
break;
case Array:
array.put(readableArrayToJSON(readableArray.getArray(i)));
break;
}
}
return array;
}
}

View file

@ -0,0 +1,473 @@
package io.lbry.browser.reactmodules;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.Manifest;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import androidx.core.content.FileProvider;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import android.telephony.TelephonyManager;
import android.view.View;
import android.view.WindowManager;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.common.MapBuilder;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.security.KeyStore;
import io.lbry.browser.DownloadManager;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
import io.lbry.lbrysdk.LbrynetService;
import io.lbry.lbrysdk.Utils;
public class UtilityModule extends ReactContextBaseJavaModule {
private static final Map<String, Integer> activeNotifications = new HashMap<String, Integer>();
private static final String FILE_PROVIDER = "io.lbry.browser.fileprovider";
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.SUBSCRIPTIONS_NOTIFICATION_CHANNEL";
public static final String ACTION_NOTIFICATION_PLAY = "io.lbry.browser.ACTION_NOTIFICATION_PLAY";
public static final String ACTION_NOTIFICATION_LATER = "io.lbry.browser.ACTION_NOTIFICATION_LATER";
public static final String RECEIVE_SUBSCRIPTION_NOTIFICATIONS = "receiveSubscriptionNotifications";
public static final String RECEIVE_REWARD_NOTIFICATIONS = "receiveRewardNotifications";
public static final String RECEIVE_INTERESTS_NOTIFICATIONS = "receiveInterestsNotifications";
public static final String RECEIVE_CREATOR_NOTIFICATIONS = "receiveCreatorNotifications";
// the last language set to be loaded
private static final String LANGUAGE_SETTING_KEY = "language";
private String language;
private Context context;
private KeyStore keyStore;
public UtilityModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
try {
this.keyStore = Utils.initKeyStore(context);
} catch (Exception ex) {
// continue without keystore
}
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
language = sp.getString(LANGUAGE_SETTING_KEY, "en");
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = MapBuilder.newHashMap();
constants.put("language", language);
return constants;
}
@Override
public String getName() {
return "UtilityModule";
}
@ReactMethod
public void keepAwakeOn() {
final Activity activity = getCurrentActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
});
}
}
@ReactMethod
public void keepAwakeOff() {
final Activity activity = getCurrentActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
});
}
}
@ReactMethod
public void hideNavigationBar() {
final Activity activity = MainActivity.getActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
public void run() {
View decorView = activity.getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
});
}
}
@ReactMethod
public void showNavigationBar() {
final Activity activity = MainActivity.getActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
public void run() {
View decorView = activity.getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_VISIBLE);
}
});
}
}
@ReactMethod
public void getDeviceId(boolean requestPermission, final Promise promise) {
if (isEmulator()) {
promise.reject("Rewards cannot be claimed from an emulator nor virtual device.");
return;
}
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String id = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
id = telephonyManager.getImei(); // GSM
if (id == null) {
id = telephonyManager.getMeid(); // CDMA
}
} else {
id = telephonyManager.getDeviceId();
}
} catch (SecurityException ex) {
// Maybe the permission was not granted? Try to acquire permission
/*if (requestPermission) {
requestPhoneStatePermission();
}*/
} catch (Exception ex) {
// id could not be obtained. Display a warning that rewards cannot be claimed.
promise.reject(ex.getMessage());
}
if (id == null || id.trim().length() == 0) {
promise.reject("Rewards cannot be claimed because your device could not be identified.");
return;
}
promise.resolve(id);
}
@ReactMethod
public void canReceiveSms(final Promise promise) {
promise.resolve(MainActivity.hasPermission(Manifest.permission.RECEIVE_SMS, MainActivity.getActivity()));
}
@ReactMethod
public void requestReceiveSmsPermission() {
MainActivity activity = (MainActivity) MainActivity.getActivity();
if (activity != null) {
// Request for the RECEIVE_SMS permission
MainActivity.checkReceiveSmsPermission(activity);
}
}
@ReactMethod
public void canReadWriteStorage(final Promise promise) {
promise.resolve(MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, MainActivity.getActivity()));
}
@ReactMethod
public void requestStoragePermission() {
MainActivity activity = (MainActivity) MainActivity.getActivity();
if (activity != null) {
MainActivity.checkStoragePermission(activity);
}
}
@ReactMethod
public void shareLogFile(Callback errorCallback) {
String logFileName = "lbrynet.log";
File logFile = new File(String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet"), logFileName);
if (!logFile.exists()) {
errorCallback.invoke("The lbrynet.log file could not be found.");
return;
}
try {
Uri fileUri = FileProvider.getUriForFile(context, FILE_PROVIDER, logFile);
if (fileUri != null) {
Intent shareIntent = new Intent();
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
Intent sendLogIntent = Intent.createChooser(shareIntent, "Send LBRY log");
sendLogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(sendLogIntent);
}
} catch (IllegalArgumentException e) {
errorCallback.invoke("The lbrynet.log file cannot be shared due to permission restrictions.");
}
}
@ReactMethod
public void shareUrl(String url) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
Intent shareUrlIntent = Intent.createChooser(shareIntent, "Share LBRY content");
shareUrlIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(shareUrlIntent);
}
@ReactMethod
public void showNotificationForContent(final String uri, String title, String publisher, final String thumbnail, boolean isPlayable) {
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "LBRY Subscriptions", NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("LBRY subscription notifications");
notificationManager.createNotificationChannel(channel);
}
if (activeNotifications.containsKey(uri)) {
// the notification for the specified uri is already present, don't try to create another one
return;
}
int id = 0;
Random random = new Random();
do {
id = random.nextInt();
} while (id < 100);
final int notificationId = id;
String uriWithParam = String.format("%s?download=true", uri);
Intent playIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriWithParam));
playIntent.putExtra(MainActivity.SOURCE_NOTIFICATION_ID_KEY, notificationId);
playIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent playPendingIntent = PendingIntent.getActivity(context, 0, playIntent, PendingIntent.FLAG_CANCEL_CURRENT);
boolean hasThumbnail = false;
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setAutoCancel(true)
.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
.setContentIntent(DownloadManager.getLaunchPendingIntent(uri, context))
.setContentTitle(publisher)
.setContentText(title)
.setSmallIcon(R.drawable.ic_lbry)
.addAction(android.R.drawable.ic_media_play, (isPlayable ? "Play" : "Open"), playPendingIntent);
activeNotifications.put(uri, notificationId);
if (thumbnail != null) {
// attempt to load the thumbnail Bitmap before displaying the notification
final Uri thumbnailUri = Uri.parse(thumbnail);
if (thumbnailUri != null) {
hasThumbnail = true;
(new AsyncTask<Void, Void, Bitmap>() {
protected Bitmap doInBackground(Void... params) {
try {
return Picasso.get().load(thumbnailUri).get();
} catch (Exception e) {
return null;
}
}
protected void onPostExecute(Bitmap result) {
if (result != null) {
builder.setLargeIcon(result)
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(result).bigLargeIcon(null));
}
notificationManager.notify(notificationId, builder.build());
}
}).execute();
}
}
if (!hasThumbnail) {
notificationManager.notify(notificationId, builder.build());
}
}
private static boolean isEmulator() {
String buildModel = Build.MODEL.toLowerCase();
return (// Check FINGERPRINT
Build.FINGERPRINT.startsWith("generic") ||
Build.FINGERPRINT.startsWith("unknown") ||
Build.FINGERPRINT.contains("test-keys") ||
// Check MODEL
buildModel.contains("google_sdk") ||
buildModel.contains("emulator") ||
buildModel.contains("android sdk built for x86") ||
// Check MANUFACTURER
Build.MANUFACTURER.contains("Genymotion") ||
"unknown".equals(Build.MANUFACTURER) ||
// Check HARDWARE
Build.HARDWARE.contains("goldfish") ||
Build.HARDWARE.contains("vbox86") ||
// Check PRODUCT
"google_sdk".equals(Build.PRODUCT) ||
"sdk_google_phone_x86".equals(Build.PRODUCT) ||
"sdk".equals(Build.PRODUCT) ||
"sdk_x86".equals(Build.PRODUCT) ||
"vbox86p".equals(Build.PRODUCT) ||
// Check BRAND and DEVICE
(Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
);
}
@ReactMethod
public void setSecureValue(String key, String value) {
if (keyStore != null) {
Utils.setSecureValue(key, value, context, keyStore);
}
}
@ReactMethod
public void getSecureValue(String key, Promise promise) {
if (keyStore == null) {
promise.reject("no keyStore found");
return;
}
promise.resolve(Utils.getSecureValue(key, context, keyStore));
}
@ReactMethod
public void checkDownloads() {
Intent intent = new Intent();
intent.setAction(LbrynetService.ACTION_CHECK_DOWNLOADS);
if (context != null) {
context.sendBroadcast(intent);
}
}
@ReactMethod
public void queueDownload(String outpoint) {
Intent intent = new Intent();
intent.setAction(LbrynetService.ACTION_QUEUE_DOWNLOAD);
intent.putExtra("outpoint", outpoint);
if (context != null) {
context.sendBroadcast(intent);
}
}
@ReactMethod
public void deleteDownload(String uri) {
Intent intent = new Intent();
intent.setAction(LbrynetService.ACTION_DELETE_DOWNLOAD);
intent.putExtra("uri", uri);
if (context != null) {
context.sendBroadcast(intent);
}
}
@ReactMethod
public void openDocumentPicker(String type) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType(type);
Activity activity = MainActivity.getActivity();
if (activity != null) {
activity.startActivityForResult(
Intent.createChooser(intent, "Select a file"), MainActivity.DOCUMENT_PICKER_RESULT_CODE);
}
}
@ReactMethod
public void setNativeBooleanSetting(String key, boolean value) {
if (context != null) {
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean(key, value);
editor.commit();
}
}
@ReactMethod
public void setNativeStringSetting(String key, String value) {
if (context != null) {
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(key, value);
editor.commit();
}
}
@ReactMethod
public void getNativeStringSetting(String key, String defaultValue, Promise promise) {
if (context != null) {
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
promise.resolve(sp.getString(key, defaultValue));
} else {
promise.resolve(null);
}
}
@ReactMethod
public void getNotificationLaunchTarget(Promise promise) {
Activity activity = MainActivity.getActivity();
if (activity != null) {
Intent intent = activity.getIntent();
if (intent != null) {
String target = intent.getStringExtra("target");
if (target != null && target.trim().length() > 0) {
promise.resolve(target);
return;
}
}
}
promise.resolve(null);
}
@ReactMethod
public void getDownloadDirectory(Promise promise) {
// This obtains a public default download directory after the storage permission has been granted
promise.resolve(Utils.getConfiguredDownloadDirectory(context));
}
}

View file

@ -0,0 +1,37 @@
package io.lbry.browser.reactmodules;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class VersionInfoModule extends ReactContextBaseJavaModule {
private Context context;
public VersionInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
}
@Override
public String getName() {
return "VersionInfo";
}
@ReactMethod
public void getAppVersion(final Promise promise) {
PackageManager packageManager = this.context.getPackageManager();
String packageName = this.context.getPackageName();
try {
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
promise.resolve(packageInfo.versionName);
} catch (PackageManager.NameNotFoundException e) {
// normally shouldn't happen
promise.resolve("Unknown");
}
}
}

View file

@ -0,0 +1,46 @@
package io.lbry.browser.reactpackages;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import io.lbry.browser.reactmodules.BackgroundMediaModule;
import io.lbry.browser.reactmodules.DaemonServiceControlModule;
import io.lbry.browser.reactmodules.FirstRunModule;
import io.lbry.browser.reactmodules.FirebaseModule;
import io.lbry.browser.reactmodules.GalleryModule;
import io.lbry.browser.reactmodules.RequestsModule;
import io.lbry.browser.reactmodules.ScreenOrientationModule;
import io.lbry.browser.reactmodules.StatePersistorModule;
import io.lbry.browser.reactmodules.VersionInfoModule;
import io.lbry.browser.reactmodules.UtilityModule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class LbryReactPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new BackgroundMediaModule(reactContext));
modules.add(new DaemonServiceControlModule(reactContext));
modules.add(new FirstRunModule(reactContext));
modules.add(new FirebaseModule(reactContext));
modules.add(new GalleryModule(reactContext));
modules.add(new RequestsModule(reactContext));
modules.add(new ScreenOrientationModule(reactContext));
modules.add(new StatePersistorModule(reactContext));
modules.add(new UtilityModule(reactContext));
modules.add(new VersionInfoModule(reactContext));
return modules;
}
}

View file

@ -0,0 +1,17 @@
package io.lbry.browser.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import io.lbry.browser.DownloadManager;
public class NotificationDeletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int notificationId = intent.getExtras().getInt(DownloadManager.NOTIFICATION_ID_KEY);
if (DownloadManager.DOWNLOAD_NOTIFICATION_GROUP_ID == notificationId) {
DownloadManager.groupCreated = false;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

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