Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(#600) Notification system for settings (for crash logs and apk updates) #656

Merged
merged 15 commits into from
Mar 9, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions Kuroba/app/src/main/java/com/github/adamantcheese/chan/Chan.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@
import com.github.adamantcheese.chan.core.manager.BoardManager;
import com.github.adamantcheese.chan.core.manager.FilterWatchManager;
import com.github.adamantcheese.chan.core.manager.ReportManager;
import com.github.adamantcheese.chan.core.manager.SettingsNotificationManager;
import com.github.adamantcheese.chan.core.settings.ChanSettings;
import com.github.adamantcheese.chan.core.site.SiteService;
import com.github.adamantcheese.chan.ui.service.LastPageNotification;
import com.github.adamantcheese.chan.ui.service.SavingNotification;
import com.github.adamantcheese.chan.ui.service.WatchNotification;
import com.github.adamantcheese.chan.ui.settings.SettingNotificationType;
import com.github.adamantcheese.chan.utils.AndroidUtils;
import com.github.adamantcheese.chan.utils.Logger;

Expand Down Expand Up @@ -75,6 +77,9 @@ public class Chan
@Inject
ReportManager reportManager;

@Inject
SettingsNotificationManager settingsNotificationManager;

private static Feather feather;

public static <T> T instance(Class<T> tClass) {
Expand Down Expand Up @@ -192,8 +197,10 @@ public void onCreate() {
System.exit(999);
});

if (ChanSettings.autoCrashLogsUpload.get()) {
reportManager.sendCollectedCrashLogs();
if (ChanSettings.collectCrashLogs.get()) {
if (reportManager.hasCrashLogs()) {
settingsNotificationManager.notify(SettingNotificationType.CrashLogs);
}
}
}

Expand Down Expand Up @@ -230,7 +237,7 @@ private void onUnhandledException(Throwable exception, String error) {
return;
}

if (ChanSettings.autoCrashLogsUpload.get()) {
if (ChanSettings.collectCrashLogs.get()) {
reportManager.storeCrashLog(error);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import android.view.KeyEvent;
import android.view.ViewGroup;

import androidx.annotation.CallSuper;

import com.github.adamantcheese.chan.StartActivity;
import com.github.adamantcheese.chan.controller.transition.FadeInTransition;
import com.github.adamantcheese.chan.controller.transition.FadeOutTransition;
Expand Down Expand Up @@ -73,13 +75,15 @@ public Controller(Context context) {
this.context = context;
}

@CallSuper
public void onCreate() {
alive = true;
if (LOG_STATES) {
Logger.test(getClass().getSimpleName() + " onCreate");
}
}

@CallSuper
public void onShow() {
shown = true;
if (LOG_STATES) {
Expand All @@ -95,6 +99,7 @@ public void onShow() {
}
}

@CallSuper
public void onHide() {
shown = false;
if (LOG_STATES) {
Expand All @@ -110,6 +115,7 @@ public void onHide() {
}
}

@CallSuper
public void onDestroy() {
alive = false;
if (LOG_STATES) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.github.adamantcheese.chan.core.manager.ReplyManager;
import com.github.adamantcheese.chan.core.manager.ReportManager;
import com.github.adamantcheese.chan.core.manager.SavedThreadLoaderManager;
import com.github.adamantcheese.chan.core.manager.SettingsNotificationManager;
import com.github.adamantcheese.chan.core.manager.ThreadSaveManager;
import com.github.adamantcheese.chan.core.manager.WakeManager;
import com.github.adamantcheese.chan.core.manager.WatchManager;
Expand Down Expand Up @@ -175,16 +176,24 @@ public MockReplyManager provideMockReplyManager() {
public ReportManager provideReportManager(
NetModule.ProxiedOkHttpClient okHttpClient,
Gson gson,
ThreadSaveManager threadSaveManager
ThreadSaveManager threadSaveManager,
SettingsNotificationManager settingsNotificationManager
) {
Logger.d(AppModule.DI_TAG, "Report manager");
File cacheDir = getCacheDir();

return new ReportManager(
okHttpClient.getProxiedClient(),
threadSaveManager,
settingsNotificationManager,
gson,
new File(cacheDir, CRASH_LOGS_DIR_NAME)
);
}

@Provides
@Singleton
public SettingsNotificationManager provideSettingsNotificationManager() {
return new SettingsNotificationManager();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.github.adamantcheese.chan.BuildConfig
import com.github.adamantcheese.chan.core.base.MResult
import com.github.adamantcheese.chan.core.settings.ChanSettings
import com.github.adamantcheese.chan.ui.controller.LogsController
import com.github.adamantcheese.chan.ui.layout.crashlogs.CrashLog
import com.github.adamantcheese.chan.ui.settings.SettingNotificationType
import com.github.adamantcheese.chan.utils.BackgroundUtils
import com.github.adamantcheese.chan.utils.Logger
import com.google.gson.Gson
Expand All @@ -28,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger
class ReportManager(
private val okHttpClient: OkHttpClient,
private val threadSaveManager: ThreadSaveManager,
private val settingsNotificationManager: SettingsNotificationManager,
private val gson: Gson,
private val crashLogsDirPath: File
) {
Expand Down Expand Up @@ -73,6 +76,13 @@ class ReportManager(
return@flatMapSingle processSingleRequest(request, crashLogFile)
}
}
.debounce(1, TimeUnit.SECONDS)
.doOnNext {
// If no more crash logs left, remove the notification
if (!hasCrashLogs()) {
settingsNotificationManager.cancel(SettingNotificationType.CrashLogs)
}
}
.subscribe({
// Do nothing
}, { error ->
Expand Down Expand Up @@ -112,7 +122,7 @@ class ReportManager(
return
}

val time = System.nanoTime()
val time = System.currentTimeMillis()
val newCrashLog = File(crashLogsDirPath, "${CRASH_LOG_FILE_NAME_PREFIX}_${time}.txt")

if (newCrashLog.exists()) {
Expand Down Expand Up @@ -143,37 +153,97 @@ class ReportManager(
Logger.d(TAG, "Stored new crash log, path = ${newCrashLog.absolutePath}")
}

// Since this is a singleton we don't care about disposing of this thing because nothing may
// leak here
@SuppressLint("CheckResult")
fun sendCollectedCrashLogs() {
fun hasCrashLogs(): Boolean {
if (!createCrashLogsDirIfNotExists()) {
return false
}

val crashLogs = crashLogsDirPath.listFiles()
return crashLogs != null && crashLogs.isNotEmpty()
}

fun countCrashLogs(): Int {
if (!createCrashLogsDirIfNotExists()) {
return 0
}

return crashLogsDirPath.listFiles()?.size ?: 0
}

fun getCrashLogs(): List<File> {
if (!createCrashLogsDirIfNotExists()) {
return emptyList()
}

return crashLogsDirPath.listFiles()
?.sortedByDescending { file -> file.lastModified() }
?.toList() ?: emptyList()
}

fun deleteCrashLogs(crashLogs: List<CrashLog>) {
if (!createCrashLogsDirIfNotExists()) {
settingsNotificationManager.cancel(SettingNotificationType.CrashLogs)
return
}

crashLogs.forEach { crashLog -> crashLog.file.delete() }

val remainingCrashLogs = crashLogsDirPath.listFiles()?.size ?: 0
if (remainingCrashLogs == 0) {
settingsNotificationManager.cancel(SettingNotificationType.CrashLogs)
return
}

// There are still crash logs left, so show the notifications if they are not shown yet
settingsNotificationManager.notify(SettingNotificationType.CrashLogs)
}

fun deleteAllCrashLogs() {
if (!createCrashLogsDirIfNotExists()) {
settingsNotificationManager.cancel(SettingNotificationType.CrashLogs)
return
}

val potentialCrashLogs = crashLogsDirPath.listFiles()
if (potentialCrashLogs.isNullOrEmpty()) {
Logger.d(TAG, "No new crash logs")
settingsNotificationManager.cancel(SettingNotificationType.CrashLogs)
return
}

potentialCrashLogs.asSequence()
.filter { file -> file.name.startsWith(CRASH_LOG_FILE_NAME_PREFIX) }
K1rakishou marked this conversation as resolved.
Show resolved Hide resolved
.forEach { crashLogFile -> crashLogFile.delete() }

val remainingCrashLogs = crashLogsDirPath.listFiles()?.size ?: 0
if (remainingCrashLogs == 0) {
settingsNotificationManager.cancel(SettingNotificationType.CrashLogs)
return
}

// There are still crash logs left, so show the notifications if they are not shown yet
settingsNotificationManager.notify(SettingNotificationType.CrashLogs)
}

fun sendCrashLogs(crashLogs: List<CrashLog>): Completable {
if (!createCrashLogsDirIfNotExists()) {
return Completable.complete()
}

if (crashLogs.isEmpty()) {
return Completable.complete()
}

// Collect and create reports on a background thread because logs may wait quite a lot now
// and it may lag the UI.
Completable.fromAction {
return Completable.fromAction {
BackgroundUtils.ensureBackgroundThread()

val potentialCrashLogs = crashLogsDirPath.listFiles()
if (potentialCrashLogs.isNullOrEmpty()) {
Logger.d(TAG, "No new crash logs")
return@fromAction
}

potentialCrashLogs.asSequence()
.filter { file -> file.name.startsWith(CRASH_LOG_FILE_NAME_PREFIX) }
.map { file -> createReportRequest(file) }
.filterNotNull()
crashLogs
.mapNotNull { crashLog -> createReportRequest(crashLog) }
.forEach { request -> crashLogSenderQueue.onNext(request) }
}
.subscribeOn(senderScheduler)
.subscribe({
// Do nothing
}, { error ->
Logger.e(TAG, "Error while collecting logs: ${error}")
})
.subscribeOn(senderScheduler)
}

fun sendReport(title: String, description: String, logs: String?): Single<MResult<Boolean>> {
Expand Down Expand Up @@ -235,11 +305,11 @@ class ReportManager(
"active: $filesLocationActiveDirType"
}

private fun createReportRequest(file: File): ReportRequestWithFile? {
private fun createReportRequest(crashLog: CrashLog): ReportRequestWithFile? {
BackgroundUtils.ensureBackgroundThread()

val log = try {
file.readText()
crashLog.file.readText()
} catch (error: Throwable) {
Logger.e(TAG, "Error reading crash log file", error)
return null
Expand All @@ -256,7 +326,7 @@ class ReportManager(

return ReportRequestWithFile(
reportRequest = request,
crashLogFile = file
crashLogFile = crashLog.file
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.github.adamantcheese.chan.core.manager

import com.github.adamantcheese.chan.ui.settings.SettingNotificationType
import com.github.adamantcheese.chan.utils.Logger
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.processors.BehaviorProcessor

class SettingsNotificationManager {
private val notifications: MutableSet<SettingNotificationType> = mutableSetOf()
K1rakishou marked this conversation as resolved.
Show resolved Hide resolved

/**
* A reactive stream that is being used to notify observer about [notifications] changes
K1rakishou marked this conversation as resolved.
Show resolved Hide resolved
* */
private val activeNotificationsSubject = BehaviorProcessor.createDefault(Unit)

/**
* If [notifications] doesn't contain [notificationType] yet, then notifies
* all observers that there is a new notification
* */
@Synchronized
fun notify(notificationType: SettingNotificationType) {
if (notifications.add(notificationType)) {
Logger.d(TAG, "Added ${notificationType.name} notification")
activeNotificationsSubject.onNext(Unit)
}
}

@Synchronized
fun getNotificationByPriority(): SettingNotificationType? {
if (contains(SettingNotificationType.ApkUpdate)) {
return SettingNotificationType.ApkUpdate
}

if (contains(SettingNotificationType.CrashLogs)) {
K1rakishou marked this conversation as resolved.
Show resolved Hide resolved
return SettingNotificationType.CrashLogs
}

// Add new notifications here. Don't forget that order matters! The order affects priority.
// For now "Apk update" has higher priority than "Crash log".

return null
}

@Synchronized
fun hasNotifications(notificationType: SettingNotificationType): Boolean {
return contains(notificationType)
}

@Synchronized
fun notificationsCount(): Int = notifications.count()

@Synchronized
fun getOrDefault(notificationType: SettingNotificationType): SettingNotificationType {
if (!contains(notificationType)) {
return SettingNotificationType.Default
}

return notificationType
}

@Synchronized
fun count(): Int = notifications.size

@Synchronized
fun contains(notificationType: SettingNotificationType): Boolean {
return notifications.contains(notificationType)
}

/**
* If [notifications] contains [notificationType], then notifies all observers that this
* notification has been canceled
* */
@Synchronized
fun cancel(notificationType: SettingNotificationType) {
if (notifications.remove(notificationType)) {
Logger.d(TAG, "Removed ${notificationType.name} notification")
activeNotificationsSubject.onNext(Unit)
}
}

/**
* Use this to observe current notification state. Duplicates checks and everything else is done
* internally so you don't have to worry that you will get the same state twice. All updates
* come on main thread so there is no need to worry about that as well.
* */
fun listenForNotificationUpdates(): Flowable<Unit> = activeNotificationsSubject
.onBackpressureBuffer()
.observeOn(AndroidSchedulers.mainThread())
K1rakishou marked this conversation as resolved.
Show resolved Hide resolved

companion object {
private const val TAG = "SettingsNotificationManager"
}
}
Loading