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

252 lines
11 KiB

/*
* 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.syncadapter.*
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
import com.etesync.journalmanager.Exceptions.HttpException
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)
ACRA.getErrorReporter().handleSilentException(null)
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
}
}
}