commit 106222b00cc969aaefaa4dd754904ee4a404c1d6 Author: sipp11 Date: Tue Oct 1 17:28:16 2019 +0900 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b75303 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..ce889bd --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,119 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dictionaries/yc00057.xml b/.idea/dictionaries/yc00057.xml new file mode 100644 index 0000000..90e14e5 --- /dev/null +++ b/.idea/dictionaries/yc00057.xml @@ -0,0 +1,7 @@ + + + + coroutine + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..af0bbdd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..b1d7fa4 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + endless-service + Project endless-service created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..e889521 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e501a99 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Endless Service + +This is just a project showing how to run an Android Foreground Service that will never stop running. diff --git a/app/.classpath b/app/.classpath new file mode 100644 index 0000000..eb19361 --- /dev/null +++ b/app/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/.project b/app/.project new file mode 100644 index 0000000..ac485d7 --- /dev/null +++ b/app/.project @@ -0,0 +1,23 @@ + + + app + Project app created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/app/.settings/org.eclipse.buildship.core.prefs b/app/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..b1886ad --- /dev/null +++ b/app/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=.. +eclipse.preferences.version=1 diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..84ca48f --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "co.zzyzx.sensorlogger" + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.github.kittinunf.fuel:fuel:2.1.0' + implementation 'com.github.kittinunf.fuel:fuel-android:2.1.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-M2' + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/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/app/src/androidTest/java/co/zzyzx/sensorlogger/ExampleInstrumentedTest.kt b/app/src/androidTest/java/co/zzyzx/sensorlogger/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..79b8410 --- /dev/null +++ b/app/src/androidTest/java/co/zzyzx/sensorlogger/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package co.zzyzx.sensorlogger + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("co.zzyzx.sensorlogger", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..15f70c6 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/co/zzyzx/sensorlogger/Actions.kt b/app/src/main/java/co/zzyzx/sensorlogger/Actions.kt new file mode 100644 index 0000000..3cfdf66 --- /dev/null +++ b/app/src/main/java/co/zzyzx/sensorlogger/Actions.kt @@ -0,0 +1,6 @@ +package co.zzyzx.sensorlogger + +enum class Actions { + START, + STOP +} diff --git a/app/src/main/java/co/zzyzx/sensorlogger/EndlessService.kt b/app/src/main/java/co/zzyzx/sensorlogger/EndlessService.kt new file mode 100644 index 0000000..efc1cbc --- /dev/null +++ b/app/src/main/java/co/zzyzx/sensorlogger/EndlessService.kt @@ -0,0 +1,236 @@ +package co.zzyzx.sensorlogger + +import android.app.* +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.os.Build +import android.os.IBinder +import android.os.PowerManager +import android.provider.Settings +import android.support.v4.app.NotificationCompat +import android.widget.Toast +import java.text.SimpleDateFormat +import java.util.* +import com.github.kittinunf.fuel.Fuel +//import com.github.kittinunf.fuel.core.extensions.jsonBody +import kotlinx.coroutines.* + + +class EndlessService : Service(), SensorEventListener { + + private var wakeLock: PowerManager.WakeLock? = null + private var isServiceStarted = false + private lateinit var nm: NotificationManager + private val notificationChannelId = "ENDLESS SERVICE CHANNEL" + private val notificationId = 1011 + + private lateinit var mSensorManager: SensorManager + private lateinit var mAccelerometer: Sensor + + override fun onBind(intent: Intent): IBinder? { + log("Some component want to bind with the service") + // We don't provide binding, so return null + return null + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + log("onStartCommand executed with startId: $startId") + if (intent != null) { + val action = intent.action + log("using an intent with action $action") + when (action) { + Actions.START.name -> startService() + Actions.STOP.name -> stopService() + else -> log("This should never happen. No action in the received intent") + } + } else { + log( + "with a null intent. It has been probably restarted by the system." + ) + } + + + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL) + + // by returning this we make sure the service is restarted if the system kills the service + return START_STICKY + } + + override fun onCreate() { + super.onCreate() + log("The service has been created".toUpperCase()) + val notification = createNotification() + notification.flags = Notification.FLAG_ONGOING_EVENT + startForeground(notificationId, notification) + + mSensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager + mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + + + } + + override fun onDestroy() { + super.onDestroy() + mSensorManager.unregisterListener(this) + log("The service has been destroyed".toUpperCase()) + Toast.makeText(this, "Service destroyed", Toast.LENGTH_SHORT).show() + } + + private fun startService() { + if (isServiceStarted) return + log("Starting the foreground service task") + Toast.makeText(this, "Service starting its task", Toast.LENGTH_SHORT).show() + isServiceStarted = true + setServiceState(this, ServiceState.STARTED) + + // we need this lock so our service gets not affected by Doze Mode + wakeLock = + (getSystemService(Context.POWER_SERVICE) as PowerManager).run { + newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply { + acquire() + } + } + + // we're starting a loop in a coroutine + GlobalScope.launch(Dispatchers.IO) { + while (isServiceStarted) { + launch(Dispatchers.IO) { + pingFakeServer() + } + delay(1 * 60 * 1000) + } + log("End of the loop for the service") + } + } + + private fun stopService() { + log("Stopping the foreground service") + Toast.makeText(this, "Service stopping", Toast.LENGTH_SHORT).show() + try { + wakeLock?.let { + if (it.isHeld) { + it.release() + } + } + stopForeground(true) + stopSelf() + } catch (e: Exception) { + log("Service stopped without being started: ${e.message}") + } + isServiceStarted = false + setServiceState(this, ServiceState.STOPPED) + nm.cancelAll() + } + + override fun onAccuracyChanged(p0: Sensor?, p1: Int) { + + } + + override fun onSensorChanged(evt: SensorEvent) { + fun Double.format(digits: Int) = java.lang.String.format("%.${digits}f", this) + val accelGravity = arrayListOf(0.toDouble(), 0.toDouble(), 0.toDouble()) + + when (evt.sensor?.type) { + Sensor.TYPE_ACCELEROMETER -> { + + val accelLin = arrayOf(0.toDouble(), 0.toDouble(), 0.toDouble()) + val alpha = 0.8f + accelGravity[0] = alpha * accelGravity[0] + (1 - alpha) * evt.values[0] + accelGravity[1] = alpha * accelGravity[1] + (1 - alpha) * evt.values[1] + accelGravity[2] = alpha * accelGravity[2] + (1 - alpha) * evt.values[2] + accelLin[0] = evt.values[0] - accelGravity[0] + accelLin[1] = evt.values[1] - accelGravity[1] + accelLin[2] = evt.values[2] - accelGravity[2] + + val txt = + "[accel] (${accelLin[0].format(3)}, ${accelLin[1].format(3)}, ${accelLin[2].format( + 3 + )})" + val notification = createNotification(txt) + nm.notify(notificationId, notification) + } + } + } + + + private fun pingFakeServer() { + val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.mmmZ") + val gmtTime = df.format(Date()) + + val deviceId = Settings.Secure.getString( + applicationContext.contentResolver, + Settings.Secure.ANDROID_ID + ) + +// val json = +// """ +// { +// "deviceId": "$deviceId", +// "createdAt": "$gmtTime" +// } +// """ + try { + Fuel.get("https://tool.everyday.in.th/ip?format=text") +// .jsonBody(json) + .response { _, _, result -> + val (bytes, error) = result + if (bytes != null) { + log("[response bytes] ${String(bytes)}") + val notification = createNotification("[req] ${gmtTime} - ${String(bytes)}") + nm.notify(notificationId, notification) + } else { + log("[response error] ${error?.message}") + } + } + } catch (e: Exception) { + log("Error making the request: ${e.message}") + } + } + + private fun createNotification(text: CharSequence = "Howdy?"): Notification { + + // depending on the Android API that we're dealing with we will have + // to use a specific method to create the notification + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channel = NotificationChannel( + notificationChannelId, + "SL notifications channel", + NotificationManager.IMPORTANCE_DEFAULT + ).let { + it.description = "SL Service channel" + it.enableLights(true) + it.lightColor = Color.RED + it.enableVibration(true) + it.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400) + it + } + nm.createNotificationChannel(channel) + } + + val pendingIntent: PendingIntent = + Intent(this, MainActivity::class.java).let { notificationIntent -> + PendingIntent.getActivity(this, 0, notificationIntent, 0) + } + + val builder: Notification.Builder = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Notification.Builder( + this, + notificationChannelId + ) else Notification.Builder(this) + + return builder + .setContentTitle("SensorLogger") + .setContentText(text) + .setContentIntent(pendingIntent) + .setSmallIcon(R.mipmap.ic_launcher) + .setTicker("Ticker text") + .setPriority(Notification.PRIORITY_DEFAULT) // for under android 26 compatibility + .build() + } +} diff --git a/app/src/main/java/co/zzyzx/sensorlogger/MainActivity.kt b/app/src/main/java/co/zzyzx/sensorlogger/MainActivity.kt new file mode 100644 index 0000000..ccfaaa2 --- /dev/null +++ b/app/src/main/java/co/zzyzx/sensorlogger/MainActivity.kt @@ -0,0 +1,46 @@ +package co.zzyzx.sensorlogger + +import android.content.Intent +import android.os.Build +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import android.widget.Button + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + title = getString(R.string.app_name) + + findViewById