diff --git a/app/src/main/kotlin/com/simplemobiletools/clock/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/clock/activities/MainActivity.kt index d6b11dcd..115ec528 100644 --- a/app/src/main/kotlin/com/simplemobiletools/clock/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/clock/activities/MainActivity.kt @@ -44,9 +44,11 @@ class MainActivity : SimpleActivity() { setupTabs() updateWidgets() - lifecycleScope.launch(Dispatchers.IO) { - if (getEnabledAlarms().isEmpty()) { - rescheduleEnabledAlarms() + getEnabledAlarms { enabledAlarms -> + if (enabledAlarms.isNullOrEmpty()) { + ensureBackgroundThread { + rescheduleEnabledAlarms() + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/clock/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/clock/extensions/Context.kt index 0d1025d4..eb5ad7fc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/clock/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/clock/extensions/Context.kt @@ -270,41 +270,58 @@ fun Context.formatTo12HourFormat(showSeconds: Boolean, hours: Int, minutes: Int, return "${formatTime(showSeconds, false, newHours, minutes, seconds)} $appendable" } -suspend fun Context.getClosestEnabledAlarmString(): String = withContext(Dispatchers.IO) { - val nextAlarmList = getEnabledAlarms() - .mapNotNull { getTimeUntilNextAlarm(it.timeInMinutes, it.days) } - - if (nextAlarmList.isEmpty()) { - return@withContext "" - } - - var closestAlarmTime = Int.MAX_VALUE - nextAlarmList.forEach { time -> - if (time < closestAlarmTime) { - closestAlarmTime = time +fun Context.getClosestEnabledAlarmString(result: (String) -> Unit) { + getEnabledAlarms { enabledAlarms -> + if (enabledAlarms == null) { + result.invoke("") + return@getEnabledAlarms } - } - if (closestAlarmTime == Int.MAX_VALUE) { - return@withContext "" - } + val nextAlarmList = enabledAlarms + .mapNotNull { getTimeUntilNextAlarm(it.timeInMinutes, it.days) } - val calendar = Calendar.getInstance().apply { firstDayOfWeek = Calendar.MONDAY } - calendar.add(Calendar.MINUTE, closestAlarmTime) - val dayOfWeekIndex = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7 - val dayOfWeek = resources.getStringArray(R.array.week_days_short)[dayOfWeekIndex] - val pattern = if (DateFormat.is24HourFormat(this@getClosestEnabledAlarmString)) { - "HH:mm" - } else { - "h:mm a" - } + if (nextAlarmList.isEmpty()) { + result.invoke("") + } - val formattedTime = SimpleDateFormat(pattern, Locale.getDefault()).format(calendar.time) - return@withContext "$dayOfWeek $formattedTime" + var closestAlarmTime = Int.MAX_VALUE + nextAlarmList.forEach { time -> + if (time < closestAlarmTime) { + closestAlarmTime = time + } + } + + if (closestAlarmTime == Int.MAX_VALUE) { + result.invoke("") + } + + val calendar = Calendar.getInstance().apply { firstDayOfWeek = Calendar.MONDAY } + calendar.add(Calendar.MINUTE, closestAlarmTime) + val dayOfWeekIndex = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7 + val dayOfWeek = resources.getStringArray(R.array.week_days_short)[dayOfWeekIndex] + val pattern = if (DateFormat.is24HourFormat(this)) { + "HH:mm" + } else { + "h:mm a" + } + + val formattedTime = SimpleDateFormat(pattern, Locale.getDefault()).format(calendar.time) + result.invoke("$dayOfWeek $formattedTime") + } } -suspend fun Context.getEnabledAlarms(): List = withContext(Dispatchers.IO) { - return@withContext dbHelper.getEnabledAlarms() +fun Context.getEnabledAlarms(enabledAlarms: (List?) -> Unit) { + ensureBackgroundThreadWithResult( + task = { + dbHelper.getEnabledAlarms() + }, + callback = { alarms -> + enabledAlarms.invoke(alarms) + }, + onError = { + enabledAlarms.invoke(null) + } + ) } fun Context.rescheduleEnabledAlarms() { diff --git a/app/src/main/kotlin/com/simplemobiletools/clock/fragments/AlarmFragment.kt b/app/src/main/kotlin/com/simplemobiletools/clock/fragments/AlarmFragment.kt index 6bf85826..14748b3f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/clock/fragments/AlarmFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/clock/fragments/AlarmFragment.kt @@ -21,6 +21,7 @@ import com.simplemobiletools.commons.extensions.getProperTextColor import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.extensions.updateTextColors import com.simplemobiletools.commons.helpers.SORT_BY_DATE_CREATED +import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.models.AlarmSound import kotlinx.android.synthetic.main.fragment_alarm.view.* import kotlinx.coroutines.Dispatchers @@ -91,12 +92,14 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface { it.timeInMinutes }) } - lifecycleScope.launch(Dispatchers.IO) { - if (context?.getEnabledAlarms()?.isEmpty() == true) { + context?.getEnabledAlarms { enabledAlarms -> + if (enabledAlarms.isNullOrEmpty()) { alarms.forEach { if (it.days == TODAY_BIT && it.isEnabled && it.timeInMinutes <= getCurrentDayMinutes()) { it.isEnabled = false - context?.dbHelper?.updateAlarmEnabledState(it.id, false) + ensureBackgroundThread { + context?.dbHelper?.updateAlarmEnabledState(it.id, false) + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/clock/fragments/ClockFragment.kt b/app/src/main/kotlin/com/simplemobiletools/clock/fragments/ClockFragment.kt index 0bfa1b8c..f7e76cf5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/clock/fragments/ClockFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/clock/fragments/ClockFragment.kt @@ -7,7 +7,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope import com.simplemobiletools.clock.R import com.simplemobiletools.clock.activities.SimpleActivity import com.simplemobiletools.clock.adapters.TimeZonesAdapter @@ -19,10 +18,7 @@ import com.simplemobiletools.clock.models.MyTimeZone import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.getProperTextColor import com.simplemobiletools.commons.extensions.updateTextColors -import kotlinx.android.synthetic.main.fragment_clock.* import kotlinx.android.synthetic.main.fragment_clock.view.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import java.util.* class ClockFragment : Fragment() { @@ -116,8 +112,7 @@ class ClockFragment : Fragment() { fun updateAlarm() { view.apply { - lifecycleScope.launch { - val nextAlarm = requireContext().getClosestEnabledAlarmString() + requireContext().getClosestEnabledAlarmString { nextAlarm -> clock_alarm.beVisibleIf(nextAlarm.isNotEmpty()) clock_alarm.text = nextAlarm clock_alarm.colorCompoundDrawable(requireContext().getProperTextColor()) diff --git a/app/src/main/kotlin/com/simplemobiletools/clock/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/clock/helpers/Constants.kt index 073a8770..df5028bf 100644 --- a/app/src/main/kotlin/com/simplemobiletools/clock/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/clock/helpers/Constants.kt @@ -1,13 +1,12 @@ package com.simplemobiletools.clock.helpers -import android.content.BroadcastReceiver +import android.os.Handler +import android.os.Looper import com.simplemobiletools.clock.models.MyTimeZone -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.launch +import com.simplemobiletools.commons.helpers.isOnMainThread import java.util.* -import kotlin.coroutines.CoroutineContext +import java.util.concurrent.Callable +import java.util.concurrent.Executors import kotlin.math.pow // shared preferences @@ -243,16 +242,39 @@ fun getTimeDifferenceInMinutes(currentTimeInMinutes: Int, alarmTimeInMinutes: In } } -fun BroadcastReceiver.goAsync( - context: CoroutineContext = (Dispatchers.Main.immediate + SupervisorJob()), - block: suspend CoroutineScope.() -> Unit +/** + * Runs tasks that you want on a background thread and returns the result or error + * @param task: Callable to add code that should execute on a background thread + * @param callback: Gives back the result from the task after it has completed executing on the Main thread + * @param onError: Gives the error thrown if any by the Callable on the Main thread + */ +fun ensureBackgroundThreadWithResult( + task: Callable, + callback: (T) -> Unit, + onError: ((Throwable) -> Unit)? = null ) { - val pendingResult = goAsync() - CoroutineScope(SupervisorJob()).launch(context) { + val executor = if (isOnMainThread()) { + Executors.newSingleThreadExecutor() + } else { + Executors.newFixedThreadPool(1) + } + + val handler = Handler(Looper.getMainLooper()) + + val future = executor.submit(task) + + executor.submit { try { - block() + val result = future.get() + handler.post { + callback(result) + } + } catch (t: Throwable) { + handler.post { + onError?.invoke(t) + } } finally { - pendingResult.finish() + executor.shutdown() } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/clock/helpers/MyDigitalTimeWidgetProvider.kt b/app/src/main/kotlin/com/simplemobiletools/clock/helpers/MyDigitalTimeWidgetProvider.kt index 5f96184b..c2e7125d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/clock/helpers/MyDigitalTimeWidgetProvider.kt +++ b/app/src/main/kotlin/com/simplemobiletools/clock/helpers/MyDigitalTimeWidgetProvider.kt @@ -42,17 +42,10 @@ class MyDigitalTimeWidgetProvider : AppWidgetProvider() { } private fun updateTexts(context: Context, views: RemoteViews) { - CoroutineScope(Dispatchers.IO).launch { - try { - val nextAlarm = context.getClosestEnabledAlarmString() - withContext(Dispatchers.Main.immediate) { - views.apply { - setText(R.id.widget_next_alarm, nextAlarm) - setVisibleIf(R.id.widget_alarm_holder, nextAlarm.isNotEmpty()) - } - } - } finally { - cancel() + context.getClosestEnabledAlarmString { nextAlarm -> + views.apply { + setText(R.id.widget_next_alarm, nextAlarm) + setVisibleIf(R.id.widget_alarm_holder, nextAlarm.isNotEmpty()) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/clock/receivers/EarlyAlarmDismissalReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/clock/receivers/EarlyAlarmDismissalReceiver.kt index 4cfa6e05..0fede3f6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/clock/receivers/EarlyAlarmDismissalReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/clock/receivers/EarlyAlarmDismissalReceiver.kt @@ -14,45 +14,49 @@ import com.simplemobiletools.clock.extensions.getOpenAlarmTabIntent import com.simplemobiletools.clock.helpers.ALARM_ID import com.simplemobiletools.clock.helpers.EARLY_ALARM_DISMISSAL_CHANNEL_ID import com.simplemobiletools.clock.helpers.EARLY_ALARM_NOTIF_ID -import com.simplemobiletools.clock.helpers.goAsync import com.simplemobiletools.commons.helpers.isOreoPlus class EarlyAlarmDismissalReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) = goAsync { + override fun onReceive(context: Context, intent: Intent) { val alarmId = intent.getIntExtra(ALARM_ID, -1) if (alarmId == -1) { - return@goAsync + return } triggerEarlyDismissalNotification(context, alarmId) } - private suspend fun triggerEarlyDismissalNotification(context: Context, alarmId: Int) { - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (isOreoPlus()) { - NotificationChannel(EARLY_ALARM_DISMISSAL_CHANNEL_ID, context.getString(R.string.early_alarm_dismissal), NotificationManager.IMPORTANCE_DEFAULT).apply { - setBypassDnd(true) - setSound(null, null) - notificationManager.createNotificationChannel(this) + private fun triggerEarlyDismissalNotification(context: Context, alarmId: Int) { + context.getClosestEnabledAlarmString { alarmString -> + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (isOreoPlus()) { + NotificationChannel( + EARLY_ALARM_DISMISSAL_CHANNEL_ID, + context.getString(R.string.early_alarm_dismissal), + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + setBypassDnd(true) + setSound(null, null) + notificationManager.createNotificationChannel(this) + } } + val dismissIntent = context.getDismissAlarmPendingIntent(alarmId, EARLY_ALARM_NOTIF_ID) + val contentIntent = context.getOpenAlarmTabIntent() + val notification = NotificationCompat.Builder(context) + .setContentTitle(context.getString(R.string.upcoming_alarm)) + .setContentText(alarmString) + .setSmallIcon(R.drawable.ic_alarm_vector) + .setPriority(Notification.PRIORITY_LOW) + .addAction(0, context.getString(R.string.dismiss), dismissIntent) + .setContentIntent(contentIntent) + .setSound(null) + .setAutoCancel(true) + .setChannelId(EARLY_ALARM_DISMISSAL_CHANNEL_ID) + .build() + + notificationManager.notify(EARLY_ALARM_NOTIF_ID, notification) } - - val dismissIntent = context.getDismissAlarmPendingIntent(alarmId, EARLY_ALARM_NOTIF_ID) - val contentIntent = context.getOpenAlarmTabIntent() - val notification = NotificationCompat.Builder(context) - .setContentTitle(context.getString(R.string.upcoming_alarm)) - .setContentText(context.getClosestEnabledAlarmString()) - .setSmallIcon(R.drawable.ic_alarm_vector) - .setPriority(Notification.PRIORITY_LOW) - .addAction(0, context.getString(R.string.dismiss), dismissIntent) - .setContentIntent(contentIntent) - .setSound(null) - .setAutoCancel(true) - .setChannelId(EARLY_ALARM_DISMISSAL_CHANNEL_ID) - .build() - - notificationManager.notify(EARLY_ALARM_NOTIF_ID, notification) } }