diff --git a/app/build.gradle b/app/build.gradle
index b6eb470a..0fee4d11 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,8 +11,8 @@ android {
applicationId 'org.radarcns.detail'
minSdkVersion 21
targetSdkVersion 30
- versionCode 51
- versionName '1.0.12'
+ versionCode 52
+ versionName '1.0.13'
manifestPlaceholders = ['appAuthRedirectScheme': 'org.radarbase.passive.app']
multiDexEnabled true
}
@@ -104,6 +104,25 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testRuntimeOnly 'org.slf4j:slf4j-simple:1.7.30'
+
+ implementation("com.android.volley:volley:1.2.0")
+
+ // Kotlin + coroutines
+ implementation("androidx.work:work-runtime-ktx:2.5.0")
+
+ // optional - RxJava2 support
+ implementation("androidx.work:work-rxjava2:2.5.0")
+
+ // optional - GCMNetworkManager support
+ implementation("androidx.work:work-gcm:2.5.0")
+
+ // optional - Test helpers
+ androidTestImplementation("androidx.work:work-testing:2.5.0")
+
+ // optional - Multiprocess support
+ implementation "androidx.work:work-multiprocess:2.5.0"
+
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4c31ee8a..094e3ecb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,6 +17,8 @@
+
+
@@ -29,6 +31,17 @@
android:name=".RadarApplicationImpl"
android:fullBackupContent="@xml/backup_descriptor"
android:installLocation="internalOnly">
+
+
+
+
+
@@ -88,9 +101,11 @@
android:parentActivityName=".MainActivityImpl"/>
-
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_update_24.xml b/app/src/main/res/drawable/ic_baseline_update_24.xml
new file mode 100644
index 00000000..3b923020
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_update_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index 2ff624bc..efcc63eb 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -65,7 +65,7 @@
android:onClick="startReset"
app:layout_constraintTop_toBottomOf="@id/enableDataHighPrioritySwitch"
app:layout_constraintBottom_toBottomOf="@id/constraintLayout"
- app:layout_constraintStart_toStartOf="@id/constraintLayout" />
+ app:layout_constraintStart_toStartOf="@id/constraintLayout"/>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6dc7be46..7937656b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -121,4 +121,16 @@
com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior
Please start RADAR pRMT
RADAR pRMT has automatically been stopped. Please press this notification to start it again.
+
+
+ Updates
+ New version is available.\nClick to download.
+ Version: %1$s
+ A new version of %1$s App (%2$s) is available.
+ You installed the latest version of %1$s App.
+ No
+ Don\'t Show Again
+ Start Downloading
+ Do you want to install it?
+ Error in package name!
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 00000000..b2389ae9
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/remote_config_defaults.xml b/app/src/main/res/xml/remote_config_defaults.xml
index dc69a1e6..f701a7c5 100644
--- a/app/src/main/res/xml/remote_config_defaults.xml
+++ b/app/src/main/res/xml/remote_config_defaults.xml
@@ -86,5 +86,21 @@
privacy_policy
http://info.thehyve.nl/radar-cns-privacy-policy
+
+ update_check
+ true
+
+
+ update_check_notification
+ true
+
+
+ update_check_frequency
+ day
+
+
+ update_releases_url
+ https://api.github.com/repos/RADAR-base/radar-prmt-android/releases
+
diff --git a/app/src/main/java/org/radarcns/detail/MainActivityImpl.kt b/app/src/playStore/java/org/radarcns/detail/MainActivityImpl.kt
similarity index 100%
rename from app/src/main/java/org/radarcns/detail/MainActivityImpl.kt
rename to app/src/playStore/java/org/radarcns/detail/MainActivityImpl.kt
diff --git a/app/src/main/res/layout/compact_overview.xml b/app/src/playStore/res/layout/compact_overview.xml
similarity index 100%
rename from app/src/main/res/layout/compact_overview.xml
rename to app/src/playStore/res/layout/compact_overview.xml
diff --git a/app/src/selfRelease/java/org/radarcns/detail/ComparePackageNames.kt b/app/src/selfRelease/java/org/radarcns/detail/ComparePackageNames.kt
new file mode 100644
index 00000000..6c82e1b8
--- /dev/null
+++ b/app/src/selfRelease/java/org/radarcns/detail/ComparePackageNames.kt
@@ -0,0 +1,19 @@
+package org.radarcns.detail
+
+import android.content.Context
+import android.content.pm.PackageManager.NameNotFoundException
+import android.util.Log
+
+fun isPackageNameSame(context: Context, apkPath: String): Boolean {
+ return context.packageName == getUpdatePackageName(context, apkPath)
+}
+
+fun getUpdatePackageName(context: Context, apkPath: String): String? {
+ try {
+ return context.packageManager.getPackageArchiveInfo(apkPath, 0)?.packageName
+ } catch (e: NameNotFoundException) {
+ Log.e("ComparePackageNames", "Cannot resolve package name for $apkPath", e)
+ e.printStackTrace()
+ }
+ return null
+}
\ No newline at end of file
diff --git a/app/src/selfRelease/java/org/radarcns/detail/CompareVersions.kt b/app/src/selfRelease/java/org/radarcns/detail/CompareVersions.kt
new file mode 100644
index 00000000..c4f4b1de
--- /dev/null
+++ b/app/src/selfRelease/java/org/radarcns/detail/CompareVersions.kt
@@ -0,0 +1,50 @@
+package org.radarcns.detail
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.util.Log
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonObject
+import org.json.JSONObject
+import org.radarcns.detail.UpdateScheduledService.Companion.UPDATE_VERSION_NAME_KEY
+import org.radarcns.detail.UpdateScheduledService.Companion.UPDATE_VERSION_URL_KEY
+
+fun getUpdatePackage(context: Context, response: String): JSONObject? {
+ val updatePackage = getUpdatePackageVersionAndUrl(response)
+ val currentPackageVersion = getInstalledPackageVersion(context)
+
+ val pattern = Regex("\\d+(\\.\\d+)+")
+
+ val rawCurrentPackageVersion = currentPackageVersion?.let { pattern.find(it)?.value }
+ val rawUpdatePackageVersion = updatePackage?.getString(UPDATE_VERSION_NAME_KEY)?.let { pattern.find(it)?.value }
+
+ if (rawCurrentPackageVersion != rawUpdatePackageVersion) {
+ return updatePackage
+ }
+ return null
+}
+
+fun getInstalledPackageVersion(context: Context): String? {
+ try {
+ return context.packageManager.getPackageInfo(context.packageName, 0).versionName
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e("CompareVersions", "Cannot resolve version for " + context.packageName, e)
+ e.printStackTrace()
+ }
+ return null
+}
+
+fun getUpdatePackageVersionAndUrl(response: String): JSONObject? {
+ val responseObject = Json.parseToJsonElement(response)
+ if (responseObject.jsonArray.size > 0) {
+ val latestRelease = responseObject.jsonArray[0]
+ val tagName = latestRelease.jsonObject["tag_name"]?.toString()?.replace("\"", "")
+ val browserDownloadUrl = latestRelease.jsonObject["assets"]?.jsonArray?.get(0)?.jsonObject?.getValue("browser_download_url").toString().replace("\"", "")
+ val updateApk = JSONObject()
+ updateApk.put(UPDATE_VERSION_URL_KEY, browserDownloadUrl)
+ updateApk.put(UPDATE_VERSION_NAME_KEY, tagName)
+ return updateApk
+ }
+ return null
+}
diff --git a/app/src/selfRelease/java/org/radarcns/detail/DownloadFileFromUrl.kt b/app/src/selfRelease/java/org/radarcns/detail/DownloadFileFromUrl.kt
new file mode 100644
index 00000000..2eda7837
--- /dev/null
+++ b/app/src/selfRelease/java/org/radarcns/detail/DownloadFileFromUrl.kt
@@ -0,0 +1,100 @@
+package org.radarcns.detail
+
+import android.content.Context
+import android.os.AsyncTask
+import android.widget.ProgressBar
+import java.io.*
+import java.net.HttpURLConnection
+import java.net.URL
+
+class DownloadFileFromUrl(context: Context, delegate: TaskDelegate) :
+ AsyncTask() {
+
+ private val mDelegate: TaskDelegate = delegate
+
+ private val mContext: Context = context
+
+ var bar: ProgressBar? = null
+
+ override fun doInBackground(vararg params: String?): String? {
+ var input: InputStream? = null
+ var output: OutputStream? = null
+ var connection: HttpURLConnection? = null
+ try {
+ val url = URL(params[0])
+ connection = url.openConnection() as HttpURLConnection
+ connection.connect()
+
+ // expect HTTP 200 OK, so we don't mistakenly save error report
+ // instead of the file
+ if (connection.responseCode !== HttpURLConnection.HTTP_OK) {
+ return "Server returned HTTP " + connection.responseCode
+ .toString() + " " + connection.responseMessage
+ }
+
+ // this will be useful to display download percentage
+ // might be -1: server did not report the length
+ val fileLength: Int = connection.contentLength
+
+ // download the file
+ input = connection.inputStream
+
+ val outputFile = File(mContext.filesDir, DOWNLOADED_FILE)
+ if (outputFile.exists()) {
+ outputFile.delete()
+ }
+
+ outputFile.setReadable(true, false)
+ output = FileOutputStream(outputFile)
+
+ val data = ByteArray(1024) //4096
+ var total: Long = 0
+ var count: Int
+ while (input.read(data).also { count = it } != -1) {
+ // allow canceling with back button
+ if (isCancelled) {
+ input.close()
+ return null
+ }
+ total += count.toLong()
+ // publishing the progress....
+ if (fileLength > 0) // only if total length is known
+ publishProgress((total * 100 / fileLength).toInt())
+ output.write(data, 0, count)
+ }
+ } catch (e: Exception) {
+ return e.toString()
+ } finally {
+ try {
+ output?.close()
+ input?.close()
+ } catch (ignored: IOException) {
+ }
+ connection?.disconnect()
+ }
+ return null
+ }
+
+ override fun onProgressUpdate(vararg values: Int?) {
+ super.onProgressUpdate(*values)
+ bar?.progress = values[0]!!
+ }
+
+ override fun onPostExecute(s: String?) {
+ super.onPostExecute(s)
+ mDelegate.taskCompletionResult("Post Exec")
+ }
+
+ fun setProgressBar(bar: ProgressBar?) {
+ this.bar = bar
+ }
+
+ companion object {
+ const val DOWNLOADED_FILE = "app-release.apk"
+ }
+}
+
+interface TaskDelegate {
+ fun taskCompletionResult(result: String?)
+}
+
diff --git a/app/src/selfRelease/java/org/radarcns/detail/MainActivityImpl.kt b/app/src/selfRelease/java/org/radarcns/detail/MainActivityImpl.kt
new file mode 100644
index 00000000..b51d75e1
--- /dev/null
+++ b/app/src/selfRelease/java/org/radarcns/detail/MainActivityImpl.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 The Hyve
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.radarcns.detail
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import org.radarbase.android.MainActivity
+import org.radarbase.android.MainActivityView
+import org.radarbase.android.RadarApplication.Companion.radarApp
+
+class MainActivityImpl : MainActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ radarApp.notificationHandler.cancel(MainActivityBootStarter.BOOT_START_NOTIFICATION_ID)
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun createView(): MainActivityView {
+ return MainActivityViewImpl(this)
+ }
+
+ fun logout(@Suppress("UNUSED_PARAMETER")view: View) {
+ logout(true)
+ }
+
+ fun showInfo(@Suppress("UNUSED_PARAMETER")view: View) {
+ startActivity(Intent(this, InfoActivity::class.java))
+ }
+
+ fun showSettings(@Suppress("UNUSED_PARAMETER")view: View) {
+ startActivity(Intent(this, SettingsActivity::class.java))
+ }
+
+ fun showUpdates(@Suppress("UNUSED_PARAMETER")view: View) {
+ startActivity(Intent(this, UpdatesActivity::class.java))
+ }
+}
diff --git a/app/src/selfRelease/java/org/radarcns/detail/OneTimeScheduleWorker.kt b/app/src/selfRelease/java/org/radarcns/detail/OneTimeScheduleWorker.kt
new file mode 100644
index 00000000..0554a44f
--- /dev/null
+++ b/app/src/selfRelease/java/org/radarcns/detail/OneTimeScheduleWorker.kt
@@ -0,0 +1,84 @@
+package org.radarcns.detail
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import org.radarcns.detail.UpdateScheduledService.Companion.UPDATE_VERSION_NAME_KEY
+import org.radarcns.detail.UpdateScheduledService.Companion.UPDATE_VERSION_URL_KEY
+import kotlin.random.Random
+
+class OneTimeScheduleWorker(
+ private val context: Context,
+ workerParams: WorkerParameters
+) : Worker(context, workerParams) {
+
+ override fun doWork(): Result {
+ val url = inputData.getString(UPDATE_VERSION_URL_KEY)
+ val versionName = inputData.getString(UPDATE_VERSION_NAME_KEY)
+
+ val intent = Intent(context, UpdatesActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ putExtra(FROM_NOTIFICATION_KEY, true)
+ putExtra(UPDATE_VERSION_NAME_KEY, versionName)
+ putExtra(UPDATE_VERSION_URL_KEY, url)
+ }
+
+ val uniqueInt = (System.currentTimeMillis() and 0xfffffff).toInt()
+ val pendingIntent = PendingIntent.getActivity(
+ context,
+ uniqueInt,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT
+ )
+
+ val title = context.getString(R.string.app_name)
+ val content = context.getString(R.string.update_notification_content)
+
+ val builder = NotificationCompat.Builder(context, UPDATE_NOTIFICATION_ID)
+ .setSmallIcon(R.drawable.ic_baseline_update_24)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setContentIntent(pendingIntent)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+
+ createNotificationChannel()
+
+ with(NotificationManagerCompat.from(context)) {
+ notify(Random.nextInt(), builder.build())
+ }
+
+ return Result.success()
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val descriptionText = context.getString(R.string.update_notification_content)
+ val importance = NotificationManager.IMPORTANCE_HIGH
+ val channel = NotificationChannel(
+ UPDATE_NOTIFICATION_ID,
+ UPDATE_NOTIFICATION_CHANNEL_NAME,
+ importance
+ ).apply {
+ description = descriptionText
+ }
+
+ val notificationManager: NotificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
+ companion object {
+ const val FROM_NOTIFICATION_KEY = "from_notification"
+ const val UPDATE_NOTIFICATION_ID = "com.radarcns.detail.update"
+ const val UPDATE_NOTIFICATION_CHANNEL_NAME = "update_notification"
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/selfRelease/java/org/radarcns/detail/RadarServiceImpl.kt b/app/src/selfRelease/java/org/radarcns/detail/RadarServiceImpl.kt
index 3d3e66df..f9a5685e 100644
--- a/app/src/selfRelease/java/org/radarcns/detail/RadarServiceImpl.kt
+++ b/app/src/selfRelease/java/org/radarcns/detail/RadarServiceImpl.kt
@@ -18,8 +18,8 @@ package org.radarcns.detail
import android.Manifest.permission.RECEIVE_BOOT_COMPLETED
import android.Manifest.permission.SYSTEM_ALERT_WINDOW
+import android.content.Intent
import android.os.Build
-import org.radarbase.android.RadarConfiguration
import org.radarbase.android.RadarConfiguration.Companion.START_AT_BOOT
import org.radarbase.android.RadarService
import org.radarbase.android.config.SingleRadarConfiguration
@@ -68,5 +68,6 @@ class RadarServiceImpl : RadarService() {
override fun doConfigure(config: SingleRadarConfiguration) {
super.doConfigure(config)
configureRunAtBoot(config, MainActivityBootStarter::class.java)
+ startService(Intent(this, UpdateScheduledService::class.java))
}
}
diff --git a/app/src/selfRelease/java/org/radarcns/detail/UpdateScheduledService.kt b/app/src/selfRelease/java/org/radarcns/detail/UpdateScheduledService.kt
new file mode 100644
index 00000000..77ac3672
--- /dev/null
+++ b/app/src/selfRelease/java/org/radarcns/detail/UpdateScheduledService.kt
@@ -0,0 +1,88 @@
+package org.radarcns.detail
+
+import android.content.Intent
+import android.util.Log
+import androidx.lifecycle.LifecycleService
+import java.util.*
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import com.android.volley.Request
+import com.android.volley.toolbox.StringRequest
+import com.android.volley.toolbox.Volley
+import java.util.concurrent.TimeUnit
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import org.json.JSONObject
+import org.radarbase.android.RadarApplication.Companion.radarConfig
+import org.radarcns.detail.UpdatesActivity.Companion.DAY
+import org.radarcns.detail.UpdatesActivity.Companion.UPDATE_CHECK_PERIOD_KEY
+import org.radarcns.detail.UpdatesActivity.Companion.UPDATE_RELEASES_URL_KEY
+
+class UpdateScheduledService: LifecycleService() {
+ private var timerStarted: Boolean = false
+ private var timer: Timer = Timer()
+
+ private var updateNotificationConfig = true
+ private var updateCheckPeriod: Long = DAY
+ private var releasesUrl: String? = null
+
+ override fun onRebind(intent: Intent?) {
+ super.onRebind(intent)
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+
+ radarConfig.config.observe(this, { config ->
+ updateCheckPeriod = config.getLong(UPDATE_CHECK_PERIOD_KEY, DAY)
+ releasesUrl = config.getString(UPDATE_RELEASES_URL_KEY)
+
+ if (timerStarted) {
+ timer.cancel()
+ timerStarted = false
+ }
+ timer = Timer()
+ timer.scheduleAtFixedRate(CheckUpdateTask(), updateCheckPeriod, updateCheckPeriod)
+ timerStarted = true
+ })
+ }
+
+ fun scheduleOneTimeNotification(initialDelay: Long, newUpdate: JSONObject) {
+ val data = Data.Builder()
+ data.putString(UPDATE_VERSION_URL_KEY, newUpdate.get(UPDATE_VERSION_URL_KEY) as String?)
+ data.putString(UPDATE_VERSION_NAME_KEY, newUpdate.get(UPDATE_VERSION_NAME_KEY) as String?)
+ val work =
+ OneTimeWorkRequestBuilder()
+ .setInputData(data.build())
+ .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS)
+ .build()
+
+ WorkManager.getInstance(this@UpdateScheduledService).enqueue(work)
+ }
+
+ inner class CheckUpdateTask : TimerTask() {
+ override fun run() {
+ val queue = Volley.newRequestQueue(this@UpdateScheduledService)
+ val url = releasesUrl
+ val stringRequest = StringRequest(
+ Request.Method.GET, url,
+ { response ->
+ val updatePackage = getUpdatePackage(this@UpdateScheduledService, response)
+ if (updatePackage != null && updateNotificationConfig) {
+ scheduleOneTimeNotification(0, updatePackage)
+ }
+ },
+ {
+ Log.v("ScheduleService", "Error")
+ })
+ queue.add(stringRequest)
+ }
+ }
+
+ companion object {
+ const val UPDATE_VERSION_URL_KEY = "updateVersionUrl"
+ const val UPDATE_VERSION_NAME_KEY = "updateVersionName"
+ const val LAST_UPDATE_CHECK_TIMESTAMP = "last_update_check_timestamp"
+ }
+}
\ No newline at end of file
diff --git a/app/src/selfRelease/java/org/radarcns/detail/UpdatesActivity.kt b/app/src/selfRelease/java/org/radarcns/detail/UpdatesActivity.kt
new file mode 100644
index 00000000..5bd32149
--- /dev/null
+++ b/app/src/selfRelease/java/org/radarcns/detail/UpdatesActivity.kt
@@ -0,0 +1,210 @@
+package org.radarcns.detail
+
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.Toolbar
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.FileProvider
+import com.android.volley.Request
+import com.android.volley.toolbox.StringRequest
+import com.android.volley.toolbox.Volley
+import org.radarbase.android.RadarApplication.Companion.radarConfig
+import org.radarbase.android.RadarConfiguration
+import org.radarcns.detail.DownloadFileFromUrl.Companion.DOWNLOADED_FILE
+import org.radarcns.detail.OneTimeScheduleWorker.Companion.FROM_NOTIFICATION_KEY
+import org.radarcns.detail.UpdateScheduledService.Companion.LAST_UPDATE_CHECK_TIMESTAMP
+import org.radarcns.detail.UpdateScheduledService.Companion.UPDATE_VERSION_NAME_KEY
+import org.radarcns.detail.UpdateScheduledService.Companion.UPDATE_VERSION_URL_KEY
+import java.io.File
+
+class UpdatesActivity : AppCompatActivity(), TaskDelegate {
+ private lateinit var config: RadarConfiguration
+ private var releasesUrl: String? = null
+
+ private lateinit var currentVersion: TextView
+ private lateinit var updateStatus: TextView
+
+ private lateinit var updateLinearLayout: LinearLayout
+ private lateinit var startDownloadingButton: Button
+ private lateinit var cancelUpdateButton: Button
+ private lateinit var stopNotificationButton: TextView
+
+ private lateinit var progressBarContainerLayout: ConstraintLayout
+ private lateinit var progressBar: ProgressBar
+ private lateinit var progressBarPercent: TextView
+
+ private lateinit var newVersionApkUrl: String
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(R.layout.activity_updates)
+ setSupportActionBar(findViewById(R.id.toolbar).apply {
+ setTitle(R.string.updates)
+ })
+ supportActionBar?.apply {
+ setDisplayShowHomeEnabled(true)
+ setDisplayHomeAsUpEnabled(true)
+ }
+ config = radarConfig
+
+ config.config.observe(this, { config ->
+ val newReleaseUrl = config.getString(UPDATE_RELEASES_URL_KEY)
+ if( releasesUrl != newReleaseUrl ) {
+ releasesUrl = newReleaseUrl
+ checkForUpdates()
+ }
+ })
+
+ currentVersion = findViewById(R.id.current_version)
+ updateStatus = findViewById(R.id.update_status)
+
+ updateLinearLayout = findViewById(R.id.update_linearLayout)
+ startDownloadingButton = findViewById(R.id.start_downloading_button)
+ cancelUpdateButton = findViewById(R.id.cancel_update_button)
+ stopNotificationButton = findViewById(R.id.stop_notification_button)
+
+ progressBarContainerLayout = findViewById(R.id.progressbar_container)
+ progressBar = findViewById(R.id.progressBar)
+ progressBarPercent = findViewById(R.id.progressbar_percent)
+
+ // TODO not all notifications should be canceled
+ val notificationMng =
+ getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationMng.cancelAll()
+
+ setCurrentVersion()
+
+ if (intent.extras?.getBoolean(FROM_NOTIFICATION_KEY, false) == true){
+ updateLinearLayout.visibility = View.VISIBLE
+ updateStatus.text = getString(
+ R.string.new_version_available,
+ getString(R.string.app_name),
+ intent.extras?.getString(
+ "versionName",
+ ""
+ )
+ )
+ stopNotificationButton.visibility = View.VISIBLE
+ }
+
+ startDownloadingButton.setOnClickListener {
+ startDownloading()
+ }
+
+ cancelUpdateButton.setOnClickListener {
+ finish()
+ }
+
+ stopNotificationButton.setOnClickListener {
+ config.put(UPDATE_CHECK_PERIOD_KEY, WEEK)
+ config.persistChanges()
+ finish()
+ }
+ }
+
+ private fun setCurrentVersion() {
+ currentVersion.text = getString(R.string.currentVersion, getInstalledPackageVersion(this))
+ }
+
+ private fun checkForUpdates() {
+ val queue = Volley.newRequestQueue(this)
+ val url = releasesUrl
+ val stringRequest = StringRequest(
+ Request.Method.GET, url,
+ { response ->
+ config.put(LAST_UPDATE_CHECK_TIMESTAMP, System.currentTimeMillis())
+ config.persistChanges()
+
+ val updatePackage = getUpdatePackage(this, response)
+ if (updatePackage != null) {
+ val updateStatus: TextView = findViewById(R.id.update_status)
+ newVersionApkUrl = updatePackage.get(UPDATE_VERSION_URL_KEY) as String
+ updateStatus.text = getString(
+ R.string.new_version_available,
+ getString(R.string.app_name),
+ updatePackage.get(UPDATE_VERSION_NAME_KEY)
+ )
+ updateLinearLayout.visibility = View.VISIBLE
+ } else {
+ val updateStatus: TextView = findViewById(R.id.update_status)
+ updateStatus.text = getString(
+ R.string.new_version_not_available, getString(
+ R.string.app_name
+ )
+ )
+ updateLinearLayout.visibility = View.GONE
+ }
+ },
+ {
+ Log.v("ScheduleService", "Error")
+ })
+ queue.add(stringRequest)
+ }
+
+ private fun startDownloading() {
+ updateLinearLayout.visibility = View.GONE
+ progressBarContainerLayout.visibility = View.VISIBLE
+ progressBar.progress = 0
+ val downloader = DownloadFileFromUrl(this, this)
+ downloader.setProgressBar(progressBar)
+ downloader.execute(newVersionApkUrl)
+ }
+
+ private fun isInstallApkValid(): Boolean {
+ val fileName = DOWNLOADED_FILE
+ val fileLocation = File(filesDir, fileName)
+
+ return isPackageNameSame(this, fileLocation.absolutePath)
+ }
+
+ private fun install() {
+ val fileName = DOWNLOADED_FILE
+ val fileLocation = File(filesDir, fileName)
+
+ val uri = FileProvider.getUriForFile(
+ this,
+ applicationContext.packageName.toString() + PROVIDER_PATH,
+ fileLocation
+ )
+ val install = Intent(Intent.ACTION_VIEW)
+ install.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+ install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ install.setDataAndType(
+ uri,
+ MIME_TYPE
+ )
+ startActivity(install)
+ }
+
+ override fun taskCompletionResult(result: String?) {
+ if(isInstallApkValid()) {
+ install()
+ progressBarContainerLayout.visibility = View.GONE
+ updateLinearLayout.visibility = View.VISIBLE
+ }else{
+ updateStatus.text = getString(R.string.error_in_package_name)
+ progressBarContainerLayout.visibility = View.GONE
+ }
+ }
+
+ companion object {
+ private const val MIME_TYPE = "application/vnd.android.package-archive"
+ private const val PROVIDER_PATH = ".provider"
+ private const val HOUR = 60 * 60 * 1000L
+ const val DAY = 24 * HOUR
+ const val WEEK = 7 * DAY
+ const val UPDATE_RELEASES_URL_KEY = "update_releases_url"
+ const val UPDATE_CHECK_PERIOD_KEY = "update_check_period"
+ }
+}
+
diff --git a/app/src/selfRelease/res/layout/activity_updates.xml b/app/src/selfRelease/res/layout/activity_updates.xml
new file mode 100644
index 00000000..429d630b
--- /dev/null
+++ b/app/src/selfRelease/res/layout/activity_updates.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/selfRelease/res/layout/compact_overview.xml b/app/src/selfRelease/res/layout/compact_overview.xml
new file mode 100644
index 00000000..f21ea3fa
--- /dev/null
+++ b/app/src/selfRelease/res/layout/compact_overview.xml
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+