add logging

This commit is contained in:
Kieran W 2025-02-08 23:18:40 +00:00
parent 16ee306864
commit eea1b0d2f5
10 changed files with 187 additions and 65 deletions

View File

@ -31,7 +31,6 @@ tasks.register("genDocs") {
}
}
android {
compileSdk = 35
buildToolsVersion = "35.0.0"
@ -84,6 +83,7 @@ dependencies {
implementation("io.coil-kt:coil-svg:2.7.0")
implementation("androidx.gridlayout:gridlayout:1.0.0")
implementation("io.noties.markwon:core:4.6.2")
implementation("com.elvishew:xlog:1.11.1")
androidTestImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test:core:1.6.1")
androidTestImplementation("androidx.test.ext:junit:1.2.1")

View File

@ -28,6 +28,7 @@ import coil.decode.SvgDecoder
import coil.decode.VideoFrameDecoder
import coil.imageLoader
import coil.load
import com.elvishew.xlog.XLog
import com.fredhappyface.ewesticker.adapter.StickerPackAdapter
import com.fredhappyface.ewesticker.model.StickerPack
import com.fredhappyface.ewesticker.utilities.Cache
@ -94,6 +95,10 @@ class ImageKeyboard : InputMethodService(), StickerClickListener {
override fun onCreate() {
// Misc
super.onCreate()
XLog.i("=".repeat(80))
XLog.i("Loaded $packageName:${javaClass.name}")
val scale = baseContext.resources.displayMetrics.density
// Setup coil
val imageLoader =
@ -113,6 +118,10 @@ class ImageKeyboard : InputMethodService(), StickerClickListener {
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(baseContext)
this.backupSharedPreferences =
this.getSharedPreferences("backup_prefs", Context.MODE_PRIVATE)
XLog.i("Loading private shared preferences: ${this.sharedPreferences.all}")
XLog.i("Loading backup shared preferences: ${this.backupSharedPreferences.all}")
this.restoreOnClose = this.backupSharedPreferences.getBoolean("restoreOnClose", false)
this.vertical = this.backupSharedPreferences.getBoolean("vertical", false)
this.scroll = this.backupSharedPreferences.getBoolean("scroll", false)
@ -147,6 +156,8 @@ class ImageKeyboard : InputMethodService(), StickerClickListener {
}
this.allStickers += pack.stickerList
}
XLog.i("Loaded all packs: [${this.loadedPacks.keys.joinToString(", ")}]")
this.activePack = this.sharedPreferences.getString("activePack", "").toString()
// Caches
this.sharedPreferences.getString("recentCache", "")?.let {
@ -222,6 +233,7 @@ class ImageKeyboard : InputMethodService(), StickerClickListener {
/** When leaving some input field update the caches */
override fun onFinishInput() {
XLog.i("Updating sharedPreferences based on use, and closing...")
val editor = this.sharedPreferences.edit()
editor.putString("recentCache", this.recentCache.toSharedPref())
editor.putString("compatCache", this.compatCache.toSharedPref())
@ -240,6 +252,7 @@ class ImageKeyboard : InputMethodService(), StickerClickListener {
* @param packName String
*/
private fun switchPackLayout(packName: String) {
XLog.i("Switching pack to '$packName'")
this.activePack = packName
for (packCard in this.packsList) {
val packButton = packCard.findViewById<ImageButton>(R.id.stickerButton)
@ -280,6 +293,7 @@ class ImageKeyboard : InputMethodService(), StickerClickListener {
* Set the current tab to the search page/ view
*/
private fun searchView() {
XLog.i("Switching to search")
for (packCard in this.packsList) {
val packButton = packCard.findViewById<ImageButton>(R.id.stickerButton)
if (packButton.tag == "__search__") {
@ -454,9 +468,6 @@ class ImageKeyboard : InputMethodService(), StickerClickListener {
this.loadedPacks.keys.sorted()
}.toTypedArray()
for (sortedPackName in sortedPackNames) {
val packButton = addPackButton(sortedPackName)
packButton.load(this.loadedPacks[sortedPackName]?.thumbSticker)

View File

@ -17,12 +17,20 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.elvishew.xlog.LogConfiguration
import com.elvishew.xlog.LogLevel
import com.elvishew.xlog.XLog
import com.elvishew.xlog.printer.AndroidPrinter
import com.elvishew.xlog.printer.file.FilePrinter
import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy
import com.fredhappyface.ewesticker.utilities.StickerImporter
import com.fredhappyface.ewesticker.utilities.Toaster
import com.google.android.material.progressindicator.LinearProgressIndicator
import io.noties.markwon.Markwon
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Calendar
/** MainActivity class inherits from the AppCompatActivity class - provides the settings view */
@ -43,6 +51,17 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val logConfig = LogConfiguration.Builder().logLevel(LogLevel.ALL).tag("EweSticker").build()
val androidPrinter =
AndroidPrinter(true) // Printer that print the log using android.util.Log
val filePrinter = FilePrinter.Builder(
File(filesDir, "logs").path
).cleanStrategy(FileLastModifiedCleanStrategy(86_400_000)).build() // 1day
XLog.init(logConfig, androidPrinter, filePrinter)
XLog.i("=".repeat(80))
XLog.i("Loaded $packageName:$localClassName")
val markwon: Markwon = Markwon.create(this)
val featuresText = findViewById<TextView>(R.id.features_text)
markwon.setMarkdown(featuresText, getString(R.string.features_text))
@ -54,6 +73,9 @@ class MainActivity : AppCompatActivity() {
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
this.backupSharedPreferences =
this.getSharedPreferences("backup_prefs", Context.MODE_PRIVATE)
XLog.i("Loading private shared preferences: ${this.sharedPreferences.all}")
XLog.i("Loading backup shared preferences: ${this.backupSharedPreferences.all}")
this.contextView = findViewById(R.id.activityMainRoot)
this.toaster = Toaster(baseContext)
refreshStickerDirPath()
@ -71,12 +93,16 @@ class MainActivity : AppCompatActivity() {
toggle(findViewById(R.id.insensitive_sort), "insensitiveSort", false) {}
val versionText: TextView = findViewById(R.id.versionText)
var version = getString(R.string.version_text)
try {
val packageInfo = packageManager.getPackageInfo(packageName, 0)
versionText.text = packageInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
versionText.text = getString(R.string.version_text)
version = packageInfo.versionName ?: version
} catch (_: PackageManager.NameNotFoundException) {
}
versionText.text = version
XLog.i("Version: $version")
}
/**
@ -107,6 +133,22 @@ class MainActivity : AppCompatActivity() {
}
}
private val saveFileLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
result.data?.data?.also { uri ->
val file = File(filesDir, "logs/log")
if (file.exists()) {
contentResolver.openOutputStream(uri)?.use { outputStream ->
file.inputStream().use { inputStream ->
inputStream.copyTo(outputStream)
}
}
}
}
}
}
/**
* Called on button press to launch settings
*
@ -129,6 +171,20 @@ class MainActivity : AppCompatActivity() {
chooseDirResultLauncher.launch(intent)
}
/**
* Called on button press to save logs
*
* @param ignoredView: View
*/
fun saveLogs(ignoredView: View) {
val saveIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, "ewesticker.log")
}
saveFileLauncher.launch(saveIntent)
}
/**
* reloadStickers
*

View File

@ -1,4 +1,4 @@
package com.fredhappyface.ewesticker
package com.fredhappyface.ewesticker.utilities
import android.content.Context
import android.net.Uri
@ -6,8 +6,8 @@ import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.documentfile.provider.DocumentFile
import com.fredhappyface.ewesticker.utilities.Toaster
import com.fredhappyface.ewesticker.utilities.Utils
import com.elvishew.xlog.XLog
import com.google.android.material.progressindicator.LinearProgressIndicator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@ -28,6 +28,7 @@ private const val BUFFER_SIZE = 64 * 1024 // 64 KB
* @property context: application baseContext
* @property toaster: an instance of Toaster (used to store an error state for later reporting to the
* user)
* @property progressBar: LinearProgressIndicator that we update as we import stickers
*/
class StickerImporter(
private val context: Context,
@ -36,6 +37,7 @@ class StickerImporter(
) {
private val supportedMimes = Utils.getSupportedMimes()
private val packSizes: MutableMap<String, Int> = mutableMapOf()
private var detectedStickers = 0
private var totalStickers = 0
private val mainHandler = Handler(Looper.getMainLooper())
@ -53,14 +55,17 @@ class StickerImporter(
* @param stickerDirPath a URI to the stickers directory to import into EweSticker
*/
suspend fun importStickers(stickerDirPath: String): Int {
XLog.i("Removing old stickers...")
File(context.filesDir, "stickers").deleteRecursively()
withContext(Dispatchers.Main) {
progressBar.visibility = View.VISIBLE
progressBar.isIndeterminate = true
}
XLog.i("Walking $stickerDirPath...")
val leafNodes = fileWalk(DocumentFile.fromTreeUri(context, Uri.parse(stickerDirPath)))
if (leafNodes.size > MAX_FILES) {
XLog.w("Found more than $MAX_FILES stickers, notify user")
toaster.setState(1)
}
@ -69,6 +74,7 @@ class StickerImporter(
}
// Perform concurrent file copy operations
XLog.i("Perform concurrent file copy operations...")
withContext(Dispatchers.IO) {
leafNodes.take(MAX_FILES).mapIndexed { index, file ->
async {
@ -84,7 +90,9 @@ class StickerImporter(
progressBar.visibility = View.GONE
}
return leafNodes.size
XLog.i("Copied $totalStickers / $detectedStickers")
return totalStickers
}
/**
@ -98,10 +106,12 @@ class StickerImporter(
val parentDir = sticker.parentFile?.name ?: "__default__"
val packSize = packSizes[parentDir] ?: 0
if (packSize > MAX_PACK_SIZE) {
XLog.w("Found more than $MAX_PACK_SIZE stickers in '$parentDir', notify user")
toaster.setState(2)
return
}
if (sticker.type !in supportedMimes) {
XLog.w("'$parentDir/${sticker.name}' is not a supported mimetype (${sticker.type}), notify user")
toaster.setState(3)
return
}
@ -126,7 +136,9 @@ class StickerImporter(
}
totalStickers++
}
} catch (_: IOException) {
} catch (e: IOException) {
XLog.e("There was an IOException when copying '${parentDir}/${sticker.name}'!")
XLog.e(e)
}
}
@ -148,9 +160,10 @@ class StickerImporter(
currentFile?.listFiles()?.forEach { file ->
if (file.isFile) {
leafNodes.add(file)
totalStickers++
detectedStickers++
if (leafNodes.size >= MAX_FILES) {
if (leafNodes.size > MAX_FILES + 1) {
XLog.w("Found more than ${MAX_FILES + 1} stickers, so returning early")
return leafNodes
}
} else if (file.isDirectory) {

View File

@ -4,7 +4,6 @@ import android.content.ClipDescription
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.util.Log
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import androidx.core.content.FileProvider
@ -13,6 +12,7 @@ import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.view.inputmethod.InputContentInfoCompat
import coil.ImageLoader
import coil.request.ImageRequest
import com.elvishew.xlog.XLog
import com.fredhappyface.ewesticker.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -50,6 +50,15 @@ class StickerSender(
private val supportedMimes = this.currentInputEditorInfo?.contentMimeTypes ?: emptyArray()
private val packageName = this.currentInputEditorInfo?.packageName
init {
XLog.i("Connecting to $packageName which supports [${supportedMimes.joinToString(", ")}]")
}
/**
* Wrapper function to display a toast message to the user
*
* @param message String
*/
private fun showToast(message: String) {
CoroutineScope(Dispatchers.Main).launch {
toaster.toast(message)
@ -68,6 +77,8 @@ class StickerSender(
val compatSticker = File(internalDir, "__compatSticker__/$compatStickerName.png")
if (!compatSticker.exists()) {
XLog.i("Create a fallback png sticker '__compatSticker__/$compatStickerName.png'")
compatSticker.parentFile?.mkdirs()
try {
val request = ImageRequest.Builder(context)
@ -95,6 +106,11 @@ class StickerSender(
return compatSticker
}
/**
* Main method to send a sticker to an InputConnectionCompat, this attempts to send via the happy path (assuming the
* InputConnectionCompat supports the stickers mime type. Otherwise attempts falling back to png, and then finally if that fails,
* opening a share sheet
*/
fun sendSticker(file: File) {
val stickerType = Utils.getMimeType(file) ?: "__unknown__"
@ -105,63 +121,28 @@ class StickerSender(
|| "video/*" in supportedMimes && stickerType.startsWith("video/"))
&& stickerType != "image/svg+xml"
) {
if (!doCommitContent(stickerType, file)) {
CoroutineScope(Dispatchers.Main).launch {
doFallbackCommitContent(file)
doFallbackCommitContent(stickerType, file)
}
}
} else {
CoroutineScope(Dispatchers.Main).launch {
doFallbackCommitContent(file)
doFallbackCommitContent(stickerType, file)
}
}
}
private fun openShareSheet(file: File) {
val uri = FileProvider.getUriForFile(
context,
"com.fredhappyface.ewesticker.inputcontent",
file,
)
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = "image/*"
}
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooserIntent = Intent.createChooser(shareIntent, "Share Sticker")
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(chooserIntent)
}
private suspend fun doFallbackCommitContent(file: File) {
if ("image/png" in supportedMimes || "image/*" in supportedMimes) {
val compatSticker = createCompatSticker(file)
if (compatSticker != null) {
if (!doCommitContent("image/png", compatSticker)) {
openShareSheet(file)
}
return
}
}
openShareSheet(file)
}
/**
* Send a sticker file to a InputConnectionCompat
* Called by sendSticker. Send a sticker file to a InputConnectionCompat
*
* @param mimeType String
* @param file File
* @return success Boolean
*/
private fun doCommitContent(mimeType: String, file: File): Boolean {
// Log.d("QWERTY", "Sending ${file.name} ($mimeType) to ${this.packageName}")
XLog.i("Sending ${file.name} ($mimeType) to ${this.packageName}")
val inputContentInfoCompat = InputContentInfoCompat(
FileProvider.getUriForFile(
context,
@ -185,4 +166,53 @@ class StickerSender(
return false
}
/**
* Called by sendSticker. Otherwise attempts falling back to png, and then finally if that fails, opening a
* share sheet to send the sticker
*
* @param mimeType String
* @param file File
*/
private suspend fun doFallbackCommitContent(mimeType: String, file: File) {
if ("image/png" in supportedMimes || "image/*" in supportedMimes) {
val compatSticker = createCompatSticker(file)
if (compatSticker != null) {
if (!doCommitContent("image/png", compatSticker)) {
openShareSheet(mimeType, file)
}
return
}
}
openShareSheet(mimeType, file)
}
/**
* Called by doFallbackCommitContent. Opens a share sheet to send the sticker
*
* @param mimeType String
* @param file File
*/
private fun openShareSheet(mimeType: String, file: File) {
XLog.i("$packageName reports that is doesn't support png over its InputConnectionCompat, so open a share sheet")
val uri = FileProvider.getUriForFile(
context,
"com.fredhappyface.ewesticker.inputcontent",
file,
)
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = mimeType
}
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooserIntent = Intent.createChooser(shareIntent, "Share Sticker")
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(chooserIntent)
}
}

View File

@ -405,6 +405,25 @@
android:text="@string/version_text" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Logs -->
<com.google.android.material.card.MaterialCardView style="@style/card">
<LinearLayout
style="@style/cardchild"
android:orientation="vertical">
<TextView
style="@style/heading"
android:text="@string/logs_heading" />
<Button
style="@style/button"
android:onClick="saveLogs"
android:text="@string/logs_button"
app:shapeAppearance="?attr/shapeAppearanceSmallComponent" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -24,7 +24,6 @@
android:layout_height="@dimen/qwerty_row_height"
android:id="@+id/search_text"/>
</LinearLayout>
<LinearLayout
@ -51,8 +50,6 @@
android:layout_gravity="center_horizontal"
>
</LinearLayout>
<LinearLayout
@ -63,10 +60,6 @@
android:layout_gravity="center_horizontal"
>
</LinearLayout>
</LinearLayout>

View File

@ -8,7 +8,6 @@
<dimen name="text_size_tiny">10sp</dimen>
<dimen name="qwerty_row_height">40sp</dimen>
<dimen name="content_margin">10dp</dimen>
<dimen name="card_margin">16dp</dimen>
<dimen name="content_margin_top">8dp</dimen>

View File

@ -70,7 +70,9 @@ Copyright © Randy Zhou</string>
(See the license for more information https://github.com/FredHappyface/Android.EweSticker/blob/main/LICENSE.md )</string>
<string name="version_heading">Version Info</string>
<string name="version_text" translatable="false">[debug]</string>
<!-- Logs -->
<string name="logs_heading">Logs</string>
<string name="logs_button">Get log file</string>
<!-- Interactive Messages -->
<string name="pref_000">Preferences changed. Reload the keyboard for settings to apply</string>
<string name="imported_010">Starting import. This might take some time!</string>

View File

@ -29,7 +29,6 @@
<item name="android:background">@drawable/qwerty_key</item>
</style>
<style name="ToolbarTitleTextAppearance" parent="@style/TextAppearance.Widget.AppCompat.Toolbar.Title">
<item name="android:textSize">@dimen/text_size_title</item>
</style>