Logger.kt 5.55 KB
Newer Older
Ricki Hirner's avatar
Ricki Hirner committed
1 2 3 4 5 6 7 8
/*
 * Copyright © 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
 */

9
package at.bitfire.davdroid.log
Ricki Hirner's avatar
Ricki Hirner committed
10

11
import android.annotation.SuppressLint
12
import android.app.PendingIntent
Ricki Hirner's avatar
Ricki Hirner committed
13
import android.content.Context
14
import android.content.Intent
15 16
import android.content.SharedPreferences
import android.preference.PreferenceManager
17
import android.util.Log
18
import android.widget.Toast
Ricki Hirner's avatar
Ricki Hirner committed
19 20
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
21
import androidx.core.content.FileProvider
22
import at.bitfire.davdroid.R
23
import at.bitfire.davdroid.ui.AppSettingsActivity
24
import at.bitfire.davdroid.ui.NotificationUtils
Ricki Hirner's avatar
Ricki Hirner committed
25 26 27 28 29
import java.io.File
import java.io.IOException
import java.util.logging.FileHandler
import java.util.logging.Level

30 31
@SuppressLint("StaticFieldLeak")    // we'll only keep an app context
object Logger : SharedPreferences.OnSharedPreferenceChangeListener {
Ricki Hirner's avatar
Ricki Hirner committed
32

33
    private const val LOG_TO_FILE = "log_to_file"
34 35 36

    val log = java.util.logging.Logger.getLogger("davdroid")!!

37
    private lateinit var context: Context
38
    private lateinit var preferences: SharedPreferences
39

40 41 42 43 44 45 46
    fun initialize(someContext: Context) {
        context = someContext.applicationContext
        preferences = PreferenceManager.getDefaultSharedPreferences(context)
        preferences.registerOnSharedPreferenceChangeListener(this)

        reinitialize()
    }
47

48 49 50 51 52
    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
        if (key == LOG_TO_FILE) {
            log.info("Logging settings changed; re-initializing logger")
            reinitialize()
        }
53 54
    }

55 56
    private fun reinitialize() {
        val logToFile = preferences.getBoolean(LOG_TO_FILE, false)
57 58 59 60 61 62 63 64 65 66 67 68 69
        val logVerbose = logToFile || Log.isLoggable(log.name, Log.DEBUG)

        log.info("Verbose logging: $logVerbose; to file: $logToFile")

        // set logging level according to preferences
        val rootLogger = java.util.logging.Logger.getLogger("")
        rootLogger.level = if (logVerbose) Level.ALL else Level.INFO

        // remove all handlers and add our own logcat handler
        rootLogger.useParentHandlers = false
        rootLogger.handlers.forEach { rootLogger.removeHandler(it) }
        rootLogger.addHandler(LogcatHandler)

70
        val nm = NotificationManagerCompat.from(context)
71 72
        // log to external file according to preferences
        if (logToFile) {
73
            val builder = NotificationUtils.newBuilder(context, NotificationUtils.CHANNEL_DEBUG)
74
            builder .setSmallIcon(R.drawable.ic_sd_storage_notification)
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
                    .setContentTitle(context.getString(R.string.logging_notification_title))

            val logDir = debugDir(context) ?: return
            val logFile = File(logDir, "davx5-log.txt")

            try {
                val fileHandler = FileHandler(logFile.toString(), true)
                fileHandler.formatter = PlainTextFormatter.DEFAULT
                rootLogger.addHandler(fileHandler)

                val prefIntent = Intent(context, AppSettingsActivity::class.java)
                prefIntent.putExtra(AppSettingsActivity.EXTRA_SCROLL_TO, LOG_TO_FILE)

                builder .setContentText(logDir.path)
                        .setCategory(NotificationCompat.CATEGORY_STATUS)
                        .setPriority(NotificationCompat.PRIORITY_HIGH)
                        .setContentText(context.getString(R.string.logging_notification_text))
                        .setContentIntent(PendingIntent.getActivity(context, 0, prefIntent, PendingIntent.FLAG_UPDATE_CURRENT))
                        .setOngoing(true)

                // add "Share" action
                val logFileUri = FileProvider.getUriForFile(context, context.getString(R.string.authority_log_provider), logFile)
                log.fine("Now logging to file: $logFile -> $logFileUri")

                val shareIntent = Intent(Intent.ACTION_SEND)
                shareIntent.setDataAndType(logFileUri, "text/plain")
                shareIntent.putExtra(Intent.EXTRA_SUBJECT, "DAVx⁵ logs")
                shareIntent.putExtra(Intent.EXTRA_STREAM, logFileUri)
                shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                val shareAction = NotificationCompat.Action.Builder(R.drawable.ic_share_action,
                        context.getString(R.string.logging_notification_share_log),
                        PendingIntent.getActivity(context, 0, shareIntent, PendingIntent.FLAG_UPDATE_CURRENT))
                builder.addAction(shareAction.build())
            } catch(e: IOException) {
                log.log(Level.SEVERE, "Couldn't create log file", e)
                Toast.makeText(context, context.getString(R.string.logging_couldnt_create_file), Toast.LENGTH_LONG).show()
            }
112

113
            nm.notify(NotificationUtils.NOTIFY_EXTERNAL_FILE_LOGGING, builder.build())
114
        } else {
115
            nm.cancel(NotificationUtils.NOTIFY_EXTERNAL_FILE_LOGGING)
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132

            // delete old logs
            debugDir(context)?.deleteRecursively()
        }
    }


    private fun debugDir(context: Context): File? {
        val dir = File(context.filesDir, "debug")
        if (dir.exists() && dir.isDirectory)
            return dir

        if (dir.mkdir())
            return dir

        Toast.makeText(context, context.getString(R.string.logging_couldnt_create_file), Toast.LENGTH_LONG).show()
        return null
Ricki Hirner's avatar
Ricki Hirner committed
133 134 135
    }

}