You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
etesync-android/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.kt

256 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright © 2013 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter.ui
import android.Manifest
import android.accounts.Account
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.app.LoaderManager
import android.content.*
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.provider.CalendarContract
import android.provider.ContactsContract
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import androidx.core.content.ContextCompat
import at.bitfire.ical4android.TaskProvider.ProviderName
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.journalmanager.Exceptions.HttpException
import com.etesync.syncadapter.*
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.EntryEntity
import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.model.ServiceDB
import com.etesync.syncadapter.model.ServiceEntity
import com.etesync.syncadapter.resource.LocalAddressBook
import org.acra.ACRA
import org.apache.commons.lang3.exception.ExceptionUtils
import org.apache.commons.lang3.text.WordUtils
import java.io.File
import java.util.logging.Level
class DebugInfoActivity : BaseActivity(), LoaderManager.LoaderCallbacks<String> {
internal lateinit var tvReport: TextView
internal lateinit var report: String
internal var reportFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_debug_info)
tvReport = findViewById(R.id.text_report)
loaderManager.initLoader(0, intent.extras, this)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.activity_debug_info, menu)
return true
}
fun onShare(item: MenuItem) {
ACRA.getErrorReporter().putCustomData("debug_info", report)
val account: Account? = intent.extras?.getParcelable(KEY_ACCOUNT)
if (account != null) {
ACRA.getErrorReporter().putCustomData("username", account.name)
}
ACRA.getErrorReporter().handleException(intent.extras?.getSerializable(KEY_THROWABLE) as Throwable?)
ACRA.getErrorReporter().removeCustomData("debug_info")
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<String> {
return ReportLoader(this, args)
}
override fun onLoadFinished(loader: Loader<String>, data: String?) {
if (data != null) {
report = data
tvReport.setText(report)
}
}
override fun onLoaderReset(loader: Loader<String>) {}
internal class ReportLoader(context: Context, val extras: Bundle?) : AsyncTaskLoader<String>(context) {
override fun onStartLoading() {
forceLoad()
}
@SuppressLint("MissingPermission")
override fun loadInBackground(): String {
var throwable: Throwable? = null
var caller: String? = null
var logs: String? = null
var authority: String? = null
var account: Account? = null
var phase: String? = null
if (extras != null) {
throwable = extras.getSerializable(KEY_THROWABLE) as Throwable?
caller = extras.getString(KEY_CALLER)
logs = extras.getString(KEY_LOGS)
account = extras.getParcelable(KEY_ACCOUNT)
authority = extras.getString(KEY_AUTHORITY)
phase = if (extras.containsKey(KEY_PHASE)) context.getString(extras.getInt(KEY_PHASE)) else null
}
val report = StringBuilder("--- BEGIN DEBUG INFO ---\n")
// begin with most specific information
if (phase != null)
report.append("SYNCHRONIZATION INFO\nSynchronization phase: ").append(phase).append("\n")
if (account != null)
report.append("Account name: ").append(account.name).append("\n")
if (authority != null)
report.append("Authority: ").append(authority).append("\n")
if (caller != null)
report.append("Debug activity source: ").append(caller).append("\n")
if (throwable is HttpException) {
val http = throwable as HttpException?
if (http!!.request != null)
report.append("\nHTTP REQUEST:\n").append(http.request).append("\n\n")
if (http.request != null)
report.append("HTTP RESPONSE:\n").append(http.request).append("\n")
}
if (throwable != null)
report.append("\nEXCEPTION:\n")
.append(ExceptionUtils.getStackTrace(throwable))
if (logs != null)
report.append("\nLOGS:\n").append(logs).append("\n")
val context = context
try {
val pm = context.packageManager
var installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID)
if (TextUtils.isEmpty(installedFrom))
installedFrom = "APK (directly)"
report.append("\nSOFTWARE INFORMATION\n" + "EteSync version: ").append(BuildConfig.VERSION_NAME).append(" (").append(BuildConfig.VERSION_CODE).append(") ").append("\n")
.append("Installed from: ").append(installedFrom).append("\n")
} catch (ex: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't get software information", ex)
}
report.append("CONFIGURATION\n")
// power saving
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager?
if (powerManager != null && Build.VERSION.SDK_INT >= 23)
report.append("Power saving disabled: ")
.append(if (powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID)) "yes" else "no")
.append("\n")
// permissions
for (permission in arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR) + ProviderName.OpenTasks.permissions + ProviderName.TasksOrg.permissions)
report.append(permission).append(" permission: ")
.append(if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) "granted" else "denied")
.append("\n")
// system-wide sync settings
report.append("System-wide synchronization: ")
.append(if (ContentResolver.getMasterSyncAutomatically()) "automatically" else "manually")
.append("\n")
// main accounts
val accountManager = AccountManager.get(context)
for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type)))
try {
val settings = AccountSettings(context, acct)
report.append("Account: ").append(acct.name).append("\n" + " Address book sync. interval: ").append(syncStatus(settings, App.addressBooksAuthority)).append("\n" + " Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" + " OpenTasks sync. interval: ").append(syncStatus(settings, ProviderName.OpenTasks.authority)).append("\n" + " Tasks.org sync. interval: ").append(syncStatus(settings, ProviderName.TasksOrg.authority)).append("\n" + " WiFi only: ").append(settings.syncWifiOnly)
if (settings.syncWifiOnlySSID != null)
report.append(", SSID: ").append(settings.syncWifiOnlySSID)
report.append("\n [CardDAV] Contact group method: ").append(settings.groupMethod)
.append("\n Manage calendar colors: ").append(settings.manageCalendarColors)
.append("\n")
} catch (e: InvalidAccountException) {
report.append(acct).append(" is invalid (unsupported settings version) or does not exist\n")
}
// address book accounts
for (acct in accountManager.getAccountsByType(App.addressBookAccountType))
try {
val addressBook = LocalAddressBook(context, acct, null)
report.append("Address book account: ").append(acct.name).append("\n" + " Main account: ").append(addressBook.mainAccount).append("\n" + " URL: ").append(addressBook.url).append("\n" + " Sync automatically: ").append(ContentResolver.getSyncAutomatically(acct, ContactsContract.AUTHORITY)).append("\n")
} catch (e: ContactsStorageException) {
report.append(acct).append(" is invalid: ").append(e.message).append("\n")
}
report.append("\n")
report.append("SQLITE DUMP\n")
val dbHelper = ServiceDB.OpenHelper(context)
dbHelper.dump(report)
dbHelper.close()
report.append("\n")
report.append("SERVICES DUMP\n")
val data = (getContext().applicationContext as App).data
for (serviceEntity in data.select(ServiceEntity::class.java).get()) {
report.append(serviceEntity.toString() + "\n")
}
report.append("\n")
report.append("JOURNALS DUMP\n")
val journals = data.select(JournalEntity::class.java).where(JournalEntity.DELETED.eq(false)).get().toList()
for (journal in journals) {
report.append(journal.toString() + "\n")
val entryCount = data.count(EntryEntity::class.java).where(EntryEntity.JOURNAL.eq(journal)).get().value()
report.append("\tEntries: " + entryCount.toString() + "\n\n")
}
report.append("\n")
try {
report.append(
"SYSTEM INFORMATION\n" + "Android version: ").append(Build.VERSION.RELEASE).append(" (").append(Build.DISPLAY).append(")\n" + "Device: ").append(WordUtils.capitalize(Build.MANUFACTURER)).append(" ").append(Build.MODEL).append(" (").append(Build.DEVICE).append(")\n\n"
)
} catch (ex: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't get system details", ex)
}
report.append("--- END DEBUG INFO ---\n")
return report.toString()
}
protected fun syncStatus(settings: AccountSettings, authority: String): String {
val interval = settings.getSyncInterval(authority)
return if (interval != null)
if (interval == AccountSettings.SYNC_INTERVAL_MANUALLY) "manually" else (interval / 60).toString() + " min"
else
""
}
}
companion object {
val KEY_CALLER = "caller"
val KEY_THROWABLE = "throwable"
val KEY_LOGS = "logs"
val KEY_AUTHORITY = "authority"
val KEY_PHASE = "phase"
fun newIntent(context: Context?, caller: String): Intent {
val intent = Intent(context, DebugInfoActivity::class.java)
intent.putExtra(KEY_CALLER, caller)
return intent
}
}
}