diff --git a/app/build.gradle b/app/build.gradle index 4a0b9251..fb62f22f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { applicationId "com.lhwdev.selfTestMacro" minSdkVersion 19 targetSdkVersion 30 - versionCode 1004 - versionName "2.4" + versionCode 1005 + versionName "2.5" multiDexEnabled true @@ -24,7 +24,7 @@ android { buildTypes { release { - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" } } diff --git a/app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt b/app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt index 5231edfa..e68e56c9 100644 --- a/app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt +++ b/app/src/main/java/com/lhwdev/selfTestMacro/MainActivity.kt @@ -2,6 +2,8 @@ package com.lhwdev.selfTestMacro import android.annotation.SuppressLint import android.app.TimePickerDialog +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.net.Uri @@ -16,6 +18,7 @@ import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.getSystemService import androidx.core.text.HtmlCompat import androidx.lifecycle.lifecycleScope import com.lhwdev.selfTestMacro.api.getDetailedUserInfo @@ -64,7 +67,7 @@ class MainActivity : AppCompatActivity() { val detailedUserInfo = try { getDetailedUserInfo(pref.school!!, pref.user!!) } catch(e: Throwable) { - Log.e("hOI", null, e) + onError(e, "사용자 정보 불러오기") showToastSuspendAsync("사용자 정보를 불러오지 못했습니다.") return@withContext } @@ -140,20 +143,20 @@ class MainActivity : AppCompatActivity() { val title: String, val message: String ) { - enum class Priority { once, every } + enum class Priority { once, everyWithDoNotShowAgain, every } } private fun checkNotice() = lifecycleScope.launch(Dispatchers.IO) { - var content: String? = null + val content: String? try { content = - URL("https://raw.githubusercontent.com/wiki/lhwdev/covid-selftest-macro/notice_v3.json").readText() + URL("https://raw.githubusercontent.com/wiki/lhwdev/covid-selftest-macro/notice_v4.json").readText() val notificationObject = Json { ignoreUnknownKeys = true /* loose */ }.decodeFromString(NotificationObject.serializer(), content) - if(notificationObject.notificationVersion != 3) { + if(notificationObject.notificationVersion != 4) { // incapable of displaying this return@launch } @@ -165,6 +168,7 @@ class MainActivity : AppCompatActivity() { for(entry in notificationObject.entries) { var show = when(entry.priority) { NotificationEntry.Priority.once -> entry.id !in preferenceState.shownNotices + NotificationEntry.Priority.everyWithDoNotShowAgain -> entry.id !in preferenceState.doNotShowAgainNotices NotificationEntry.Priority.every -> true } show = show && (entry.version?.let { currentVersion in it } ?: true) @@ -176,6 +180,11 @@ class MainActivity : AppCompatActivity() { setPositiveButton("확인") { _, _ -> preferenceState.shownNotices += entry.id } + if(entry.priority == NotificationEntry.Priority.everyWithDoNotShowAgain) + setNegativeButton("다시 보지 않기") { _, _ -> + preferenceState.doNotShowAgainNotices += entry.id + preferenceState.shownNotices += entry.id + } }.show().apply { findViewById(android.R.id.message)!!.movementMethod = LinkMovementMethod.getInstance() @@ -185,7 +194,8 @@ class MainActivity : AppCompatActivity() { } catch(e: Exception) { // ignore; - network error or etc // notification is not that important - Log.e("hOI", content, e) + + onError(e, "알림") } } @@ -237,6 +247,16 @@ class MainActivity : AppCompatActivity() { startActivity(Intent(this, FirstActivity::class.java)) true } + R.id.debug_info -> lifecycleScope.launch { + val info = getLogcat() + AlertDialog.Builder(this@MainActivity).apply { + setTitle("진단정보") + setMessage(info) + setPositiveButton("복사") { _, _ -> + getSystemService()!!.setPrimaryClip(ClipData.newPlainText("진단정보", info)) + } + }.show() + }.let { true } else -> super.onOptionsItemSelected(item) } } diff --git a/app/src/main/java/com/lhwdev/selfTestMacro/MainApplication.kt b/app/src/main/java/com/lhwdev/selfTestMacro/MainApplication.kt index 02aa3a8f..51662881 100644 --- a/app/src/main/java/com/lhwdev/selfTestMacro/MainApplication.kt +++ b/app/src/main/java/com/lhwdev/selfTestMacro/MainApplication.kt @@ -1,6 +1,10 @@ package com.lhwdev.selfTestMacro import android.app.Application +import kotlinx.coroutines.DEBUG_PROPERTY_NAME +import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_OFF +import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON +import kotlinx.coroutines.runBlocking @Suppress("unused") @@ -9,5 +13,17 @@ class MainApplication : Application() { super.onCreate() sDummyForInitialization sDebugFetch = BuildConfig.DEBUG + + // debug code + System.setProperty( + DEBUG_PROPERTY_NAME, + if(BuildConfig.DEBUG) DEBUG_PROPERTY_VALUE_ON else DEBUG_PROPERTY_VALUE_OFF + ) + + Thread.setDefaultUncaughtExceptionHandler { _, exception -> + runBlocking { + writeErrorLog(getErrorInfo(exception, "defaultUncaughtExceptionHandler")) + } + } } } diff --git a/app/src/main/java/com/lhwdev/selfTestMacro/debugUtils.kt b/app/src/main/java/com/lhwdev/selfTestMacro/debugUtils.kt new file mode 100644 index 00000000..02fd290a --- /dev/null +++ b/app/src/main/java/com/lhwdev/selfTestMacro/debugUtils.kt @@ -0,0 +1,88 @@ +package com.lhwdev.selfTestMacro + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import androidx.appcompat.app.AlertDialog +import androidx.core.content.getSystemService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File + + +suspend fun Context.onError(error: Throwable, description: String = "error") { + val info = getErrorInfo(error, description) + if(getExternalFilesDir(null)?.let { File(it, "debug.txt").exists() } == true) withContext( + Dispatchers.Main + ) { + showErrorInfo(info) + } + writeErrorLog(info) +} + +suspend fun Context.writeErrorLog(info: String) { + withContext(Dispatchers.IO) { + getExternalFilesDir(null)?.appendText(info) + } +} + +suspend fun getErrorInfo(error: Throwable, description: String) = """ + $description + Android sdk version: ${Build.VERSION.SDK_INT} + Model: ${Build.DEVICE} / ${Build.PRODUCT} + Stacktrace: + ${error.stackTraceToString()} + + Logcat: + ${getLogcat()} +""".trimIndent() + +private fun Context.showErrorInfo(info: String) { + AlertDialog.Builder(this).apply { + setTitle("오류 발생") + setMessage("* 복사된 오류정보는 기기의 정보 등 민감한 정보를 포함할 수 있습니다.\n$info") + + setPositiveButton("오류정보 복사") { _, _ -> + CoroutineScope(Dispatchers.Main).launch { + getSystemService()!! + .setPrimaryClip(ClipData.newPlainText("오류정보", info)) + showToastSuspendAsync("복사 완료") + } + } + setNegativeButton("취소", null) + }.show() +} + + +suspend fun getLogcat(): String = withContext(Dispatchers.IO) { + val command = arrayOf("logcat", "-d", "-v", "threadtime") + val process = Runtime.getRuntime().exec(command) + process.inputStream.reader().use { it.readText() } +} + +// //Code here +// val log: StringBuilder +// get() { +// val builder = StringBuilder() +// try { +// val command = arrayOf("logcat", "-d", "-v", "threadtime") +// val process = Runtime.getRuntime().exec(command) +// val bufferedReader = process.inputStream.bufferedReader() +// +// +// var line: String +// while(bufferedReader.readLine().also { line = it } != null) { +// if(line.contains(processId)) { +// builder.append(line) +// //Code here +// } +// } +// } catch(ex: IOException) { +// Log.e(TAG, "getLog failed", ex) +// } +// return builder +// } + diff --git a/app/src/main/java/com/lhwdev/selfTestMacro/selfTestUtils.kt b/app/src/main/java/com/lhwdev/selfTestMacro/selfTestUtils.kt index b25bf379..ca17ea5b 100644 --- a/app/src/main/java/com/lhwdev/selfTestMacro/selfTestUtils.kt +++ b/app/src/main/java/com/lhwdev/selfTestMacro/selfTestUtils.kt @@ -7,7 +7,6 @@ import android.annotation.SuppressLint import android.app.AlarmManager import android.app.PendingIntent import android.content.Context -import android.util.Log import com.lhwdev.selfTestMacro.api.SurveyData import com.lhwdev.selfTestMacro.api.registerSurvey import java.util.Calendar @@ -29,7 +28,7 @@ suspend fun Context.submitSuspend(notification: Boolean = true) { } } catch(e: Throwable) { showTestFailedNotification(e.stackTraceToString()) - Log.e("hOI", null, e) + onError(e, "제출 실패") } } diff --git a/app/src/main/java/com/lhwdev/selfTestMacro/utils.kt b/app/src/main/java/com/lhwdev/selfTestMacro/utils.kt index f755b7e9..6d98107c 100644 --- a/app/src/main/java/com/lhwdev/selfTestMacro/utils.kt +++ b/app/src/main/java/com/lhwdev/selfTestMacro/utils.kt @@ -3,22 +3,25 @@ package com.lhwdev.selfTestMacro import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences +import android.content.* +import android.os.Build import android.os.Handler import android.util.Base64 import android.view.View import android.widget.EditText import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.edit +import androidx.core.content.getSystemService import com.google.android.material.snackbar.Snackbar import com.lhwdev.selfTestMacro.api.LoginType import com.lhwdev.selfTestMacro.api.SchoolInfo import com.lhwdev.selfTestMacro.api.UserInfo import com.lhwdev.selfTestMacro.api.encodeBase64 +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer @@ -89,10 +92,14 @@ class PreferenceState(val pref: SharedPreferences) { set(value) = pref.edit { putStringSet("shownNotices", value) } + var doNotShowAgainNotices: Set + get() = pref.getStringSet("shownNotices", setOf())!! + set(value) = pref.edit { + putStringSet("shownNotices", value) + } } - // I knew that global things are bad in android(i waz lazy), but didn't know would be by far worst. // Only one line below caused TWO bugs; I WON'T do like this in the future // @@ -115,7 +122,6 @@ val Context.preferenceState: PreferenceState } - fun SharedPreferences.preferenceInt(key: String, defaultValue: Int) = object : ReadWriteProperty { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { @@ -137,7 +143,11 @@ fun SharedPreferences.preferenceString(key: String, defaultValue: String? = null } @OptIn(ExperimentalSerializationApi::class) -fun SharedPreferences.preferenceSerialized(key: String, serializer: KSerializer, formatter: StringFormat = Json) = +fun SharedPreferences.preferenceSerialized( + key: String, + serializer: KSerializer, + formatter: StringFormat = Json +) = object : ReadWriteProperty { var updated = false var cache: T? = null diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index f7847e7c..d1a801fa 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -1,10 +1,14 @@ + tools:context="com.lhwdev.selfTestMacro.MainActivity" + tools:ignore="HardcodedText"> + android:title="@string/action_settings" /> + +