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

First pass at adding custom vibration patterns #1742

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ interface NotificationManager {

fun createNotificationChannel(threadId: Long = 0L)

fun replaceNotificationChannel(threadId: Long = 0L)

fun buildNotificationChannelId(threadId: Long): String

fun getNotificationForBackup(): NotificationCompat.Builder
Expand Down
18 changes: 18 additions & 0 deletions domain/src/main/java/com/moez/QKSMS/util/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,22 @@ class Preferences @Inject constructor(
else -> rxPrefs.getString("ringtone_$threadId", default.get())
}
}

fun vibratePattern(threadId: Long = 0): Preference<Int> {
val default = rxPrefs.getInteger("vibrate_pattern", 0)

return when (threadId) {
0L -> default
else -> rxPrefs.getInteger("vibrate_pattern_$threadId", default.get())
}
}

fun notificationChannelCount(threadId: Long = 0): Preference<Int> {
val suffix = if (threadId == 0L) "" else "_$threadId"
return rxPrefs.getInteger("notification_channel_count$suffix", 0)
// return when (threadId) {
// 0L -> rxPrefs.getInteger("notification_channel_count", 0)
// else -> rxPrefs.getInteger("notification_channel_count_$threadId", 0)
// }
}
}
2 changes: 1 addition & 1 deletion presentation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ android {
flavorDimensions "analytics"

defaultConfig {
applicationId "com.moez.QKSMS"
applicationId "com.moez.QKSMSDev"
minSdkVersion 21
targetSdkVersion 29
versionCode 2213
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.ContactsContract
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
Expand Down Expand Up @@ -80,6 +81,18 @@ class NotificationManagerImpl @Inject constructor(

private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

private fun getVibratePattern(threadId: Long):LongArray {
return(when(prefs.vibratePattern(threadId).get()) {
0 -> longArrayOf(0, 400)
1 -> longArrayOf(0, 200)
2 -> longArrayOf(0, 400, 200, 400)
3 -> longArrayOf(0, 200, 200, 200)
4 -> longArrayOf(0, 400, 200, 200)
5 -> longArrayOf(0, 200, 200, 400)
else -> longArrayOf(0, 400)
})
}

init {
// Make sure the default channel has been initialized
createNotificationChannel()
Expand Down Expand Up @@ -141,7 +154,7 @@ class NotificationManagerImpl @Inject constructor(
.setSound(ringtone)
.setLights(Color.WHITE, 500, 2000)
.setWhen(conversation.lastMessage?.date ?: System.currentTimeMillis())
.setVibrate(if (prefs.vibration(threadId).get()) VIBRATE_PATTERN else longArrayOf(0))
.setVibrate(if (prefs.vibration(threadId).get()) getVibratePattern(threadId) else longArrayOf(0))

// Tell the notification if it's a group message
val messagingStyle = NotificationCompat.MessagingStyle("Me")
Expand Down Expand Up @@ -349,7 +362,7 @@ class NotificationManagerImpl @Inject constructor(
.setContentIntent(contentPI)
.setSound(Uri.parse(prefs.ringtone(threadId).get()))
.setLights(Color.WHITE, 500, 2000)
.setVibrate(if (prefs.vibration(threadId).get()) VIBRATE_PATTERN else longArrayOf(0))
.setVibrate(if (prefs.vibration(threadId).get()) getVibratePattern(threadId) else longArrayOf(0))

notificationManager.notify(threadId.toInt() + 100000, notification.build())
}
Expand Down Expand Up @@ -387,11 +400,12 @@ class NotificationManagerImpl @Inject constructor(
}

val channel = when (threadId) {
0L -> NotificationChannel(DEFAULT_CHANNEL_ID, "Default", NotificationManager.IMPORTANCE_HIGH).apply {
0L -> NotificationChannel(buildNotificationChannelId(0L),
"Default", NotificationManager.IMPORTANCE_HIGH).apply {
enableLights(true)
lightColor = Color.WHITE
enableVibration(true)
vibrationPattern = VIBRATE_PATTERN
vibrationPattern = getVibratePattern(threadId)
}

else -> {
Expand All @@ -402,7 +416,7 @@ class NotificationManagerImpl @Inject constructor(
enableLights(true)
lightColor = Color.WHITE
enableVibration(true)
vibrationPattern = VIBRATE_PATTERN
vibrationPattern = getVibratePattern(threadId)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
setSound(prefs.ringtone().get().let(Uri::parse), AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
Expand All @@ -415,6 +429,46 @@ class NotificationManagerImpl @Inject constructor(
notificationManager.createNotificationChannel(channel)
}

/**
* Removes the existing notification channel and replaces it, as this is the only way to
* change a setting
*/

override fun replaceNotificationChannel(threadId: Long) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return

// Get the existing channel, if it exists
val oldChannelId = buildNotificationChannelId(threadId)
val oldChannel = notificationManager.notificationChannels.find { channel -> channel.id == oldChannelId }
if (oldChannel != null) {
// Increment the channel count so that we always make a new, unique channel ID
prefs.notificationChannelCount(threadId).set(prefs.notificationChannelCount(threadId).get() + 1)
// Make a get a new channel id with the incremented count
val newChannelId = buildNotificationChannelId(threadId)
// Make a new channel with all the same properties as the old
// Replace channelId and Vibration with new values
// lockscreenVisibility and setBypassDnd can not be set at all, so will be reset by Android
val newChannel = NotificationChannel(newChannelId, oldChannel.name, oldChannel.importance)
newChannel.vibrationPattern = getVibratePattern(threadId)

newChannel.enableLights(oldChannel.shouldShowLights())
newChannel.enableVibration(oldChannel.shouldVibrate())
// newChannel.lockscreenVisibility = oldChannel.lockscreenVisibility
newChannel.setSound(oldChannel.sound, oldChannel.audioAttributes)
newChannel.setAllowBubbles(oldChannel.canBubble())
// newChannel.setBypassDnd(oldChannel.canBypassDnd())
newChannel.setShowBadge(oldChannel.canShowBadge())

notificationManager.deleteNotificationChannel(oldChannelId)
notificationManager.createNotificationChannel(newChannel)

}
else {
// Call the standard create function if the channel doesn't already exist
createNotificationChannel(threadId)
}
}

/**
* Returns the notification channel for the given conversation, or null if it doesn't exist
*/
Expand All @@ -437,20 +491,29 @@ class NotificationManagerImpl @Inject constructor(
*/
private fun getChannelIdForNotification(threadId: Long): String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return getNotificationChannel(threadId)?.id ?: DEFAULT_CHANNEL_ID
return getNotificationChannel(threadId)?.id ?: buildNotificationChannelId(0L)
}

return DEFAULT_CHANNEL_ID
return buildNotificationChannelId(0L)
}

/**
* Formats a notification channel id for a given thread id, whether the channel exists or not
*/
override fun buildNotificationChannelId(threadId: Long): String {
return when (threadId) {
0L -> DEFAULT_CHANNEL_ID
else -> "notifications_$threadId"
val channelID = when (threadId) {
// Add an incrementing number to the end of the channel
// In order to support previous channels that didn't have the appended 0, only append
// if count is > 0
0L -> { val channelCount = prefs.notificationChannelCount(0L).get()
DEFAULT_CHANNEL_ID + if (channelCount == 0) "" else "_$channelCount"
}
else -> { val channelCount = prefs.notificationChannelCount(threadId).get()
"notifications_$threadId" + if (channelCount == 0) "" else "_$channelCount"
}
}
// Log.i("NotificationManagerImpl", "ChannelID: $channelID")
return channelID
}

override fun getNotificationForBackup(): NotificationCompat.Builder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ import javax.inject.Inject
class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView {

@Inject lateinit var previewModeDialog: QkDialog
@Inject lateinit var vibrationPatternDialog: QkDialog
@Inject lateinit var actionsDialog: QkDialog
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory

override val preferenceClickIntent: Subject<PreferenceView> = PublishSubject.create()
override val previewModeSelectedIntent by lazy { previewModeDialog.adapter.menuItemClicks }
override val ringtoneSelectedIntent: Subject<String> = PublishSubject.create()
override val vibratePatternIntent by lazy { vibrationPatternDialog.adapter.menuItemClicks }
override val actionsSelectedIntent by lazy { actionsDialog.adapter.menuItemClicks }

private val binding by viewBinding(NotificationPrefsActivityBinding::inflate)
Expand All @@ -80,6 +82,7 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView {

previewModeDialog.setTitle(R.string.settings_notification_previews_title)
previewModeDialog.adapter.setData(R.array.notification_preview_options)
vibrationPatternDialog.adapter.setData(R.array.settings_vibrate_patterns)
actionsDialog.adapter.setData(R.array.notification_actions)

// Listen to clicks for all of the preferences
Expand All @@ -103,6 +106,7 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView {
binding.wake.widget<QkSwitch>().isChecked = state.wakeEnabled
binding.vibration.widget<QkSwitch>().isChecked = state.vibrationEnabled
binding.ringtone.summary = state.ringtoneName
binding.vibratePattern.summary = state.vibratePatternSummary

binding.actionsDivider.isVisible = state.threadId == 0L
binding.actionsTitle.isVisible = state.threadId == 0L
Expand Down Expand Up @@ -133,6 +137,11 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView {
startActivityForResult(intent, 123)
}

override fun showVibratePatternDialog(selected: Int) {
vibrationPatternDialog.adapter.selectedItem = selected
vibrationPatternDialog.show(this)
}

override fun showActionDialog(selected: Int) {
actionsDialog.adapter.selectedItem = selected
actionsDialog.show(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ data class NotificationPrefsState(
val action3Summary: String = "",
val vibrationEnabled: Boolean = true,
val ringtoneName: String = "",
val vibratePatternSummary: String = "",
val qkReplyEnabled: Boolean = Build.VERSION.SDK_INT < Build.VERSION_CODES.N,
val qkReplyTapDismiss: Boolean = true
)
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ interface NotificationPrefsView : QkView<NotificationPrefsState> {
val preferenceClickIntent: Subject<PreferenceView>
val previewModeSelectedIntent: Subject<Int>
val ringtoneSelectedIntent: Observable<String>
val vibratePatternIntent: Subject<Int>
val actionsSelectedIntent: Subject<Int>

fun showPreviewModeDialog()
fun showRingtonePicker(default: Uri?)
fun showVibratePatternDialog(selected: Int)
fun showActionDialog(selected: Int)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.net.Uri
import com.moez.QKSMS.R
import com.moez.QKSMS.common.Navigator
import com.moez.QKSMS.common.base.QkViewModel
import com.moez.QKSMS.common.util.NotificationManagerImpl
import com.moez.QKSMS.extensions.mapNotNull
import com.moez.QKSMS.repository.ConversationRepository
import com.moez.QKSMS.util.Preferences
Expand All @@ -41,6 +42,7 @@ class NotificationPrefsViewModel @Inject constructor(
private val context: Context,
private val conversationRepo: ConversationRepository,
private val navigator: Navigator,
private val notificationManagerImpl: NotificationManagerImpl,
private val prefs: Preferences
) : QkViewModel<NotificationPrefsView, NotificationPrefsState>(NotificationPrefsState(threadId = threadId)) {

Expand All @@ -49,6 +51,7 @@ class NotificationPrefsViewModel @Inject constructor(
private val wake = prefs.wakeScreen(threadId)
private val vibration = prefs.vibration(threadId)
private val ringtone = prefs.ringtone(threadId)
private val vibratePattern = prefs.vibratePattern(threadId)

init {
disposables += Flowable.just(threadId)
Expand Down Expand Up @@ -91,6 +94,10 @@ class NotificationPrefsViewModel @Inject constructor(
}
.subscribe { title -> newState { copy(ringtoneName = title) } }

val vibratePatternLabels = context.resources.getStringArray(R.array.settings_vibrate_patterns)
disposables += vibratePattern.asObservable()
.subscribe { previewId -> newState { copy(vibratePatternSummary = vibratePatternLabels[previewId]) } }

disposables += prefs.qkreply.asObservable()
.subscribe { enabled -> newState { copy(qkReplyEnabled = enabled) } }

Expand All @@ -117,6 +124,8 @@ class NotificationPrefsViewModel @Inject constructor(

R.id.ringtone -> view.showRingtonePicker(ringtone.get().takeIf { it.isNotEmpty() }?.let(Uri::parse))

R.id.vibrate_pattern -> view.showVibratePatternDialog(prefs.vibratePattern(threadId).get())

R.id.action1 -> view.showActionDialog(prefs.notifAction1.get())

R.id.action2 -> view.showActionDialog(prefs.notifAction2.get())
Expand All @@ -137,6 +146,11 @@ class NotificationPrefsViewModel @Inject constructor(
.autoDisposable(view.scope())
.subscribe { ringtone -> this.ringtone.set(ringtone) }

view.vibratePatternIntent
.autoDisposable(view.scope())
.subscribe { vibratePattern.set(it)
notificationManagerImpl.replaceNotificationChannel(threadId)}

view.actionsSelectedIntent
.withLatestFrom(view.preferenceClickIntent) { action, preference ->
when (preference.id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@
android:layout_height="wrap_content"
app:title="@string/settings_ringtone_title" />

<com.moez.QKSMS.common.widget.PreferenceView
android:id="@+id/vibrate_pattern"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="@string/settings_vibrate_pattern_title" />

<View
android:id="@+id/actionsDivider"
android:layout_width="match_parent"
Expand Down
11 changes: 10 additions & 1 deletion presentation/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
-->
<resources>

<string name="app_name" translatable="false">QKSMS</string>
<string name="app_name" translatable="false">QKSMSDev</string>

<string name="shortcut_compose_long_label">New conversation</string>
<string name="shortcut_compose_short_label">Compose</string>
Expand Down Expand Up @@ -231,6 +231,15 @@
<string name="settings_vibration_title">Vibration</string>
<string name="settings_ringtone_title">Sound</string>
<string name="settings_ringtone_none">None</string>
<string name="settings_vibrate_pattern_title">Vibrate Pattern</string>
<string-array name="settings_vibrate_patterns">
<item>Long</item>
<item>Short</item>
<item>Long Long</item>
<item>Short Short</item>
<item>Long Short</item>
<item>Short Long</item>
</string-array>
<string name="settings_qkreply_title">QK Reply</string>
<string name="settings_qkreply_summary">Popup for new messages</string>
<string name="settings_qkreply_tap_dismiss_title">Tap to dismiss</string>
Expand Down