diff --git a/app/src/main/java/org/tasks/backup/TasksJsonExporter.kt b/app/src/main/java/org/tasks/backup/TasksJsonExporter.kt index b07a3adb1..eade0bdc8 100755 --- a/app/src/main/java/org/tasks/backup/TasksJsonExporter.kt +++ b/app/src/main/java/org/tasks/backup/TasksJsonExporter.kt @@ -112,6 +112,20 @@ class TasksJsonExporter @Inject constructor( } } + suspend fun doSettingsExport(os: OutputStream?) = withContext(Dispatchers.IO) { + val writer = os!!.bufferedWriter() + with (JsonWriter(writer)) { + write("{") + write("version", BuildConfig.VERSION_CODE) + write("timestamp", currentTimeMillis()) + write("\"data\":{") + writePreferences() + write("}") + write("}") + } + writer.flush() + } + @Throws(IOException::class) private suspend fun doTasksExport(os: OutputStream?, taskIds: List) = withContext(Dispatchers.IO) { val writer = os!!.bufferedWriter() @@ -146,11 +160,7 @@ class TasksJsonExporter @Inject constructor( write("caldavCalendars", caldavDao.getCalendars()) write("taskListMetadata", taskListMetadataDao.getAll()) write("taskAttachments", taskAttachmentDao.getAttachments()) - write("intPrefs", preferences.getPrefs(Integer::class.java)) - write("longPrefs", preferences.getPrefs(java.lang.Long::class.java)) - write("stringPrefs", preferences.getPrefs(String::class.java)) - write("boolPrefs", preferences.getPrefs(java.lang.Boolean::class.java)) - write("setPrefs", preferences.getPrefs(Set::class.java) as Map>, lastItem = true) + writePreferences() write("}") write("}") } @@ -159,6 +169,14 @@ class TasksJsonExporter @Inject constructor( exportCount = taskIds.size } + private fun JsonWriter.writePreferences() { + write("intPrefs", preferences.getPrefs(Integer::class.java)) + write("longPrefs", preferences.getPrefs(java.lang.Long::class.java)) + write("stringPrefs", preferences.getPrefs(String::class.java)) + write("boolPrefs", preferences.getPrefs(java.lang.Boolean::class.java)) + write("setPrefs", preferences.getPrefs(Set::class.java) as Map>, lastItem = true) + } + private fun onFinishExport(outputFile: String) = post { context?.toast( R.string.export_toast, diff --git a/app/src/main/java/org/tasks/logging/FileLogger.kt b/app/src/main/java/org/tasks/logging/FileLogger.kt index b1b8323cc..29069097f 100644 --- a/app/src/main/java/org/tasks/logging/FileLogger.kt +++ b/app/src/main/java/org/tasks/logging/FileLogger.kt @@ -4,12 +4,14 @@ import android.annotation.SuppressLint import android.app.Application import android.os.Process import android.util.Log +import dagger.Lazy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.tasks.BuildConfig +import org.tasks.backup.TasksJsonExporter import org.tasks.logging.LogFormatter.Companion.LINE_SEPARATOR import org.tasks.preferences.Device import timber.log.Timber @@ -27,6 +29,8 @@ import javax.inject.Singleton @Singleton class FileLogger @Inject constructor( private val context: Application, + private val device: Lazy, + private val tasksJsonExporter: Lazy, ) : Timber.DebugTree() { private val logDirectory = File(context.cacheDir, "logs").apply { mkdirs() } private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) @@ -82,7 +86,10 @@ class FileLogger @Inject constructor( Timber.e(e, "Failed to save logcat") } zos.putNextEntry(ZipEntry("device.txt")) - zos.write(Device(context).debugInfo.toByteArray()) + zos.write(device.get().debugInfo.toByteArray()) + zos.closeEntry() + zos.putNextEntry(ZipEntry("settings.json")) + tasksJsonExporter.get().doSettingsExport(zos) zos.closeEntry() fileHandler.flush() logDirectory diff --git a/app/src/main/java/org/tasks/preferences/Device.java b/app/src/main/java/org/tasks/preferences/Device.java deleted file mode 100644 index 4b7b2a1c3..000000000 --- a/app/src/main/java/org/tasks/preferences/Device.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.tasks.preferences; - -import static java.util.Arrays.asList; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Build; -import android.speech.RecognizerIntent; - -import com.google.common.base.Joiner; - -import org.tasks.BuildConfig; - -import java.util.List; - -import javax.inject.Inject; - -import dagger.hilt.android.qualifiers.ApplicationContext; -import timber.log.Timber; - -public class Device { - - private final Context context; - - @Inject - public Device(@ApplicationContext Context context) { - this.context = context; - } - - public boolean hasCamera() { - return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); - } - - public boolean hasMicrophone() { - return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE); - } - - public boolean voiceInputAvailable() { - PackageManager pm = context.getPackageManager(); - List activities = - pm.queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); - return (activities.size() != 0); - } - - public String getDebugInfo() { - try { - return Joiner.on("\n") - .join( - asList( - "", - "----------", - "Tasks: " - + BuildConfig.VERSION_NAME - + " (" - + BuildConfig.FLAVOR - + " build " - + BuildConfig.VERSION_CODE - + ")", - "Android: " + Build.VERSION.RELEASE + " (" + Build.DISPLAY + ")", - "Locale: " + java.util.Locale.getDefault(), - "Model: " + Build.MANUFACTURER + " " + Build.MODEL, - "Product: " + Build.PRODUCT + " (" + Build.DEVICE + ")", - "Kernel: " - + System.getProperty("os.version") - + " (" - + Build.VERSION.INCREMENTAL - + ")", - "----------", - "")); - } catch (Exception e) { - Timber.e(e); - } - return ""; - } -} diff --git a/app/src/main/java/org/tasks/preferences/Device.kt b/app/src/main/java/org/tasks/preferences/Device.kt new file mode 100644 index 000000000..7d39c2ff6 --- /dev/null +++ b/app/src/main/java/org/tasks/preferences/Device.kt @@ -0,0 +1,47 @@ +package org.tasks.preferences + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.speech.RecognizerIntent +import dagger.hilt.android.qualifiers.ApplicationContext +import org.tasks.BuildConfig +import java.util.Locale +import javax.inject.Inject + +class Device @Inject constructor( + @ApplicationContext private val context: Context, + private val permissionChecker: PermissionChecker, +) { + @SuppressLint("UnsupportedChromeOsCameraSystemFeature") + fun hasCamera() = context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) + + fun hasMicrophone() = context.packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) + + fun voiceInputAvailable(): Boolean { + val pm = context.packageManager + val activities = + pm.queryIntentActivities(Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0) + return (activities.size != 0) + } + + val debugInfo: String + get() = """ + ---------- + Tasks: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR} build ${BuildConfig.VERSION_CODE}) + Android: ${Build.VERSION.RELEASE} (${Build.DISPLAY}) + Locale: ${Locale.getDefault()} + Model: ${Build.MANUFACTURER} ${Build.MODEL} + Product: ${Build.PRODUCT} (${Build.DEVICE}) + Kernel: ${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL}) + ---------- + notifications: ${permissionChecker.hasNotificationPermission()} + reminders: ${permissionChecker.hasAlarmsAndRemindersPermission()} + background location: ${permissionChecker.canAccessBackgroundLocation()} + foreground location: ${permissionChecker.canAccessForegroundLocation()} + calendar: ${permissionChecker.canAccessCalendars()} + ---------- + """.trimIndent() +} diff --git a/app/src/main/java/org/tasks/preferences/PermissionChecker.java b/app/src/main/java/org/tasks/preferences/PermissionChecker.java index daa79af25..7684d7fdf 100644 --- a/app/src/main/java/org/tasks/preferences/PermissionChecker.java +++ b/app/src/main/java/org/tasks/preferences/PermissionChecker.java @@ -41,9 +41,12 @@ public class PermissionChecker { return !atLeastTiramisu() || checkPermissions(permission.POST_NOTIFICATIONS); } + public boolean hasAlarmsAndRemindersPermission() { + return org.tasks.extensions.Context.INSTANCE.canScheduleExactAlarms(context); + } + public boolean canNotify() { - return org.tasks.extensions.Context.INSTANCE.canScheduleExactAlarms(context) - && hasNotificationPermission(); + return hasAlarmsAndRemindersPermission() && hasNotificationPermission(); } private boolean checkPermissions(String... permissions) { diff --git a/app/src/main/java/org/tasks/ui/TaskListViewModel.kt b/app/src/main/java/org/tasks/ui/TaskListViewModel.kt index f0d4b243d..b14b6e795 100644 --- a/app/src/main/java/org/tasks/ui/TaskListViewModel.kt +++ b/app/src/main/java/org/tasks/ui/TaskListViewModel.kt @@ -220,7 +220,7 @@ class TaskListViewModel @Inject constructor( fun dismissBanner(tookAction: Boolean = false) { when (state.value.banner) { Banner.NotificationsDisabled -> preferences.warnNotificationsDisabled = tookAction - Banner.AlarmsDisabled -> preferences.warnAlarmsDisabled = false + Banner.AlarmsDisabled -> preferences.warnAlarmsDisabled = tookAction Banner.QuietHoursEnabled -> preferences.warnQuietHoursDisabled = false Banner.BegForMoney -> { preferences.lastSubscribeRequest = currentTimeMillis()