Skip to content

Commit

Permalink
Release 1.2
Browse files Browse the repository at this point in the history
- fixed bug that self test scheduling doesn't work in background when device is in Doze mode
- automatically requires to turn off the battery optimization(will use another method later)
- notice feature
  • Loading branch information
lhwdev committed Aug 31, 2020
1 parent 435c01b commit fab921f
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ android {
applicationId "com.lhwdev.selfTestMacro"
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
versionCode 4
versionName "1.2"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

<application
android:allowBackup="true"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/lhwdev/selfTestMacro/AlarmReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ class AlarmReceiver : BroadcastReceiver() {
}

override fun onReceive(context: Context, intent: Intent) {
val result = goAsync()
GlobalScope.launch { // TODO: is this okay?
context.submitSuspend()
result.finish()
}

val pref = PreferenceState(context.prefMain())
Expand Down
95 changes: 95 additions & 0 deletions app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
package com.lhwdev.selfTestMacro

import android.annotation.SuppressLint
import android.app.TimePickerDialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import android.text.Html
import android.text.SpannableStringBuilder
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_main.toolbar
import kotlinx.android.synthetic.main.content_main.submit
import kotlinx.android.synthetic.main.content_main.time
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.net.URL


const val IGNORE_BATTERY_OPTIMIZATION_REQUEST = 1001


@Suppress("SpellCheckingInspection")
class MainActivity : AppCompatActivity() {

private var batteryOptimizationPromptShown = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -37,6 +60,8 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)

checkNotice()

title = "자가진단: ${pref.studentInfo}"

val intent = createIntent()
Expand All @@ -62,6 +87,8 @@ class MainActivity : AppCompatActivity() {

time.setOnClickListener {
TimePickerDialog(this, { _, newHour, newMin ->
checkBatteryOptimizationPermission()

pref.hour = newHour
pref.min = newMin
updateTime()
Expand All @@ -82,6 +109,74 @@ class MainActivity : AppCompatActivity() {
}
}

private fun checkNotice() = lifecycleScope.launch(Dispatchers.IO) {
val versions =
JSONObject(URL("https://raw.githubusercontent.com/wiki/lhwdev/covid-selftest-macro/_notice.md").readText())

/*
* Spec:
* {"$version | all": {"id": "$id", "priority": "once | every", "title": "$title", "message": "$message"}}
*
* Version spec: 1.0 1.3..2.1 ..1.5
*/

val thisVersion = Version(BuildConfig.VERSION_NAME)

for(key in versions.keys()) if(key == "all" || thisVersion in VersionSpec(key)) {
val notice = Notice(versions.getJSONObject(key))
val show = when(notice.priority) {
Notice.Priority.once -> notice.id !in preferenceState.shownNotices
Notice.Priority.every -> true
}

if(show) withContext(Dispatchers.Main) {
AlertDialog.Builder(this@MainActivity).apply {
setTitle(notice.title)
val message = HtmlCompat.fromHtml(notice.message, 0)
setMessage(message)
setPositiveButton("확인", null)
}.show().apply {
findViewById<TextView>(android.R.id.message)!!.movementMethod =
LinkMovementMethod.getInstance()
}

preferenceState.shownNotices += notice.id
}
}
}

/*
* > Task automation app
* App's core function is scheduling automated actions, such as for instant messaging, voice calling, or new photo management
* Acceptable
*
* I won't upload it on Play Store (so far), but it's always good to follow guidelines
* In case I upload, I won't use this api, instead will use foreground service.
* https://developer.android.com/training/monitoring-device-state/doze-standby#exemption-cases
*/
private fun checkBatteryOptimizationPermission() {
val pwrm = applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
val name = applicationContext.packageName

if(!batteryOptimizationPromptShown
&& android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M
&& !pwrm.isIgnoringBatteryOptimizations(name)) {
AlertDialog.Builder(this).apply {
setTitle("베터리 최적화 설정을 꺼야 알림 기능이 정상적으로 작동됩니다.")
setPositiveButton("설정") { _, _ ->
@SuppressLint("BatteryLife")
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
intent.data = Uri.parse("package:$packageName")
startActivityForResult(intent, IGNORE_BATTERY_OPTIMIZATION_REQUEST)
}

setNegativeButton("취소", null)
}

batteryOptimizationPromptShown = true
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
Expand Down
118 changes: 100 additions & 18 deletions app/src/main/java/com/lhwdev/selfTestMacro/utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,48 @@ import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty


// our project is too small to use form like 'major.minor.bugFixes'
data class Version(val major: Int, val minor: Int) : Comparable<Version> {
override fun compareTo(other: Version) =
if(major == other.major) minor - other.minor
else major - other.major
}

// {"id": "$id", "priority": "once | every", "title": "$title", "message": "$message"}
class Notice(obj: JSONObject) {
enum class Priority { once, every }

val id: String = obj.getString("id")

val priority = when(obj.getString("priority")) {
"once" -> Priority.once
"every" -> Priority.every
else -> error("wow")
}

val title: String = obj.getString("title")
val message: String = obj.getString("message")
}


fun Version(string: String): Version {
val split = string.split('.').map { it.toInt() }
require(split.size == 2)
return Version(split[0], split[1])
}

data class VersionSpec(val from: Version, val to: Version) {
operator fun contains(version: Version) = version in from..to
}

fun VersionSpec(string: String): VersionSpec {
val index = string.indexOf("..")
if(index == -1) return Version(string).let { VersionSpec(it, it) }
val from = string.take(index)
val to = string.drop(index + 2)
return VersionSpec(Version(from), Version(to))
}

class PreferenceState(val pref: SharedPreferences) {
var firstState by pref.preferenceInt("first", 0)
var hour by pref.preferenceInt("hour", -1)
Expand All @@ -41,11 +83,20 @@ class PreferenceState(val pref: SharedPreferences) {
var cert by pref.preferenceString("cert")

var studentInfo
get() = StudentInfo(schoolName = pref.getString("schoolName", "?")!!, studentName = pref.getString("studentName", "?")!!)
get() = StudentInfo(
schoolName = pref.getString("schoolName", "?")!!,
studentName = pref.getString("studentName", "?")!!
)
set(value) = pref.edit {
putString("schoolName", value.schoolName)
putString("studentName", value.studentName)
}

var shownNotices: Set<String>
get() = pref.getStringSet("shownNotices", setOf())!!
set(value) = pref.edit {
putStringSet("shownNotices", value)
}
}

lateinit var preferenceState: PreferenceState
Expand Down Expand Up @@ -80,7 +131,8 @@ fun Context.prefMain() = getSharedPreferences("main", AppCompatActivity.MODE_PRI

fun Context.createIntent() = PendingIntent.getBroadcast(
this, AlarmReceiver.REQUEST_CODE, Intent(this, AlarmReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT)
PendingIntent.FLAG_UPDATE_CURRENT
)

fun Context.updateTime(intent: PendingIntent) {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
Expand All @@ -90,18 +142,27 @@ fun Context.updateTime(intent: PendingIntent) {
}

@SuppressLint("NewApi")
fun Context.scheduleNextAlarm(intent: PendingIntent, hour: Int, min: Int, nextDay: Boolean = false) {
(getSystemService(Context.ALARM_SERVICE) as AlarmManager).setExact(AlarmManager.RTC_WAKEUP, Calendar.getInstance().run {
val new = clone() as Calendar
new[Calendar.HOUR_OF_DAY] = hour
new[Calendar.MINUTE] = min
new[Calendar.SECOND] = 0
new[Calendar.MILLISECOND] = 0
if(nextDay || new <= this) new.add(Calendar.DAY_OF_YEAR, 1)
Log.e("HOI", "$this")
Log.e("HOI", "$new")
new.timeInMillis
}, intent)
fun Context.scheduleNextAlarm(
intent: PendingIntent,
hour: Int,
min: Int,
nextDay: Boolean = false
) {
(getSystemService(Context.ALARM_SERVICE) as AlarmManager).setExact(
AlarmManager.RTC_WAKEUP,
Calendar.getInstance().run {
val new = clone() as Calendar
new[Calendar.HOUR_OF_DAY] = hour
new[Calendar.MINUTE] = min
new[Calendar.SECOND] = 0
new[Calendar.MILLISECOND] = 0
if(nextDay || new <= this) new.add(Calendar.DAY_OF_YEAR, 1)
Log.e("HOI", "$this")
Log.e("HOI", "$new")
new.timeInMillis
},
intent
)
}

@Suppress("NOTHING_TO_INLINE")
Expand All @@ -124,7 +185,14 @@ suspend fun checkStudentInfoSuspend(site: URL, cert: String) = withContext(Dispa
.openConnection() as HttpURLConnection
checkRequest.requestMethod = "POST"
DataOutputStream(checkRequest.outputStream).use {
it.writeBytes("rtnRsltCode=SUCCESS&qstnCrtfcNoEncpt=${URLEncoder.encode(cert, "utf-8")}&schulNm=&stdntName=&rspns01=1&rspns02=1&rspns07=0&rspns08=0&rspns09=0")
it.writeBytes(
"rtnRsltCode=SUCCESS&qstnCrtfcNoEncpt=${
URLEncoder.encode(
cert,
"utf-8"
)
}&schulNm=&stdntName=&rspns01=1&rspns02=1&rspns07=0&rspns08=0&rspns09=0"
)
it.flush()
}

Expand Down Expand Up @@ -159,10 +227,20 @@ suspend fun Context.submitSuspend() = withContext(Dispatchers.IO) {
.openConnection() as HttpURLConnection
request.requestMethod = "POST"
request.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
request.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8")
request.setRequestProperty(
"User-Agent",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8"
)
HttpURLConnection.setFollowRedirects(true)
DataOutputStream(request.outputStream).use {
it.writeBytes("rtnRsltCode=SUCCESS&qstnCrtfcNoEncpt=${URLEncoder.encode(cert, "utf-8")}&schulNm=&stdntName=&rspns01=1&rspns01=2&rspns02=1&rspns03=1&rspns05=1&rspns13=1&rspns14=1&rspns15=1&rspns04=1&rspns11=1&rspns07=0&rspns07=1&rspns08=0&rspns08=1&rspns09=0&rspns09=1")
it.writeBytes(
"rtnRsltCode=SUCCESS&qstnCrtfcNoEncpt=${
URLEncoder.encode(
cert,
"utf-8"
)
}&schulNm=&stdntName=&rspns01=1&rspns01=2&rspns02=1&rspns03=1&rspns05=1&rspns13=1&rspns14=1&rspns15=1&rspns04=1&rspns11=1&rspns07=0&rspns07=1&rspns08=0&rspns08=1&rspns09=0&rspns09=1"
)
it.flush()
}
Log.i("HOI", "${request.responseMessage} / ${request.inputStream.reader().readText()}")
Expand All @@ -171,5 +249,9 @@ suspend fun Context.submitSuspend() = withContext(Dispatchers.IO) {

// log
File(getExternalFilesDir(null)!!, "log.txt")
.appendText("self-tested at ${DateFormat.getDateTimeInstance().format(Date())} ${request.responseMessage}\n")
.appendText(
"self-tested at ${
DateFormat.getDateTimeInstance().format(Date())
} ${request.responseMessage}\n"
)
}

0 comments on commit fab921f

Please sign in to comment.