You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MagiskModuleManager/app/src/main/kotlin/com/fox2code/mmm/settings/DebugFragment.kt

211 lines
10 KiB
Kotlin

package com.fox2code.mmm.settings
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.core.content.FileProvider
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.fox2code.mmm.BuildConfig
import com.fox2code.mmm.Constants
import com.fox2code.mmm.MainApplication
import com.fox2code.mmm.R
import com.fox2code.mmm.installer.InstallerInitializer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.apache.commons.io.FileUtils
import timber.log.Timber
import java.io.BufferedReader
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStreamReader
import java.util.Date
class DebugFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val name = "mmmx"
val context: Context? = MainApplication.INSTANCE
val masterKey: MasterKey
val preferenceManager = preferenceManager
val dataStore: SharedPreferenceDataStore
try {
masterKey =
MasterKey.Builder(context!!).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
dataStore = SharedPreferenceDataStore(
EncryptedSharedPreferences.create(
context,
name,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
)
preferenceManager!!.preferenceDataStore = dataStore
preferenceManager.sharedPreferencesName = "mmm"
} catch (e: Exception) {
Timber.e(e, "Failed to create encrypted shared preferences")
throw RuntimeException(getString(R.string.error_encrypted_shared_preferences))
}
setPreferencesFromResource(R.xml.debugging_preferences, rootKey)
if (!MainApplication.isDeveloper) {
findPreference<Preference>("pref_disable_low_quality_module_filter")!!.isVisible = false
// Find pref_clear_data and set it invisible
findPreference<Preference>("pref_clear_data")!!.isVisible = false
}
// hande clear cache
findPreference<Preference>("pref_clear_cache")!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { _: Preference? ->
// Clear cache
MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.clear_cache_dialogue_title)
.setMessage(
R.string.clear_cache_dialogue_message
).setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int ->
// Clear app cache
try {
// use apache commons IO to delete the cache
FileUtils.deleteDirectory(requireContext().cacheDir)
// create a new cache dir
FileUtils.forceMkdir(requireContext().cacheDir)
// create cache dirs for cronet and webview
FileUtils.forceMkdir(File(requireContext().cacheDir, "cronet"))
FileUtils.forceMkdir(File(MainApplication.INSTANCE!!.dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/wasm"))
FileUtils.forceMkdir(File(MainApplication.INSTANCE!!.dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/js"))
Toast.makeText(
requireContext(), R.string.cache_cleared, Toast.LENGTH_SHORT
).show()
} catch (e: Exception) {
Timber.e(e)
Toast.makeText(
requireContext(), R.string.cache_clear_failed, Toast.LENGTH_SHORT
).show()
}
}.setNegativeButton(R.string.no) { _: DialogInterface?, _: Int -> }.show()
true
}
if (!BuildConfig.DEBUG || InstallerInitializer.peekMagiskPath() == null) {
// Hide the pref_crash option if not in debug mode - stop users from purposely crashing the app
Timber.i(InstallerInitializer.peekMagiskPath())
findPreference<Preference?>("pref_test_crash")!!.isVisible = false
} else {
if (findPreference<Preference?>("pref_test_crash") != null && findPreference<Preference?>(
"pref_clear_data"
) != null
) {
findPreference<Preference>("pref_test_crash")!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { _: Preference? ->
throw RuntimeException("This is a test crash with a stupidly long description to show off the crash handler. Are we having fun yet?")
}
findPreference<Preference>("pref_clear_data")!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { _: Preference? ->
// Clear app data
MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.clear_data_dialogue_title)
.setMessage(
R.string.clear_data_dialogue_message
).setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int ->
// Clear app data
MainApplication.INSTANCE!!.resetApp()
}.setNegativeButton(R.string.no) { _: DialogInterface?, _: Int -> }
.show()
true
}
} else {
Timber.e(
"Something is null: %s, %s",
findPreference("pref_clear_data"),
findPreference("pref_test_crash")
)
}
}
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND || !MainApplication.isDeveloper || InstallerInitializer.isKsu) {
findPreference<Preference>("pref_use_magisk_install_command")!!.isVisible = false
}
// handle pref_save_logs which saves logs to our external storage and shares them
val saveLogs = findPreference<Preference>("pref_save_logs")
saveLogs!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener setOnPreferenceClickListener@{ _: Preference? ->
// Save logs to external storage
val logsFile = File(requireContext().getExternalFilesDir(null), "logs.txt")
var fileOutputStream: FileOutputStream? = null
try {
logsFile.createNewFile()
fileOutputStream = FileOutputStream(logsFile)
// first, some device and app info: namely device oem and model, android version and build, app version and build
fileOutputStream.write(
String.format(
"Device: %s %s\nAndroid Version: %s\nROM: %s\nApp Version: %s (%s)\n\n",
Build.MANUFACTURER,
Build.MODEL,
Build.VERSION.RELEASE,
Build.FINGERPRINT,
BuildConfig.VERSION_NAME,
BuildConfig.VERSION_CODE
).toByteArray()
)
// next, the logs
// get our logs from logcat
val process = Runtime.getRuntime().exec("logcat -d")
val bufferedReader = BufferedReader(
InputStreamReader(process.inputStream)
)
var line: String?
val iterator: Iterator<String> = bufferedReader.lines().iterator()
while (iterator.hasNext()) {
line = iterator.next()
fileOutputStream.write(line.toByteArray())
fileOutputStream.write("\n".toByteArray())
}
fileOutputStream.flush()
Toast.makeText(
requireContext(), R.string.logs_saved, Toast.LENGTH_SHORT
).show()
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(
requireContext(), R.string.error_saving_logs, Toast.LENGTH_SHORT
).show()
return@setOnPreferenceClickListener true
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close()
} catch (ignored: IOException) {
}
}
}
// save logs to our external storage - name is current date and time
try {
val extStorage = File(requireContext().getExternalFilesDir(null), "logs" + File.separator + "log-" + Date().toString() + ".txt")
FileUtils.copyFile(logsFile, extStorage)
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(
requireContext(), R.string.error_saving_logs, Toast.LENGTH_SHORT
).show()
return@setOnPreferenceClickListener true
}
// Share logs
val shareIntent = Intent()
// create a new intent and grantUriPermission to the file provider
shareIntent.action = Intent.ACTION_SEND
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
shareIntent.putExtra(
Intent.EXTRA_STREAM, FileProvider.getUriForFile(
requireContext(), BuildConfig.APPLICATION_ID + ".file-provider", logsFile
)
)
shareIntent.type = "text/plain"
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_logs)))
true
}
}
}