Add notification piggyback (#1503)

This commit is contained in:
Rafał Borcz 2021-09-25 14:02:38 +02:00 committed by GitHub
parent de6131f4f5
commit 9211baf7ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 145 additions and 18 deletions

View File

@ -166,7 +166,7 @@ ext {
}
dependencies {
implementation "io.github.wulkanowy:sdk:230d2075df"
implementation "io.github.wulkanowy:sdk:49c2071d10"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'

View File

@ -93,6 +93,13 @@
<service
android:name=".services.widgets.TimetableWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<service
android:name=".services.piggyback.VulcanNotificationListenerService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<receiver
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"

View File

@ -14,7 +14,6 @@ import io.github.wulkanowy.sdk.toLocalDate
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import io.github.wulkanowy.utils.toTimestamp
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -108,6 +107,14 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_notification_upcoming_lessons_enable
)
val isNotificationPiggybackEnabledKey =
context.getString(R.string.pref_key_notifications_piggyback)
val isNotificationPiggybackEnabled: Boolean
get() = getBoolean(
R.string.pref_key_notifications_piggyback,
R.bool.pref_default_notification_piggyback
)
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
val isDebugNotificationEnable: Boolean
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
@ -176,10 +183,8 @@ class PreferencesRepository @Inject constructor(
)
var lasSyncDate: LocalDateTime
get() = getLong(
R.string.pref_key_last_sync_date,
R.string.pref_default_last_sync_date
).toLocalDateTime()
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
.toLocalDateTime()
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
@ -230,8 +235,10 @@ class PreferencesRepository @Inject constructor(
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
var inAppReviewDate: LocalDate?
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
?.toLocalDate()
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
.apply()
var isAppReviewDone: Boolean
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)

View File

@ -0,0 +1,24 @@
package io.github.wulkanowy.services.piggyback
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.services.sync.SyncManager
import javax.inject.Inject
@AndroidEntryPoint
class VulcanNotificationListenerService : NotificationListenerService() {
@Inject
lateinit var syncManager: SyncManager
@Inject
lateinit var preferenceRepository: PreferencesRepository
override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) {
if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) {
syncManager.startOneTimeSyncWorker()
}
}
}

View File

@ -57,14 +57,20 @@ class SyncManager @Inject constructor(
fun startPeriodicSyncWorker(restart: Boolean = false) {
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
val serviceInterval = preferencesRepository.servicesInterval
workManager.enqueueUniquePeriodicWork(
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
.setInitialDelay(10, MINUTES)
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
.setConstraints(Constraints.Builder()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
.build())
.build())
.build()
)
.build()
)
}
}
@ -77,7 +83,11 @@ class SyncManager @Inject constructor(
)
.build()
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
workManager.enqueueUniqueWork(
"${SyncWorker::class.java.simpleName}_one_time",
ExistingWorkPolicy.REPLACE,
work
)
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
}

View File

@ -10,9 +10,12 @@ import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationManagerCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import androidx.recyclerview.widget.RecyclerView
import com.thelittlefireman.appkillermanager.AppKillerManager
import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException
@ -43,6 +46,21 @@ class NotificationsFragment : PreferenceFragmentCompat(),
override val titleStringId get() = R.string.pref_settings_notifications_title
override val isNotificationPermissionGranted: Boolean
get() {
val packageNameList =
NotificationManagerCompat.getEnabledListenerPackages(requireContext())
val appPackageName = requireContext().packageName
return appPackageName in packageNameList
}
private val notificationSettingsContract =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
presenter.onNotificationPermissionResult()
}
override fun initView(showDebugNotificationSwitch: Boolean) {
findPreference<Preference>(getString(R.string.pref_key_notification_debug))?.isVisible =
showDebugNotificationSwitch
@ -57,13 +75,12 @@ class NotificationsFragment : PreferenceFragmentCompat(),
}
}
findPreference<Preference>(getString(R.string.pref_key_notifications_system_settings))?.run {
setOnPreferenceClickListener {
findPreference<Preference>(getString(R.string.pref_key_notifications_system_settings))
?.setOnPreferenceClickListener {
presenter.onOpenSystemSettingsClicked()
true
}
}
}
override fun onCreateRecyclerView(
inflater: LayoutInflater?,
@ -157,6 +174,24 @@ class NotificationsFragment : PreferenceFragmentCompat(),
}
}
override fun openNotificationPermissionDialog() {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.pref_notification_piggyback_popup_title))
.setMessage(getString(R.string.pref_notification_piggyback_popup_description))
.setPositiveButton(getString(R.string.pref_notification_piggyback_popup_positive)) { _, _ ->
notificationSettingsContract.launch(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
setNotificationPiggybackPreferenceChecked(false)
}
.show()
}
override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) {
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_piggyback))?.isChecked =
isChecked
}
override fun onResume() {
super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)

View File

@ -31,6 +31,9 @@ class NotificationsPresenter @Inject constructor(
)
initView(appInfo.isDebug)
}
checkNotificationPiggybackState()
Timber.i("Settings notifications view was initialized")
}
@ -47,6 +50,11 @@ class NotificationsPresenter @Inject constructor(
isDebugNotificationEnableKey -> {
chuckerCollector.showNotification = isDebugNotificationEnable
}
isNotificationPiggybackEnabledKey -> {
if (isNotificationPiggybackEnabled && view?.isNotificationPermissionGranted == false) {
view?.openNotificationPermissionDialog()
}
}
}
}
analytics.logEvent("setting_changed", "name" to key)
@ -59,4 +67,18 @@ class NotificationsPresenter @Inject constructor(
fun onOpenSystemSettingsClicked() {
view?.openSystemSettings()
}
fun onNotificationPermissionResult() {
view?.run {
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
}
}
private fun checkNotificationPiggybackState() {
if (preferencesRepository.isNotificationPiggybackEnabled) {
view?.run {
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
}
}
}
}

View File

@ -4,6 +4,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface NotificationsView : BaseView {
val isNotificationPermissionGranted: Boolean
fun initView(showDebugNotificationSwitch: Boolean)
fun showFixSyncDialog()
@ -11,4 +13,8 @@ interface NotificationsView : BaseView {
fun openSystemSettings()
fun enableNotification(notificationKey: String, enable: Boolean)
fun openNotificationPermissionDialog()
fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean)
}

View File

@ -624,6 +624,10 @@
<string name="pref_notify_fix_sync_issues_settings_button">Przejdź do ustawień</string>
<string name="pref_notify_debug_switch">Pokazuj powiadomienia debugowania</string>
<string name="pref_notify_disabled_summary">Synchronizacja jest wyłączona</string>
<string name="pref_notify_notifications_piggyback">Przechwytywanie powiadomień oficjalnej aplikacji</string>
<string name="pref_notification_piggyback_popup_title">Przechwytywanie powiadomień</string>
<string name="pref_notification_piggyback_popup_description">Dzięki tej funkcji możesz zyskać namiastkę powiadomień push takich, jak w oficjalnej aplikacji. Wystarczy że zezwolisz Wulkanowemu na odbieranie wszystkich powiadomień w ustawieniach systemowych.\n\nJak to działa?\nGdy dostaniesz powiadomienie w Dzienniczku VULCANa Wulkanowy zostanie o tym powiadomiony (po to te dodatkowe uprawnienia) i uruchomi synchronizację, dzięki czemu będzie mógł wysłać własne powiadomienie.\n\nTYLKO DLA ZAAWANSOWANYCH UŻYTKOWNIKÓW</string>
<string name="pref_notification_piggyback_popup_positive">Przejdź do ustawień</string>
<string name="pref_services_header">Synchronizacja</string>
<string name="pref_services_switch">Automatyczna aktualizacja</string>
<string name="pref_services_suspended">Zawieszona na wakacjach</string>

View File

@ -26,6 +26,7 @@
<bool name="pref_default_subjects_without_grades">false</bool>
<bool name="pref_default_optional_arithmetic_average">false</bool>
<string name="pref_default_last_sync_date">0</string>
<bool name="pref_default_notification_piggyback">false</bool>
<string-array name="pref_default_dashboard_tiles">
<item>LUCKY_NUMBER</item>
<item>MESSAGES</item>

View File

@ -32,4 +32,5 @@
<string name="pref_key_message_send_is_draft">message_send_is_draft</string>
<string name="pref_key_message_send_draft">message_send_recipients</string>
<string name="pref_key_last_sync_date">last_sync_date</string>
<string name="pref_key_notifications_piggyback">notifications_piggyback</string>
</resources>

View File

@ -625,6 +625,10 @@
<string name="pref_notify_fix_sync_issues_settings_button">Go to settings</string>
<string name="pref_notify_debug_switch">Show debug notifications</string>
<string name="pref_notify_disabled_summary">Synchronization is disabled</string>
<string name="pref_notify_notifications_piggyback">Capture official app notifications</string>
<string name="pref_notification_piggyback_popup_title">Capture notifications</string>
<string name="pref_notification_piggyback_popup_description">With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY</string>
<string name="pref_notification_piggyback_popup_positive">Go to settings</string>
<string name="pref_services_header">Synchronization</string>
<string name="pref_services_switch">Automatic update</string>

View File

@ -14,6 +14,12 @@
app:key="@string/pref_key_notifications_upcoming_lessons_enable"
app:singleLineTitle="false"
app:title="@string/pref_notify_upcoming_lessons_switch" />
<SwitchPreferenceCompat
app:defaultValue="@bool/pref_default_notification_piggyback"
app:iconSpaceReserved="false"
app:key="@string/pref_key_notifications_piggyback"
app:singleLineTitle="false"
app:title="@string/pref_notify_notifications_piggyback" />
<SwitchPreferenceCompat
app:defaultValue="@bool/pref_default_notification_debug"
app:iconSpaceReserved="false"