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.fox2code.mmm.utils.sentry.SentryMain 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 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("pref_disable_low_quality_module_filter")!!.isVisible = false // Find pref_clear_data and set it invisible findPreference("pref_clear_data")!!.isVisible = false } // hande clear cache findPreference("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 (!SentryMain.IS_SENTRY_INSTALLED || !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("pref_test_crash")!!.isVisible = false } else { if (findPreference("pref_test_crash") != null && findPreference( "pref_clear_data" ) != null ) { findPreference("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("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) { findPreference("pref_use_magisk_install_command")!!.isVisible = false } // handle pref_save_logs which saves logs to our external storage and shares them val saveLogs = findPreference("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 = 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) { } } } // 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 } } }