Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f22dd3e0 authored by Ricki Hirner's avatar Ricki Hirner
Browse files

Debug info: use ViewModel

parent 913b395e
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ android {
        targetCompatibility JavaVersion.VERSION_1_8
    }

    dataBinding.enabled = true

    flavorDimensions "distribution"
    productFlavors {
        standard {
@@ -82,6 +84,9 @@ dependencies {
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'androidx.fragment:fragment:1.0.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
    implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
    implementation 'androidx.preference:preference:1.0.0'
    implementation 'com.google.android.material:material:1.0.0'

+181 −200
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@ package at.bitfire.davdroid.ui
import android.Manifest
import android.accounts.Account
import android.accounts.AccountManager
import android.app.Activity
import android.app.Application
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
@@ -32,26 +32,28 @@ import androidx.core.app.ShareCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.pm.PackageInfoCompat
import androidx.loader.app.LoaderManager
import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableField
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModelProviders
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.R
import at.bitfire.davdroid.databinding.ActivityDebugInfoBinding
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.ServiceDB
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.ical4android.TaskProvider
import kotlinx.android.synthetic.main.activity_debug_info.*
import org.dmfs.tasks.contract.TaskContract
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.util.logging.Level
import kotlin.concurrent.thread

class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<String> {
class DebugInfoActivity: AppCompatActivity() {

    companion object {
        const val KEY_THROWABLE = "throwable"
@@ -63,13 +65,17 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
        const val KEY_REMOTE_RESOURCE = "remoteResource"
    }

    private var report: String? = null
    private lateinit var model: ReportModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_debug_info)

        LoaderManager.getInstance(this).initLoader(0, intent.extras, this)
        model = ViewModelProviders.of(this).get(ReportModel::class.java)
        if (savedInstanceState == null)
            model.generateReport(intent.extras)

        val binding = DataBindingUtil.setContentView<ActivityDebugInfoBinding>(this, R.layout.activity_debug_info)
        binding.model = model
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -79,7 +85,7 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri


    fun onShare(item: MenuItem) {
        report?.let {
        model.report.get()?.let { report ->
            val builder = ShareCompat.IntentBuilder.from(this)
                    .setSubject("${getString(R.string.app_name)} ${BuildConfig.VERSION_NAME} debug info")
                    .setType("text/plain")
@@ -92,14 +98,14 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
                val reportFile = File(debugInfoDir, "davx5-info.txt")
                Logger.log.fine("Writing debug info to ${reportFile.absolutePath}")
                val writer = FileWriter(reportFile)
                writer.write(it)
                writer.write(report)
                writer.close()

                builder.setStream(FileProvider.getUriForFile(this, getString(R.string.authority_debug_provider), reportFile))
                builder.intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
            } catch(e: IOException) {
                // creating an attachment failed, so send it inline
                val text = "Couldn't create debug info file: " + Log.getStackTraceString(e) + "\n\n$it"
                val text = "Couldn't create debug info file: " + Log.getStackTraceString(e) + "\n\n$report"
                builder.setText(text)
            }

@@ -108,84 +114,61 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
    }


    override fun onCreateLoader(id: Int, args: Bundle?) =
            ReportLoader(this, args)

    override fun onLoadFinished(loader: Loader<String>, data: String?) {
        data?.let {
            report = it

            text_report.text = it
            text_report.setTextIsSelectable(true)
        }
    }

    override fun onLoaderReset(loader: Loader<String>) {}

    class ReportModel(application: Application): AndroidViewModel(application) {

    class ReportLoader(
            val activity: Activity,
            val extras: Bundle?
    ): AsyncTaskLoader<String>(activity) {
        val report = ObservableField<String>()

        var result: String? = null

        override fun onStartLoading() {
            if (result != null)
                deliverResult(result)
            else
                forceLoad()
        }

        override fun loadInBackground(): String {
            Logger.log.info("Building debug info")
            val report = StringBuilder("--- BEGIN DEBUG INFO ---\n")
        fun generateReport(extras: Bundle?) {
            Logger.log.info("Generating debug info report")
            thread {
                val context = getApplication<Application>()
                val text = StringBuilder("--- BEGIN DEBUG INFO ---\n")

                // begin with most specific information
                extras?.getInt(KEY_PHASE, -1).takeIf { it != -1 }?.let {
                report.append("SYNCHRONIZATION INFO\nSynchronization phase: $it\n")
                    text.append("SYNCHRONIZATION INFO\nSynchronization phase: $it\n")
                }
                extras?.getParcelable<Account>(KEY_ACCOUNT)?.let {
                report.append("Account name: ${it.name}\n")
                    text.append("Account name: ${it.name}\n")
                }
                extras?.getString(KEY_AUTHORITY)?.let {
                report.append("Authority: $it\n")
                    text.append("Authority: $it\n")
                }

                // exception details
                val throwable = extras?.getSerializable(KEY_THROWABLE) as Throwable?
                if (throwable is HttpException) {
                    throwable.request?.let {
                    report.append("\nHTTP REQUEST:\n$it\n")
                    throwable.requestBody?.let { report.append(it) }
                    report.append("\n\n")
                        text.append("\nHTTP REQUEST:\n$it\n")
                        throwable.requestBody?.let { text.append(it) }
                        text.append("\n\n")
                    }
                    throwable.response?.let {
                    report.append("HTTP RESPONSE:\n$it\n")
                    throwable.responseBody?.let { report.append(it) }
                    report.append("\n\n")
                        text.append("HTTP RESPONSE:\n$it\n")
                        throwable.responseBody?.let { text.append(it) }
                        text.append("\n\n")
                    }
                }

                extras?.getString(KEY_LOCAL_RESOURCE)?.let {
                report.append("\nLOCAL RESOURCE:\n$it\n")
                    text.append("\nLOCAL RESOURCE:\n$it\n")
                }
                extras?.getString(KEY_REMOTE_RESOURCE)?.let {
                report.append("\nREMOTE RESOURCE:\n$it\n")
                    text.append("\nREMOTE RESOURCE:\n$it\n")
                }

                throwable?.let {
                report.append("\nEXCEPTION:\n${Log.getStackTraceString(throwable)}")
                    text.append("\nEXCEPTION:\n${Log.getStackTraceString(throwable)}")
                }

                // logs (for instance, from failed resource detection)
                extras?.getString(KEY_LOGS)?.let {
                report.append("\nLOGS:\n$it\n")
                    text.append("\nLOGS:\n$it\n")
                }

                // software information
                try {
                report.append("\nSOFTWARE INFORMATION\n")
                    text.append("\nSOFTWARE INFORMATION\n")
                    val pm = context.packageManager
                    val appIDs = mutableSetOf(      // we always want info about these packages
                            BuildConfig.APPLICATION_ID,                     // DAVx5
@@ -205,19 +188,19 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
                    for (appID in appIDs)
                        try {
                            val info = pm.getPackageInfo(appID, 0)
                        report  .append("* ").append(appID)
                            text  .append("* ").append(appID)
                                    .append(" ").append(info.versionName)
                                    .append(" (").append(PackageInfoCompat.getLongVersionCode(info)).append(")")
                            pm.getInstallerPackageName(appID)?.let { installer ->
                            report.append(" from ").append(installer)
                                text.append(" from ").append(installer)
                            }
                            info.applicationInfo?.let { applicationInfo ->
                                if (!applicationInfo.enabled)
                                report.append(" disabled!")
                                    text.append(" disabled!")
                                if (applicationInfo.flags.and(ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0)
                                report.append(" on external storage!")
                                    text.append(" on external storage!")
                            }
                        report.append("\n")
                            text.append("\n")
                        } catch(e: PackageManager.NameNotFoundException) {
                        }
                } catch(e: Exception) {
@@ -225,7 +208,7 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
                }

                // connectivity
            report.append("\nCONNECTIVITY (at the moment)\n")
                text.append("\nCONNECTIVITY (at the moment)\n")
                val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
                connectivityManager.activeNetworkInfo?.let { networkInfo ->
                    val type = when (networkInfo.type) {
@@ -233,19 +216,19 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
                        ConnectivityManager.TYPE_MOBILE -> "mobile"
                        else -> "type: ${networkInfo.type}"
                    }
                report.append("Active connection: $type, ${networkInfo.detailedState}\n")
                    text.append("Active connection: $type, ${networkInfo.detailedState}\n")
                }
                if (Build.VERSION.SDK_INT >= 23)
                    connectivityManager.defaultProxy?.let { proxy ->
                    report.append("System default proxy: ${proxy.host}:${proxy.port}")
                        text.append("System default proxy: ${proxy.host}:${proxy.port}")
                    }
            report.append("\n")
                text.append("\n")

            report.append("CONFIGURATION\n")
                text.append("CONFIGURATION\n")
                // power saving
                val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
                if (Build.VERSION.SDK_INT >= 23)
                report.append("Power saving disabled: ")
                    text.append("Power saving disabled: ")
                            .append(if (powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID)) "yes" else "no")
                            .append("\n")
                // permissions
@@ -253,7 +236,7 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
                        Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR,
                        TaskProvider.PERMISSION_READ_TASKS, TaskProvider.PERMISSION_WRITE_TASKS,
                        Manifest.permission.ACCESS_COARSE_LOCATION)) {
                report  .append(permission).append(": ")
                    text  .append(permission).append(": ")
                            .append(if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED)
                                "granted"
                            else
@@ -261,7 +244,7 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
                            .append("\n")
                }
                // system-wide sync settings
            report.append("System-wide synchronization: ")
                text.append("System-wide synchronization: ")
                        .append(if (ContentResolver.getMasterSyncAutomatically()) "automatically" else "manually")
                        .append("\n")
                // main accounts
@@ -269,43 +252,43 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
                for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type)))
                    try {
                        val accountSettings = AccountSettings(context, acct)
                    report.append("Account: ${acct.name}\n" +
                        text.append("Account: ${acct.name}\n" +
                                "  Address book sync. interval: ${syncStatus(accountSettings, context.getString(R.string.address_books_authority))}\n" +
                                "  Calendar     sync. interval: ${syncStatus(accountSettings, CalendarContract.AUTHORITY)}\n" +
                                "  OpenTasks    sync. interval: ${syncStatus(accountSettings, TaskProvider.ProviderName.OpenTasks.authority)}\n" +
                                "  WiFi only: ").append(accountSettings.getSyncWifiOnly())
                        accountSettings.getSyncWifiOnlySSIDs()?.let {
                        report.append(", SSIDs: ${accountSettings.getSyncWifiOnlySSIDs()}")
                            text.append(", SSIDs: ${accountSettings.getSyncWifiOnlySSIDs()}")
                        }
                    report.append("\n  [CardDAV] Contact group method: ${accountSettings.getGroupMethod()}")
                        text.append("\n  [CardDAV] Contact group method: ${accountSettings.getGroupMethod()}")
                                .append("\n  [CalDAV] Time range (past days): ${accountSettings.getTimeRangePastDays()}")
                                .append("\n           Manage calendar colors: ${accountSettings.getManageCalendarColors()}")
                                .append("\n           Use event colors: ${accountSettings.getEventColors()}")
                                .append("\n")
                    } catch (e: InvalidAccountException) {
                    report.append("$acct is invalid (unsupported settings version) or does not exist\n")
                        text.append("$acct is invalid (unsupported settings version) or does not exist\n")
                    }
                // address book accounts
                for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)))
                    try {
                        val addressBook = LocalAddressBook(context, acct, null)
                    report.append("Address book account: ${acct.name}\n" +
                        text.append("Address book account: ${acct.name}\n" +
                                "  Main account: ${addressBook.mainAccount}\n" +
                                "  URL: ${addressBook.url}\n" +
                                "  Sync automatically: ").append(ContentResolver.getSyncAutomatically(acct, ContactsContract.AUTHORITY)).append("\n")
                    } catch(e: Exception) {
                    report.append("$acct is invalid: ${e.message}\n")
                        text.append("$acct is invalid: ${e.message}\n")
                    }
            report.append("\n")
                text.append("\n")

                ServiceDB.OpenHelper(context).use { dbHelper ->
                report.append("SQLITE DUMP\n")
                dbHelper.dump(report)
                report.append("\n")
                    text.append("SQLITE DUMP\n")
                    dbHelper.dump(text)
                    text.append("\n")
                }

                try {
                report.append(
                    text.append(
                            "SYSTEM INFORMATION\n" +
                                    "Android version: ${Build.VERSION.RELEASE} (${Build.DISPLAY})\n" +
                                    "Device: ${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})\n\n"
@@ -314,11 +297,8 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
                    Logger.log.log(Level.SEVERE, "Couldn't get system details", e)
                }

            report.append("--- END DEBUG INFO ---\n")

            report.toString().let {
                result = it
                return it
                text.append("--- END DEBUG INFO ---\n")
                report.set(text.toString())
            }
        }

@@ -329,6 +309,7 @@ class DebugInfoActivity: AppCompatActivity(), LoaderManager.LoaderCallbacks<Stri
            } else
                "—"
        }

    }

}
+21 −13
Original line number Diff line number Diff line
@@ -7,8 +7,14 @@
  ~ http://www.gnu.org/licenses/gpl.html
  -->

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="model"
            type="at.bitfire.davdroid.ui.DebugInfoActivity.ReportModel"/>
    </data>

    <ScrollView android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="10dp">
@@ -18,7 +24,9 @@
            android:layout_height="wrap_content"
            android:typeface="monospace"
            android:fontFamily="monospace"
        android:text="@string/please_wait"
            android:text="@{model.report ?? @string/please_wait}"
            android:textIsSelectable="true"
            android:id="@+id/text_report"/>

    </ScrollView>
</layout>
 No newline at end of file