diff --git a/mobile/thirdparty/transistor-background-fetch/LICENSE b/mobile/thirdparty/transistor-background-fetch/LICENSE new file mode 100644 index 000000000..526bbac98 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 Transistor Software + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/mobile/thirdparty/transistor-background-fetch/README.md b/mobile/thirdparty/transistor-background-fetch/README.md new file mode 100644 index 000000000..418370978 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/README.md @@ -0,0 +1,22 @@ +Transistor Background Fetch +=========================================================================== + +Copyright (c) 2017 Transistor Software + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/mobile/thirdparty/transistor-background-fetch/TSBackgroundFetch.podspec b/mobile/thirdparty/transistor-background-fetch/TSBackgroundFetch.podspec new file mode 100644 index 000000000..1967bd2e9 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/TSBackgroundFetch.podspec @@ -0,0 +1,28 @@ +# +# Be sure to run `pod lib lint TSBackgroundFetch.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'TSBackgroundFetch' + s.version = '0.0.1' + s.summary = 'iOS Background Fetch API Manager' + + s.description = <<-DESC +iOS Background Fetch API Manager with ability to handle multiple listeners. + DESC + + s.homepage = 'http://www.transistorsoft.com' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'christocracy' => 'christocracy@gmail.com' } + s.source = { :git => 'https://github.com/transistorsoft/transistor-background-fetch.git', :tag => s.version.to_s } + s.social_media_url = 'https://twitter.com/christocracy' + + s.ios.deployment_target = '8.0' + + s.source_files = 'ios/TSBackgroundFetch/TSBackgroundFetch/*.{h,m}' + s.vendored_frameworks = 'ios/TSBackgroundFetch/TSBackgroundFetch.framework' +end diff --git a/mobile/thirdparty/transistor-background-fetch/android/.gitignore b/mobile/thirdparty/transistor-background-fetch/android/.gitignore new file mode 100644 index 000000000..39fb081a4 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/.gitignore b/mobile/thirdparty/transistor-background-fetch/android/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/build.gradle b/mobile/thirdparty/transistor-background-fetch/android/app/build.gradle new file mode 100644 index 000000000..d44eb09b6 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 30 + defaultConfig { + applicationId "com.transistorsoft.backgroundfetch" + minSdkVersion 16 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation fileTree(dir: 'libs', include: ['*.jar']) + +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/proguard-rules.pro b/mobile/thirdparty/transistor-background-fetch/android/app/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/androidTest/java/com/transistorsoft/backgroundfetch/ExampleInstrumentedTest.java b/mobile/thirdparty/transistor-background-fetch/android/app/src/androidTest/java/com/transistorsoft/backgroundfetch/ExampleInstrumentedTest.java new file mode 100644 index 000000000..983348763 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/androidTest/java/com/transistorsoft/backgroundfetch/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.transistorsoft.backgroundfetch; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.transistorsoft.backgroundfetch", appContext.getPackageName()); + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/AndroidManifest.xml b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..b22ee9b63 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..c7bd21dbd --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/drawable/ic_launcher_background.xml b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..d5fccc538 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..eca70cfe5 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..eca70cfe5 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..a2f590828 Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..1b5239980 Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..ff10afd6e Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..115a4c768 Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..dcd3cd808 Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..459ca609d Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..8ca12fe02 Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..8e19b410a Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..b824ebdd4 Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..4c19a13c2 Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/colors.xml b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..3ab3e9cbc --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/strings.xml b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..89ece41f6 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + BackgroundFetch + diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/styles.xml b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..5885930df --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/mobile/thirdparty/transistor-background-fetch/android/app/src/test/java/com/transistorsoft/backgroundfetch/ExampleUnitTest.java b/mobile/thirdparty/transistor-background-fetch/android/app/src/test/java/com/transistorsoft/backgroundfetch/ExampleUnitTest.java new file mode 100644 index 000000000..6536d1cd3 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/app/src/test/java/com/transistorsoft/backgroundfetch/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.transistorsoft.backgroundfetch; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/mobile/thirdparty/transistor-background-fetch/android/build.gradle b/mobile/thirdparty/transistor-background-fetch/android/build.gradle new file mode 100644 index 000000000..a00f681d0 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/build.gradle @@ -0,0 +1,34 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.3' + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +ext { + compileSdkVersion = 32 + targetSdkVersion = 31 + buildToolsVersion = "29.0.6" + appCompatVersion = "1.4.1" +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/gradle.properties b/mobile/thirdparty/transistor-background-fetch/android/gradle.properties new file mode 100644 index 000000000..ee80fe3b1 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +VERSION_NAME=0.5.6 +VERSION_CODE=21 + +android.useAndroidX=true +android.enableJetifier=true diff --git a/mobile/thirdparty/transistor-background-fetch/android/gradle/wrapper/gradle-wrapper.jar b/mobile/thirdparty/transistor-background-fetch/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..13372aef5 Binary files /dev/null and b/mobile/thirdparty/transistor-background-fetch/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/mobile/thirdparty/transistor-background-fetch/android/gradle/wrapper/gradle-wrapper.properties b/mobile/thirdparty/transistor-background-fetch/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..945cac86b --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jul 15 09:21:17 EDT 2021 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/mobile/thirdparty/transistor-background-fetch/android/gradlew b/mobile/thirdparty/transistor-background-fetch/android/gradlew new file mode 100755 index 000000000..9d82f7891 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/mobile/thirdparty/transistor-background-fetch/android/gradlew.bat b/mobile/thirdparty/transistor-background-fetch/android/gradlew.bat new file mode 100644 index 000000000..8a0b282aa --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mobile/thirdparty/transistor-background-fetch/android/settings.gradle b/mobile/thirdparty/transistor-background-fetch/android/settings.gradle new file mode 100644 index 000000000..8b053e2cc --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/settings.gradle @@ -0,0 +1 @@ +include ':app', ':tsbackgroundfetch' diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/.gitignore b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/build.gradle b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/build.gradle new file mode 100644 index 000000000..3651bcfb8 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/build.gradle @@ -0,0 +1,152 @@ +apply plugin: 'com.android.library' +apply plugin: 'maven' +apply plugin: 'maven-publish' + +android { + compileSdkVersion rootProject.compileSdkVersion + defaultConfig { + minSdkVersion 16 + targetSdkVersion rootProject.targetSdkVersion + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + publishing { + publications { + tslocationmanager(MavenPublication) { + groupId 'com.transistorsoft' + artifactId 'tsbackgroundfetch' + version VERSION_NAME + artifact("$buildDir/outputs/aar/tsbackgroundfetch-release.aar") + + } + } + repositories { + mavenLocal() + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation "androidx.lifecycle:lifecycle-runtime:2.5.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion" + +} + +// Build Release +task buildRelease { task -> + task.dependsOn 'flutterRelease' +} + +// Publish Release. +task publishRelease { task -> + task.dependsOn 'assembleRelease' +} +tasks["publishRelease"].mustRunAfter("assembleRelease") +tasks["publishRelease"].finalizedBy("publish") + +def WORKSPACE_PATH = "/Users/chris/workspace" + +// Build local maven repo. +def LIBRARY_PATH = "com/transistorsoft/tsbackgroundfetch" +task buildLocalRepository { task -> + task.dependsOn 'publishRelease' + doLast { + delete "$buildDir/repo-local" + copy { + from "$buildDir/repo/$LIBRARY_PATH/$VERSION_NAME" + into "$buildDir/repo-local/$LIBRARY_PATH/$VERSION_NAME" + } + copy { + from("$buildDir/repo/$LIBRARY_PATH/maven-metadata.xml") + into("$buildDir/repo-local/$LIBRARY_PATH") + } + } +} + +def cordovaDir = "$WORKSPACE_PATH/background-geolocation/cordova/cordova-plugin-background-fetch" +task cordovaRelease { task -> + task.dependsOn 'buildLocalRepository' + doLast { + delete "$cordovaDir/src/android/libs" + copy { + // Maven repo format. + from("$buildDir/repo-local") + into("$cordovaDir/src/android/libs") + // OLD FORMAT + //from("$buildDir/outputs/aar/tsbackgroundfetch-release.aar") + //into("$cordovaDir/src/android/libs/tsbackgroundfetch") + //rename(/(.*)-release/, '$1-' + VERSION_NAME) + } + } +} + +def reactNativeDir = "$WORKSPACE_PATH/background-geolocation/react/react-native-background-fetch" +task reactNativeRelease { task -> + task.dependsOn 'buildLocalRepository' + doLast { + delete "$reactNativeDir/android/libs" + copy { + // Maven repo format. + from("$buildDir/repo-local") + into("$reactNativeDir/android/libs") + // OLD format. + //from("$buildDir/outputs/aar/tsbackgroundfetch-release.aar") + //into("$reactNativeDir/android/libs") + //rename(/(.*)-release/, '$1-' + VERSION_NAME) + } + } +} + +def flutterDir = "$WORKSPACE_PATH/background-geolocation/flutter/flutter_background_fetch" +task flutterRelease { task -> + task.dependsOn 'buildLocalRepository' + doLast { + delete "$flutterDir/android/libs" + copy { + // Maven repo format. + from("$buildDir/repo-local") + into("$flutterDir/android/libs") + // OLD format. + //from("$buildDir/outputs/aar/tsbackgroundfetch-release.aar") + //into("$flutterDir/android/libs") + //rename(/(.*)-release/, '$1-' + VERSION_NAME) + } + } +} + +def capacitorDir = "$WORKSPACE_PATH/background-geolocation/capacitor/capacitor-background-fetch" +task capacitorRelease { task -> + task.dependsOn 'buildLocalRepository' + doLast { + delete "$capacitorDir/android/libs" + copy { + // Maven repo format. + from("$buildDir/repo-local") + into("$capacitorDir/android/libs") + } + } +} + +task nativeScriptRelease(type: Copy) { + from('./build/outputs/aar/tsbackgroundfetch-release.aar') + into("$WORKSPACE_PATH/NativeScript/background-geolocation/nativescript-background-fetch/src/platforms/android/libs") + rename('tsbackgroundfetch-release.aar', 'tsbackgroundfetch.aar') +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/proguard-rules.pro b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/androidTest/java/com/transistorsoft/tsbackgroundfetch/ExampleInstrumentedTest.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/androidTest/java/com/transistorsoft/tsbackgroundfetch/ExampleInstrumentedTest.java new file mode 100644 index 000000000..2bb391be3 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/androidTest/java/com/transistorsoft/tsbackgroundfetch/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.transistorsoft.tsbackgroundfetch; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.transistorsoft.tsbackgroundfetch.test", appContext.getPackageName()); + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/AndroidManifest.xml b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/AndroidManifest.xml new file mode 100644 index 000000000..b89ce151f --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BGTask.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BGTask.java new file mode 100644 index 000000000..9a5b94d73 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BGTask.java @@ -0,0 +1,291 @@ +package com.transistorsoft.tsbackgroundfetch; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.PersistableBundle; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class BGTask { + static int MAX_TIME = 60000; + + private static final List mTasks = new ArrayList<>(); + + static BGTask getTask(String taskId) { + synchronized (mTasks) { + for (BGTask task : mTasks) { + if (task.hasTaskId(taskId)) return task; + } + } + return null; + } + + static void addTask(BGTask task) { + synchronized (mTasks) { + mTasks.add(task); + } + } + + static void removeTask(String taskId) { + synchronized (mTasks) { + BGTask found = null; + for (BGTask task : mTasks) { + if (task.hasTaskId(taskId)) { + found = task; + break; + } + } + if (found != null) { + mTasks.remove(found); + } + } + } + + static void clear() { + synchronized (mTasks) { + mTasks.clear(); + } + } + + private FetchJobService.CompletionHandler mCompletionHandler; + private String mTaskId; + private int mJobId; + private Runnable mTimeoutTask; + private boolean mTimedout = false; + + BGTask(final Context context, String taskId, FetchJobService.CompletionHandler handler, int jobId) { + mTaskId = taskId; + mCompletionHandler = handler; + mJobId = jobId; + + mTimeoutTask = new Runnable() { + @Override public void run() { + onTimeout(context); + } + }; + BackgroundFetch.getUiHandler().postDelayed(mTimeoutTask, MAX_TIME); + } + + public boolean getTimedOut() { + return mTimedout; + } + + public String getTaskId() { return mTaskId; } + + int getJobId() { return mJobId; } + + boolean hasTaskId(String taskId) { + return ((mTaskId != null) && mTaskId.equalsIgnoreCase(taskId)); + } + + void setCompletionHandler(FetchJobService.CompletionHandler handler) { + mCompletionHandler = handler; + } + + void finish() { + if (mCompletionHandler != null) { + mCompletionHandler.finish(); + } + if (mTimeoutTask != null) { + BackgroundFetch.getUiHandler().removeCallbacks(mTimeoutTask); + } + mCompletionHandler = null; + removeTask(mTaskId); + } + + static void reschedule(Context context, BackgroundFetchConfig existing, BackgroundFetchConfig config) { + BGTask existingTask = BGTask.getTask(existing.getTaskId()); + if (existingTask != null) { + existingTask.finish(); + } + cancel(context, existing.getTaskId(), existing.getJobId()); + + schedule(context, config); + } + + static void schedule(Context context, BackgroundFetchConfig config) { + Log.d(BackgroundFetch.TAG, config.toString()); + + long interval = (config.isFetchTask()) ? (TimeUnit.MINUTES.toMillis(config.getMinimumFetchInterval())) : config.getDelay(); + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !config.getForceAlarmManager()) { + // API 21+ uses new JobScheduler API + + JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + JobInfo.Builder builder = new JobInfo.Builder(config.getJobId(), new ComponentName(context, FetchJobService.class)) + .setRequiredNetworkType(config.getRequiredNetworkType()) + .setRequiresDeviceIdle(config.getRequiresDeviceIdle()) + .setRequiresCharging(config.getRequiresCharging()) + .setPersisted(config.getStartOnBoot() && !config.getStopOnTerminate()); + + if (config.getPeriodic()) { + if (android.os.Build.VERSION.SDK_INT >= 24) { + builder.setPeriodic(interval, interval); + } else { + builder.setPeriodic(interval); + } + } else { + builder.setMinimumLatency(interval); + } + PersistableBundle extras = new PersistableBundle(); + extras.putString(BackgroundFetchConfig.FIELD_TASK_ID, config.getTaskId()); + extras.putLong("scheduled_at", System.currentTimeMillis()); + + builder.setExtras(extras); + + if (android.os.Build.VERSION.SDK_INT >= 26) { + builder.setRequiresStorageNotLow(config.getRequiresStorageNotLow()); + builder.setRequiresBatteryNotLow(config.getRequiresBatteryNotLow()); + } + if (jobScheduler != null) { + jobScheduler.schedule(builder.build()); + } + } else { + // Everyone else get AlarmManager + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + if (alarmManager != null) { + PendingIntent pi = getAlarmPI(context, config.getTaskId()); + long delay = System.currentTimeMillis() + interval; + if (config.getPeriodic()) { + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, delay, interval, pi); + } else { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, delay, pi); + } else if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + alarmManager.setExact(AlarmManager.RTC_WAKEUP, delay, pi); + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, delay, pi); + } + } + } + } + } + + void onTimeout(Context context) { + mTimedout = true; + Log.d(BackgroundFetch.TAG, "[BGTask] timeout: " + mTaskId); + + BackgroundFetch adapter = BackgroundFetch.getInstance(context); + + if (!LifecycleManager.getInstance().isHeadless()) { + BackgroundFetch.Callback callback = adapter.getFetchCallback(); + if (callback != null) { + callback.onTimeout(mTaskId); + } + } else { + BackgroundFetchConfig config = adapter.getConfig(mTaskId); + if (config != null) { + if (config.getJobService() != null) { + fireHeadlessEvent(context, config); + } else { + adapter.finish(mTaskId); + } + } else { + Log.e(BackgroundFetch.TAG, "[BGTask] failed to load config for taskId: " + mTaskId); + adapter.finish(mTaskId); + } + } + } + + // Fire a headless background-fetch event by reflecting an instance of Config.jobServiceClass. + // Will attempt to reflect upon two different forms of Headless class: + // 1: new HeadlessTask(context, taskId) + // or + // 2: new HeadlessTask().onFetch(context, taskId); + // + void fireHeadlessEvent(Context context, BackgroundFetchConfig config) throws Error { + try { + // Get class via reflection. + Class HeadlessClass = Class.forName(config.getJobService()); + Class[] types = { Context.class, BGTask.class }; + Object[] params = { context, this}; + try { + // 1: new HeadlessTask(context, taskId); + Constructor constructor = HeadlessClass.getDeclaredConstructor(types); + constructor.newInstance(params); + } catch (NoSuchMethodException e) { + // 2: new HeadlessTask().onFetch(context, taskId); + Constructor constructor = HeadlessClass.getConstructor(); + Object instance = constructor.newInstance(); + Method onFetch = instance.getClass().getDeclaredMethod("onFetch", types); + onFetch.invoke(instance, params); + } + } catch (ClassNotFoundException e) { + throw new Error(e.getMessage()); + } catch (NoSuchMethodException e) { + throw new Error(e.getMessage()); + } catch (IllegalAccessException e) { + throw new Error(e.getMessage()); + } catch (InstantiationException e) { + throw new Error(e.getMessage()); + } catch (InvocationTargetException e) { + throw new Error(e.getMessage()); + } + } + + static void cancel(Context context, String taskId, int jobId) { + Log.i(BackgroundFetch.TAG, "- cancel taskId=" + taskId + ", jobId=" + jobId); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (jobId != 0)) { + JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + if (jobScheduler != null) { + jobScheduler.cancel(jobId); + } + } else { + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + if (alarmManager != null) { + alarmManager.cancel(BGTask.getAlarmPI(context, taskId)); + } + } + } + + static PendingIntent getAlarmPI(Context context, String taskId) { + Intent intent = new Intent(context, FetchAlarmReceiver.class); + intent.setAction(taskId); + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE); + } + + public String toString() { + return "[BGTask taskId=" + mTaskId + "]"; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("taskId", mTaskId); + map.put("timeout", mTimedout); + return map; + } + + public JSONObject toJson() { + JSONObject json = new JSONObject(); + try { + json.put("taskId", mTaskId); + json.put("timeout", mTimedout); + } catch (JSONException e) { + e.printStackTrace(); + } + return json; + } + + static class Error extends RuntimeException { + public Error(String msg) { + super(msg); + } + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetch.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetch.java new file mode 100644 index 000000000..24eaba27a --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetch.java @@ -0,0 +1,300 @@ +package com.transistorsoft.tsbackgroundfetch; + +import android.annotation.TargetApi; +import android.app.ActivityManager; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; + +import android.util.Log; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by chris on 2018-01-11. + */ + +public class BackgroundFetch { + public static final String TAG = "TSBackgroundFetch"; + + public static final String ACTION_CONFIGURE = "configure"; + public static final String ACTION_START = "start"; + public static final String ACTION_STOP = "stop"; + public static final String ACTION_FINISH = "finish"; + public static final String ACTION_STATUS = "status"; + public static final String ACTION_FORCE_RELOAD = TAG + "-forceReload"; + + public static final String EVENT_FETCH = ".event.BACKGROUND_FETCH"; + + public static final int STATUS_AVAILABLE = 2; + + private static BackgroundFetch mInstance = null; + + private static ExecutorService sThreadPool; + + private static Handler uiHandler; + + @SuppressWarnings({"WeakerAccess"}) + public static Handler getUiHandler() { + if (uiHandler == null) { + uiHandler = new Handler(Looper.getMainLooper()); + } + return uiHandler; + } + + @SuppressWarnings({"WeakerAccess"}) + public static ExecutorService getThreadPool() { + if (sThreadPool == null) { + sThreadPool = Executors.newCachedThreadPool(); + } + return sThreadPool; + } + + @SuppressWarnings({"WeakerAccess"}) + public static BackgroundFetch getInstance(Context context) { + if (mInstance == null) { + mInstance = getInstanceSynchronized(context.getApplicationContext()); + } + return mInstance; + } + + private static synchronized BackgroundFetch getInstanceSynchronized(Context context) { + if (mInstance == null) mInstance = new BackgroundFetch(context.getApplicationContext()); + return mInstance; + } + + private Context mContext; + private BackgroundFetch.Callback mFetchCallback; + + private final Map mConfig = new HashMap<>(); + + private BackgroundFetch(Context context) { + mContext = context; + // Start Lifecycle Observer to be notified when app enters background. + getUiHandler().post(LifecycleManager.getInstance()); + } + + @SuppressWarnings({"unused"}) + public void configure(BackgroundFetchConfig config, BackgroundFetch.Callback callback) { + Log.d(TAG, "- " + ACTION_CONFIGURE); + mFetchCallback = callback; + + synchronized (mConfig) { + if (mConfig.containsKey(config.getTaskId())) { + // Developer called `.configure` again. Re-configure the plugin by re-scheduling the fetch task. + BackgroundFetchConfig existing = mConfig.get(config.getTaskId()); + Log.d(TAG, "Re-configured existing task"); + BGTask.reschedule(mContext, existing, config); + mConfig.put(config.getTaskId(), config); + return; + } else { + mConfig.put(config.getTaskId(), config); + } + } + start(config.getTaskId()); + } + + void onBoot() { + BackgroundFetchConfig.load(mContext, new BackgroundFetchConfig.OnLoadCallback() { + @Override public void onLoad(List result) { + for (BackgroundFetchConfig config : result) { + if (!config.getStartOnBoot() || config.getStopOnTerminate()) { + config.destroy(mContext); + continue; + } + synchronized (mConfig) { + mConfig.put(config.getTaskId(), config); + } + if ((android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) || config.getForceAlarmManager()) { + if (config.isFetchTask()) { + start(config.getTaskId()); + } else { + scheduleTask(config); + } + } + } + } + }); + } + + @SuppressWarnings({"WeakerAccess"}) + @TargetApi(21) + public void start(String fetchTaskId) { + Log.d(TAG, "- " + ACTION_START); + + BGTask task = BGTask.getTask(fetchTaskId); + if (task != null) { + Log.e(TAG, "[" + TAG + " start] Task " + fetchTaskId + " already registered"); + return; + } + registerTask(fetchTaskId); + } + + @SuppressWarnings({"WeakerAccess"}) + public void stop(String taskId) { + String msg = "- " + ACTION_STOP; + if (taskId != null) { + msg += ": " + taskId; + } + Log.d(TAG, msg); + + if (taskId == null) { + synchronized (mConfig) { + for (BackgroundFetchConfig config : mConfig.values()) { + BGTask task = BGTask.getTask(config.getTaskId()); + if (task != null) { + task.finish(); + BGTask.removeTask(config.getTaskId()); + } + BGTask.cancel(mContext, config.getTaskId(), config.getJobId()); + config.destroy(mContext); + } + BGTask.clear(); + } + } else { + BGTask task = BGTask.getTask(taskId); + if (task != null) { + task.finish(); + BGTask.removeTask(task.getTaskId()); + } + BackgroundFetchConfig config = getConfig(taskId); + if (config != null) { + config.destroy(mContext); + BGTask.cancel(mContext, config.getTaskId(), config.getJobId()); + } + } + } + + @SuppressWarnings({"WeakerAccess"}) + public void scheduleTask(BackgroundFetchConfig config) { + synchronized (mConfig) { + if (mConfig.containsKey(config.getTaskId())) { + // This BackgroundFetchConfig already exists? Should we halt any existing Job/Alarm here? + } + config.save(mContext); + mConfig.put(config.getTaskId(), config); + } + String taskId = config.getTaskId(); + registerTask(taskId); + } + + @SuppressWarnings({"WeakerAccess"}) + public void finish(String taskId) { + Log.d(TAG, "- " + ACTION_FINISH + ": " + taskId); + + BGTask task = BGTask.getTask(taskId); + if (task != null) { + task.finish(); + } + + BackgroundFetchConfig config = getConfig(taskId); + + if ((config != null) && !config.getPeriodic()) { + config.destroy(mContext); + synchronized (mConfig) { + mConfig.remove(taskId); + } + } + } + + public int status() { + return STATUS_AVAILABLE; + } + + BackgroundFetch.Callback getFetchCallback() { + return mFetchCallback; + } + + void onFetch(final BGTask task) { + BGTask.addTask(task); + Log.d(TAG, "- Background Fetch event received: " + task.getTaskId()); + synchronized (mConfig) { + if (mConfig.isEmpty()) { + BackgroundFetchConfig.load(mContext, new BackgroundFetchConfig.OnLoadCallback() { + @Override + public void onLoad(List result) { + synchronized (mConfig) { + for (BackgroundFetchConfig config : result) { + mConfig.put(config.getTaskId(), config); + } + } + doFetch(task); + } + }); + + return; + } + } + doFetch(task); + } + + private void registerTask(String taskId) { + BackgroundFetchConfig config = getConfig(taskId); + + if (config == null) { + Log.e(TAG, "- registerTask failed to find BackgroundFetchConfig for taskId " + taskId); + return; + } + config.save(mContext); + + String msg = "- registerTask: " + taskId; + if (!config.getForceAlarmManager()) { + msg += " (jobId: " + config.getJobId() + ")"; + } + Log.d(TAG, msg); + + BGTask.schedule(mContext, config); + } + + private void doFetch(BGTask task) { + BackgroundFetchConfig config = getConfig(task.getTaskId()); + + if (config == null) { + BGTask.cancel(mContext, task.getTaskId(), task.getJobId()); + return; + } + + if (!LifecycleManager.getInstance().isHeadless()) { + if (mFetchCallback != null) { + mFetchCallback.onFetch(task.getTaskId()); + } + } else if (config.getStopOnTerminate()) { + Log.d(TAG, "- Stopping on terminate"); + stop(task.getTaskId()); + } else if (config.getJobService() != null) { + try { + task.fireHeadlessEvent(mContext, config); + } catch (BGTask.Error e) { + Log.e(TAG, "Headless task error: " + e.getMessage()); + e.printStackTrace(); + } + } else { + // {stopOnTerminate: false, forceReload: false} with no Headless JobService?? Don't know what else to do here but stop + Log.w(TAG, "- BackgroundFetch event has occurred while app is terminated but there's no jobService configured to handle the event. BackgroundFetch will terminate."); + finish(task.getTaskId()); + stop(task.getTaskId()); + } + } + + BackgroundFetchConfig getConfig(String taskId) { + synchronized (mConfig) { + return (mConfig.containsKey(taskId)) ? mConfig.get(taskId) : null; + } + } + + /** + * @interface BackgroundFetch.Callback + */ + public interface Callback { + void onFetch(String taskId); + void onTimeout(String taskId); + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetchConfig.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetchConfig.java new file mode 100644 index 000000000..5ff655fe9 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetchConfig.java @@ -0,0 +1,362 @@ +package com.transistorsoft.tsbackgroundfetch; + +import android.app.job.JobInfo; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Build; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Created by chris on 2018-01-11. + */ + +public class BackgroundFetchConfig { + private Builder config; + + private static final int MINIMUM_FETCH_INTERVAL = 1; + private static final int DEFAULT_FETCH_INTERVAL = 15; + + public static final String FIELD_TASK_ID = "taskId"; + public static final String FIELD_MINIMUM_FETCH_INTERVAL = "minimumFetchInterval"; + public static final String FIELD_START_ON_BOOT = "startOnBoot"; + public static final String FIELD_REQUIRED_NETWORK_TYPE = "requiredNetworkType"; + public static final String FIELD_REQUIRES_BATTERY_NOT_LOW = "requiresBatteryNotLow"; + public static final String FIELD_REQUIRES_CHARGING = "requiresCharging"; + public static final String FIELD_REQUIRES_DEVICE_IDLE = "requiresDeviceIdle"; + public static final String FIELD_REQUIRES_STORAGE_NOT_LOW = "requiresStorageNotLow"; + public static final String FIELD_STOP_ON_TERMINATE = "stopOnTerminate"; + public static final String FIELD_JOB_SERVICE = "jobService"; + public static final String FIELD_FORCE_ALARM_MANAGER = "forceAlarmManager"; + public static final String FIELD_PERIODIC = "periodic"; + public static final String FIELD_DELAY = "delay"; + public static final String FIELD_IS_FETCH_TASK = "isFetchTask"; + + public static class Builder { + private String taskId; + private int minimumFetchInterval = DEFAULT_FETCH_INTERVAL; + private long delay = -1; + private boolean periodic = false; + private boolean forceAlarmManager = false; + private boolean stopOnTerminate = true; + private boolean startOnBoot = false; + private int requiredNetworkType = 0; + private boolean requiresBatteryNotLow = false; + private boolean requiresCharging = false; + private boolean requiresDeviceIdle = false; + private boolean requiresStorageNotLow = false; + private boolean isFetchTask = false; + + private String jobService = null; + + public Builder setTaskId(String taskId) { + this.taskId = taskId; + return this; + } + + public Builder setIsFetchTask(boolean value) { + this.isFetchTask = value; + return this; + } + + public Builder setMinimumFetchInterval(int fetchInterval) { + if (fetchInterval >= MINIMUM_FETCH_INTERVAL) { + this.minimumFetchInterval = fetchInterval; + } + return this; + } + + public Builder setStopOnTerminate(boolean stopOnTerminate) { + this.stopOnTerminate = stopOnTerminate; + return this; + } + + public Builder setStartOnBoot(boolean startOnBoot) { + this.startOnBoot = startOnBoot; + return this; + } + + public Builder setRequiredNetworkType(int networkType) { + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + if ( + (networkType != JobInfo.NETWORK_TYPE_ANY) && + (networkType != JobInfo.NETWORK_TYPE_CELLULAR) && + (networkType != JobInfo.NETWORK_TYPE_NONE) && + (networkType != JobInfo.NETWORK_TYPE_NOT_ROAMING) && + (networkType != JobInfo.NETWORK_TYPE_UNMETERED) + ) { + Log.e(BackgroundFetch.TAG, "[ERROR] Invalid " + FIELD_REQUIRED_NETWORK_TYPE + ": " + networkType + "; Defaulting to NETWORK_TYPE_NONE"); + networkType = JobInfo.NETWORK_TYPE_NONE; + } + this.requiredNetworkType = networkType; + } + return this; + } + + public Builder setRequiresBatteryNotLow(boolean value) { + this.requiresBatteryNotLow = value; + return this; + } + + public Builder setRequiresCharging(boolean value) { + this.requiresCharging = value; + return this; + } + + public Builder setRequiresDeviceIdle(boolean value) { + this.requiresDeviceIdle = value; + return this; + } + + public Builder setRequiresStorageNotLow(boolean value) { + this.requiresStorageNotLow = value; + return this; + } + + public Builder setJobService(String className) { + this.jobService = className; + return this; + } + + public Builder setForceAlarmManager(boolean value) { + this.forceAlarmManager = value; + return this; + } + + public Builder setPeriodic(boolean value) { + this.periodic = value; + return this; + } + + public Builder setDelay(long value) { + this.delay = value; + return this; + } + + public BackgroundFetchConfig build() { + return new BackgroundFetchConfig(this); + } + + public BackgroundFetchConfig load(Context context, String taskId) { + SharedPreferences preferences = context.getSharedPreferences(BackgroundFetch.TAG + ":" + taskId, 0); + if (preferences.contains(FIELD_TASK_ID)) { + setTaskId(preferences.getString(FIELD_TASK_ID, taskId)); + } + if (preferences.contains(FIELD_IS_FETCH_TASK)) { + setIsFetchTask(preferences.getBoolean(FIELD_IS_FETCH_TASK, isFetchTask)); + } + if (preferences.contains(FIELD_MINIMUM_FETCH_INTERVAL)) { + setMinimumFetchInterval(preferences.getInt(FIELD_MINIMUM_FETCH_INTERVAL, minimumFetchInterval)); + } + if (preferences.contains(FIELD_STOP_ON_TERMINATE)) { + setStopOnTerminate(preferences.getBoolean(FIELD_STOP_ON_TERMINATE, stopOnTerminate)); + } + if (preferences.contains(FIELD_REQUIRED_NETWORK_TYPE)) { + setRequiredNetworkType(preferences.getInt(FIELD_REQUIRED_NETWORK_TYPE, requiredNetworkType)); + } + if (preferences.contains(FIELD_REQUIRES_BATTERY_NOT_LOW)) { + setRequiresBatteryNotLow(preferences.getBoolean(FIELD_REQUIRES_BATTERY_NOT_LOW, requiresBatteryNotLow)); + } + if (preferences.contains(FIELD_REQUIRES_CHARGING)) { + setRequiresCharging(preferences.getBoolean(FIELD_REQUIRES_CHARGING, requiresCharging)); + } + if (preferences.contains(FIELD_REQUIRES_DEVICE_IDLE)) { + setRequiresDeviceIdle(preferences.getBoolean(FIELD_REQUIRES_DEVICE_IDLE, requiresDeviceIdle)); + } + if (preferences.contains(FIELD_REQUIRES_STORAGE_NOT_LOW)) { + setRequiresStorageNotLow(preferences.getBoolean(FIELD_REQUIRES_STORAGE_NOT_LOW, requiresStorageNotLow)); + } + if (preferences.contains(FIELD_START_ON_BOOT)) { + setStartOnBoot(preferences.getBoolean(FIELD_START_ON_BOOT, startOnBoot)); + } + if (preferences.contains(FIELD_JOB_SERVICE)) { + setJobService(preferences.getString(FIELD_JOB_SERVICE, null)); + } + if (preferences.contains(FIELD_FORCE_ALARM_MANAGER)) { + setForceAlarmManager(preferences.getBoolean(FIELD_FORCE_ALARM_MANAGER, forceAlarmManager)); + } + if (preferences.contains(FIELD_PERIODIC)) { + setPeriodic(preferences.getBoolean(FIELD_PERIODIC, periodic)); + } + if (preferences.contains(FIELD_DELAY)) { + setDelay(preferences.getLong(FIELD_DELAY, delay)); + } + return new BackgroundFetchConfig(this); + } + } + + private BackgroundFetchConfig(Builder builder) { + config = builder; + // Validate config + if (config.jobService == null) { + if (!config.stopOnTerminate) { + Log.w(BackgroundFetch.TAG, "- Configuration error: In order to use stopOnTerminate: false, you must set enableHeadless: true"); + config.setStopOnTerminate(true); + } + if (config.startOnBoot) { + Log.w(BackgroundFetch.TAG, "- Configuration error: In order to use startOnBoot: true, you must enableHeadless: true"); + config.setStartOnBoot(false); + } + } + } + + void save(Context context) { + SharedPreferences preferences = context.getSharedPreferences(BackgroundFetch.TAG, 0); + Set taskIds = preferences.getStringSet("tasks", new HashSet()); + if (taskIds == null) { + taskIds = new HashSet<>(); + } + if (!taskIds.contains(config.taskId)) { + Set newIds = new HashSet<>(taskIds); + newIds.add(config.taskId); + + SharedPreferences.Editor editor = preferences.edit(); + editor.putStringSet("tasks", newIds); + editor.apply(); + } + + SharedPreferences.Editor editor = context.getSharedPreferences(BackgroundFetch.TAG + ":" + config.taskId, 0).edit(); + + editor.putString(FIELD_TASK_ID, config.taskId); + editor.putBoolean(FIELD_IS_FETCH_TASK, config.isFetchTask); + editor.putInt(FIELD_MINIMUM_FETCH_INTERVAL, config.minimumFetchInterval); + editor.putBoolean(FIELD_STOP_ON_TERMINATE, config.stopOnTerminate); + editor.putBoolean(FIELD_START_ON_BOOT, config.startOnBoot); + editor.putInt(FIELD_REQUIRED_NETWORK_TYPE, config.requiredNetworkType); + editor.putBoolean(FIELD_REQUIRES_BATTERY_NOT_LOW, config.requiresBatteryNotLow); + editor.putBoolean(FIELD_REQUIRES_CHARGING, config.requiresCharging); + editor.putBoolean(FIELD_REQUIRES_DEVICE_IDLE, config.requiresDeviceIdle); + editor.putBoolean(FIELD_REQUIRES_STORAGE_NOT_LOW, config.requiresStorageNotLow); + editor.putString(FIELD_JOB_SERVICE, config.jobService); + editor.putBoolean(FIELD_FORCE_ALARM_MANAGER, config.forceAlarmManager); + editor.putBoolean(FIELD_PERIODIC, config.periodic); + editor.putLong(FIELD_DELAY, config.delay); + + editor.apply(); + } + + void destroy(Context context) { + SharedPreferences preferences = context.getSharedPreferences(BackgroundFetch.TAG, 0); + Set taskIds = preferences.getStringSet("tasks", new HashSet()); + if (taskIds == null) { + taskIds = new HashSet<>(); + } + if (taskIds.contains(config.taskId)) { + Set newIds = new HashSet<>(taskIds); + newIds.remove(config.taskId); + SharedPreferences.Editor editor = preferences.edit(); + editor.putStringSet("tasks", newIds); + editor.apply(); + } + if (!config.isFetchTask) { + SharedPreferences.Editor editor = context.getSharedPreferences(BackgroundFetch.TAG + ":" + config.taskId, 0).edit(); + editor.clear(); + editor.apply(); + } + } + + static int FETCH_JOB_ID = 999; + + boolean isFetchTask() { + return config.isFetchTask; + } + + public String getTaskId() { return config.taskId; } + public int getMinimumFetchInterval() { + return config.minimumFetchInterval; + } + + public int getRequiredNetworkType() { return config.requiredNetworkType; } + public boolean getRequiresBatteryNotLow() { return config.requiresBatteryNotLow; } + public boolean getRequiresCharging() { return config.requiresCharging; } + public boolean getRequiresDeviceIdle() { return config.requiresDeviceIdle; } + public boolean getRequiresStorageNotLow() { return config.requiresStorageNotLow; } + public boolean getStopOnTerminate() { + return config.stopOnTerminate; + } + public boolean getStartOnBoot() { + return config.startOnBoot; + } + + public String getJobService() { return config.jobService; } + + public boolean getForceAlarmManager() { + return config.forceAlarmManager; + } + + public boolean getPeriodic() { + return config.periodic || isFetchTask(); + } + + public long getDelay() { + return config.delay; + } + + int getJobId() { + if (config.forceAlarmManager) { + return 0; + } else { + return (isFetchTask()) ? FETCH_JOB_ID : config.taskId.hashCode(); + } + } + + public String toString() { + JSONObject output = new JSONObject(); + try { + output.put(FIELD_TASK_ID, config.taskId); + output.put(FIELD_IS_FETCH_TASK, config.isFetchTask); + output.put(FIELD_MINIMUM_FETCH_INTERVAL, config.minimumFetchInterval); + output.put(FIELD_STOP_ON_TERMINATE, config.stopOnTerminate); + output.put(FIELD_REQUIRED_NETWORK_TYPE, config.requiredNetworkType); + output.put(FIELD_REQUIRES_BATTERY_NOT_LOW, config.requiresBatteryNotLow); + output.put(FIELD_REQUIRES_CHARGING, config.requiresCharging); + output.put(FIELD_REQUIRES_DEVICE_IDLE, config.requiresDeviceIdle); + output.put(FIELD_REQUIRES_STORAGE_NOT_LOW, config.requiresStorageNotLow); + output.put(FIELD_START_ON_BOOT, config.startOnBoot); + output.put(FIELD_JOB_SERVICE, config.jobService); + output.put(FIELD_FORCE_ALARM_MANAGER, config.forceAlarmManager); + output.put(FIELD_PERIODIC, getPeriodic()); + output.put(FIELD_DELAY, config.delay); + + return output.toString(2); + } catch (JSONException e) { + e.printStackTrace(); + return output.toString(); + } + } + + static void load(final Context context, final OnLoadCallback callback) { + BackgroundFetch.getThreadPool().execute(new Runnable() { + @Override + public void run() { + final List result = new ArrayList<>(); + + SharedPreferences preferences = context.getSharedPreferences(BackgroundFetch.TAG, 0); + Set taskIds = preferences.getStringSet("tasks", new HashSet()); + + if (taskIds != null) { + for (String taskId : taskIds) { + result.add(new BackgroundFetchConfig.Builder().load(context, taskId)); + } + } + BackgroundFetch.getUiHandler().post(new Runnable() { + @Override public void run() { + callback.onLoad(result); + } + }); + } + }); + } + + interface OnLoadCallback { + void onLoad(Listconfig); + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BootReceiver.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BootReceiver.java new file mode 100644 index 000000000..b161c082e --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/BootReceiver.java @@ -0,0 +1,24 @@ +package com.transistorsoft.tsbackgroundfetch; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +/** + * Created by chris on 2018-01-15. + */ + +public class BootReceiver extends BroadcastReceiver { + + @Override + public void onReceive(final Context context, Intent intent) { + String action = intent.getAction(); + Log.d(BackgroundFetch.TAG, "BootReceiver: " + action); + BackgroundFetch.getThreadPool().execute(new Runnable() { + @Override public void run() { + BackgroundFetch.getInstance(context.getApplicationContext()).onBoot(); + } + }); + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchAlarmReceiver.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchAlarmReceiver.java new file mode 100644 index 000000000..afcb900dd --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchAlarmReceiver.java @@ -0,0 +1,40 @@ +package com.transistorsoft.tsbackgroundfetch; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.util.Log; + +import static android.content.Context.POWER_SERVICE; + +/** + * Created by chris on 2018-01-11. + */ + +public class FetchAlarmReceiver extends BroadcastReceiver { + + @Override + public void onReceive(final Context context, Intent intent) { + PowerManager powerManager = (PowerManager) context.getSystemService(POWER_SERVICE); + final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BackgroundFetch.TAG + "::" + intent.getAction()); + // WakeLock expires in MAX_TIME + 4s buffer. + wakeLock.acquire((BGTask.MAX_TIME + 4000)); + + final String taskId = intent.getAction(); + + final FetchJobService.CompletionHandler completionHandler = new FetchJobService.CompletionHandler() { + @Override + public void finish() { + if (wakeLock.isHeld()) { + wakeLock.release(); + Log.d(BackgroundFetch.TAG, "- FetchAlarmReceiver finish"); + } + } + }; + + BGTask task = new BGTask(context, taskId, completionHandler, 0); + + BackgroundFetch.getInstance(context.getApplicationContext()).onFetch(task); + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchJobService.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchJobService.java new file mode 100644 index 000000000..a9f51fd5d --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchJobService.java @@ -0,0 +1,59 @@ +package com.transistorsoft.tsbackgroundfetch; + +import android.annotation.TargetApi; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.os.PersistableBundle; +import android.util.Log; + +/** + * Created by chris on 2018-01-11. + */ +@TargetApi(21) +public class FetchJobService extends JobService { + @Override + public boolean onStartJob(final JobParameters params) { + PersistableBundle extras = params.getExtras(); + long scheduleAt = extras.getLong("scheduled_at"); + long dt = System.currentTimeMillis() - scheduleAt; + // Scheduled < 1s ago? Ignore. + if (dt < 1000) { + // JobScheduler always immediately fires an initial event on Periodic jobs -- We IGNORE these. + jobFinished(params, false); + return true; + } + + final String taskId = extras.getString(BackgroundFetchConfig.FIELD_TASK_ID); + + CompletionHandler completionHandler = new CompletionHandler() { + @Override + public void finish() { + Log.d(BackgroundFetch.TAG, "- jobFinished"); + jobFinished(params, false); + } + }; + BGTask task = new BGTask(this, taskId, completionHandler, params.getJobId()); + BackgroundFetch.getInstance(getApplicationContext()).onFetch(task); + + return true; + } + + @Override + public boolean onStopJob(final JobParameters params) { + Log.d(BackgroundFetch.TAG, "- onStopJob"); + + PersistableBundle extras = params.getExtras(); + final String taskId = extras.getString(BackgroundFetchConfig.FIELD_TASK_ID); + + BGTask task = BGTask.getTask(taskId); + if (task != null) { + task.onTimeout(getApplicationContext()); + } + jobFinished(params, false); + return true; + } + + public interface CompletionHandler { + void finish(); + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/LifecycleManager.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/LifecycleManager.java new file mode 100644 index 000000000..55455b693 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/java/com/transistorsoft/tsbackgroundfetch/LifecycleManager.java @@ -0,0 +1,225 @@ +package com.transistorsoft.tsbackgroundfetch; + +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ProcessLifecycleOwner; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Component for managing app life-cycle changes, including headless-mode. + */ +public class LifecycleManager implements DefaultLifecycleObserver, Runnable { + private static LifecycleManager sInstance; + + public static LifecycleManager getInstance() { + if (sInstance == null) { + sInstance = getInstanceSynchronized(); + } + return sInstance; + } + + private static synchronized LifecycleManager getInstanceSynchronized() { + if (sInstance == null) sInstance = new LifecycleManager(); + return sInstance; + } + + private final List mHeadlessChangeCallbacks = new ArrayList<>(); + private final List mStateChangeCallbacks = new ArrayList<>(); + private final Handler mHandler; + private Runnable mHeadlessChangeEvent; + + private final AtomicBoolean mIsBackground = new AtomicBoolean(true); + private final AtomicBoolean mIsHeadless = new AtomicBoolean(true); + private final AtomicBoolean mStarted = new AtomicBoolean(false); + private final AtomicBoolean mPaused = new AtomicBoolean(false); + + private LifecycleManager() { + mHandler = new Handler(Looper.getMainLooper()); + onHeadlessChange(isHeadless -> { + if (isHeadless) { + Log.d(BackgroundFetch.TAG, "☯️ HeadlessMode? " + isHeadless); + } + }); + } + + /** + * Temporarily disable responding to pause/resume events. This was placed here for handling TSLocationManagerActivity events + * whose presentation causes onPause / onResume events that we don't want to react to. + */ + public void pause() { + mPaused.set(true); + } + + /** + * Re-engage responding to pause/resume events. + */ + public void resume() { + mPaused.set(false); + } + /** + * Are we in the background? + * @return boolean + */ + public boolean isBackground() { + return mIsBackground.get(); + } + /** + * Are we headless + * @return boolean + */ + public boolean isHeadless() { + return mIsHeadless.get(); + } + /** + * Explicitly state that we are headless. Probably called when MainActivity is known to have been destroyed. + * @param value boolean + */ + public void setHeadless(boolean value) { + mIsHeadless.set(value); + if (mIsHeadless.get()) { + Log.d(BackgroundFetch.TAG,"☯️ HeadlessMode? " + mIsHeadless); + } + if (mHeadlessChangeEvent != null) { + mHandler.removeCallbacks(mHeadlessChangeEvent); + mStarted.set(true); + fireHeadlessChangeListeners(); + } + } + /** + * Register Headless-mode change listener. + */ + public void onHeadlessChange(OnHeadlessChangeCallback callback) { + if (mStarted.get()) { + callback.onChange(mIsHeadless.get()); + return; + } + synchronized (mHeadlessChangeCallbacks) { + mHeadlessChangeCallbacks.add(callback); + } + } + /** + * Register pause/resume listener. + */ + public void onStateChange(OnStateChangeCallback callback) { + synchronized (mStateChangeCallbacks) { + mStateChangeCallbacks.add(callback); + } + } + + /** + * Regiser the LifecycleObserver + */ + @Override + public void run() { + ProcessLifecycleOwner.get().getLifecycle().addObserver(this); + } + + @Override + public void onCreate(@NonNull LifecycleOwner owner) { + Log.d(BackgroundFetch.TAG,"☯️ onCreate"); + // If this 50ms Timer fires before onStart, we are headless + mHeadlessChangeEvent = new Runnable() { + @Override public void run() { + mStarted.set(true); + fireHeadlessChangeListeners(); + } + }; + + mHandler.postDelayed(mHeadlessChangeEvent, 50); + mIsHeadless.set(true); + mIsBackground.set(true); + } + + @Override + public void onStart(@NonNull LifecycleOwner owner) { + Log.d(BackgroundFetch.TAG, "☯️ onStart"); + // Cancel StateChange Timer. + if (mPaused.get()) { + return; + } + if (mHeadlessChangeEvent != null) { + mHandler.removeCallbacks(mHeadlessChangeEvent); + } + + mStarted.set(true); + mIsHeadless.set(false); + mIsBackground.set(false); + + // Fire listeners. + fireHeadlessChangeListeners(); + } + + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + Log.d(BackgroundFetch.TAG, "☯️ onDestroy"); + mIsBackground.set(true); + mIsHeadless.set(true); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + Log.d(BackgroundFetch.TAG, "☯️ onStop"); + if (mPaused.compareAndSet(true, false)) { + return; + } + mIsBackground.set(true); + + } + + @Override + public void onPause(@NonNull LifecycleOwner owner) { + Log.d(BackgroundFetch.TAG, "☯️ onPause"); + mIsBackground.set(true); + fireStateChangeListeners(false); + } + + @Override + public void onResume(@NonNull LifecycleOwner owner) { + Log.d(BackgroundFetch.TAG, "☯️ onResume"); + if (mPaused.get()) { + return; + } + mIsBackground.set(false); + mIsHeadless.set(false); + fireStateChangeListeners(true); + } + + /// Fire pause/resume change listeners + private void fireStateChangeListeners(boolean isForeground) { + synchronized (mStateChangeCallbacks) { + for (OnStateChangeCallback callback : mStateChangeCallbacks) { + callback.onChange(isForeground); + } + } + } + + /// Fire headless mode change listeners. + private void fireHeadlessChangeListeners() { + if (mHeadlessChangeEvent != null) { + mHandler.removeCallbacks(mHeadlessChangeEvent); + mHeadlessChangeEvent = null; + } + synchronized (mHeadlessChangeCallbacks) { + for (OnHeadlessChangeCallback callback : mHeadlessChangeCallbacks) { + callback.onChange(mIsHeadless.get()); + } + mHeadlessChangeCallbacks.clear(); + } + } + + public interface OnHeadlessChangeCallback { + void onChange(boolean isHeadless); + } + + public interface OnStateChangeCallback { + void onChange(boolean isForeground); + } +} diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/res/values/strings.xml b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/res/values/strings.xml new file mode 100644 index 000000000..17b4bbaa0 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + TSBackgroundFetch + diff --git a/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/test/java/com/transistorsoft/tsbackgroundfetch/ExampleUnitTest.java b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/test/java/com/transistorsoft/tsbackgroundfetch/ExampleUnitTest.java new file mode 100644 index 000000000..99449e071 --- /dev/null +++ b/mobile/thirdparty/transistor-background-fetch/android/tsbackgroundfetch/src/test/java/com/transistorsoft/tsbackgroundfetch/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.transistorsoft.tsbackgroundfetch; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file