Merge pull request #436 from Naveen3Singh/dialpad_feedback

Implement setting toggles for vibrations and beeps at dialpad
This commit is contained in:
Tibor Kaputa 2022-09-02 22:57:57 +02:00 committed by GitHub
commit cb4cc7bb48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 238 additions and 34 deletions

View File

@ -18,6 +18,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.telecom.action.CONFIGURE_PHONE_ACCOUNT" /> <uses-permission android:name="android.telecom.action.CONFIGURE_PHONE_ACCOUNT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"

View File

@ -364,7 +364,7 @@ class CallActivity : SimpleActivity() {
} }
private fun dialpadPressed(char: Char) { private fun dialpadPressed(char: Char) {
CallManager.keypad(char) CallManager.keypad(this, char)
dialpad_input.addCharacter(char) dialpad_input.addCharacter(char)
} }

View File

@ -1,17 +1,22 @@
package com.simplemobiletools.dialer.activities package com.simplemobiletools.dialer.activities
import android.annotation.SuppressLint
import android.annotation.TargetApi import android.annotation.TargetApi
import android.content.Intent import android.content.Intent
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Telephony.Sms.Intents.SECRET_CODE_ACTION import android.provider.Telephony.Sms.Intents.SECRET_CODE_ACTION
import android.telephony.PhoneNumberUtils import android.telephony.PhoneNumberUtils
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.util.TypedValue import android.util.TypedValue
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewConfiguration
import com.reddit.indicatorfastscroll.FastScrollItemIndicator import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
@ -19,11 +24,14 @@ import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.dialer.R import com.simplemobiletools.dialer.R
import com.simplemobiletools.dialer.adapters.ContactsAdapter import com.simplemobiletools.dialer.adapters.ContactsAdapter
import com.simplemobiletools.dialer.extensions.* import com.simplemobiletools.dialer.extensions.*
import com.simplemobiletools.dialer.helpers.ToneGeneratorHelper
import com.simplemobiletools.dialer.models.SpeedDial import com.simplemobiletools.dialer.models.SpeedDial
import kotlinx.android.synthetic.main.activity_dialpad.* import kotlinx.android.synthetic.main.activity_dialpad.*
import kotlinx.android.synthetic.main.activity_dialpad.dialpad_holder import kotlinx.android.synthetic.main.activity_dialpad.dialpad_holder
import kotlinx.android.synthetic.main.dialpad.* import kotlinx.android.synthetic.main.dialpad.*
import java.util.* import java.util.*
import kotlin.math.roundToInt
class DialpadActivity : SimpleActivity() { class DialpadActivity : SimpleActivity() {
private var allContacts = ArrayList<SimpleContact>() private var allContacts = ArrayList<SimpleContact>()
@ -31,6 +39,10 @@ class DialpadActivity : SimpleActivity() {
private val russianCharsMap = HashMap<Char, Int>() private val russianCharsMap = HashMap<Char, Int>()
private var hasRussianLocale = false private var hasRussianLocale = false
private var privateCursor: Cursor? = null private var privateCursor: Cursor? = null
private var toneGeneratorHelper: ToneGeneratorHelper? = null
private val longPressTimeout = ViewConfiguration.getLongPressTimeout().toLong()
private val longPressHandler = Handler(Looper.getMainLooper())
private val pressedKeys = mutableSetOf<Char>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -45,6 +57,8 @@ class DialpadActivity : SimpleActivity() {
speedDialValues = config.getSpeedDialValues() speedDialValues = config.getSpeedDialValues()
privateCursor = getMyContactsCursor(false, true) privateCursor = getMyContactsCursor(false, true)
toneGeneratorHelper = ToneGeneratorHelper(this)
if (hasRussianLocale) { if (hasRussianLocale) {
initRussianChars() initRussianChars()
dialpad_2_letters.append("\nАБВГ") dialpad_2_letters.append("\nАБВГ")
@ -65,30 +79,19 @@ class DialpadActivity : SimpleActivity() {
} }
} }
dialpad_0_holder.setOnClickListener { dialpadPressed('0', it) } setupCharClick(dialpad_1_holder, '1')
dialpad_1_holder.setOnClickListener { dialpadPressed('1', it) } setupCharClick(dialpad_2_holder, '2')
dialpad_2_holder.setOnClickListener { dialpadPressed('2', it) } setupCharClick(dialpad_3_holder, '3')
dialpad_3_holder.setOnClickListener { dialpadPressed('3', it) } setupCharClick(dialpad_4_holder, '4')
dialpad_4_holder.setOnClickListener { dialpadPressed('4', it) } setupCharClick(dialpad_5_holder, '5')
dialpad_5_holder.setOnClickListener { dialpadPressed('5', it) } setupCharClick(dialpad_6_holder, '6')
dialpad_6_holder.setOnClickListener { dialpadPressed('6', it) } setupCharClick(dialpad_7_holder, '7')
dialpad_7_holder.setOnClickListener { dialpadPressed('7', it) } setupCharClick(dialpad_8_holder, '8')
dialpad_8_holder.setOnClickListener { dialpadPressed('8', it) } setupCharClick(dialpad_9_holder, '9')
dialpad_9_holder.setOnClickListener { dialpadPressed('9', it) } setupCharClick(dialpad_0_holder, '0')
setupCharClick(dialpad_asterisk_holder, '*', longClickable = false)
setupCharClick(dialpad_hashtag_holder, '#', longClickable = false)
dialpad_1_holder.setOnLongClickListener { speedDial(1); true }
dialpad_2_holder.setOnLongClickListener { speedDial(2); true }
dialpad_3_holder.setOnLongClickListener { speedDial(3); true }
dialpad_4_holder.setOnLongClickListener { speedDial(4); true }
dialpad_5_holder.setOnLongClickListener { speedDial(5); true }
dialpad_6_holder.setOnLongClickListener { speedDial(6); true }
dialpad_7_holder.setOnLongClickListener { speedDial(7); true }
dialpad_8_holder.setOnLongClickListener { speedDial(8); true }
dialpad_9_holder.setOnLongClickListener { speedDial(9); true }
dialpad_0_holder.setOnLongClickListener { dialpadPressed('+', null); true }
dialpad_asterisk_holder.setOnClickListener { dialpadPressed('*', it) }
dialpad_hashtag_holder.setOnClickListener { dialpadPressed('#', it) }
dialpad_clear_char.setOnClickListener { clearChar(it) } dialpad_clear_char.setOnClickListener { clearChar(it) }
dialpad_clear_char.setOnLongClickListener { clearInput(); true } dialpad_clear_char.setOnLongClickListener { clearInput(); true }
dialpad_call_button.setOnClickListener { initCall(dialpad_input.value, 0) } dialpad_call_button.setOnClickListener { initCall(dialpad_input.value, 0) }
@ -164,12 +167,12 @@ class DialpadActivity : SimpleActivity() {
private fun dialpadPressed(char: Char, view: View?) { private fun dialpadPressed(char: Char, view: View?) {
dialpad_input.addCharacter(char) dialpad_input.addCharacter(char)
view?.performHapticFeedback() maybePerformDialpadHapticFeedback(view)
} }
private fun clearChar(view: View) { private fun clearChar(view: View) {
dialpad_input.dispatchKeyEvent(dialpad_input.getKeyEvent(KeyEvent.KEYCODE_DEL)) dialpad_input.dispatchKeyEvent(dialpad_input.getKeyEvent(KeyEvent.KEYCODE_DEL))
view.performHapticFeedback() maybePerformDialpadHapticFeedback(view)
} }
private fun clearInput() { private fun clearInput() {
@ -270,13 +273,15 @@ class DialpadActivity : SimpleActivity() {
} }
} }
private fun speedDial(id: Int) { private fun speedDial(id: Int): Boolean {
if (dialpad_input.value.isEmpty()) { if (dialpad_input.value.length == 1) {
val speedDial = speedDialValues.firstOrNull { it.id == id } val speedDial = speedDialValues.firstOrNull { it.id == id }
if (speedDial?.isValid() == true) { if (speedDial?.isValid() == true) {
initCall(speedDial.number, -1) initCall(speedDial.number, -1)
return true
} }
} }
return false
} }
private fun initRussianChars() { private fun initRussianChars() {
@ -289,4 +294,77 @@ class DialpadActivity : SimpleActivity() {
russianCharsMap['ш'] = 8; russianCharsMap['щ'] = 8; russianCharsMap['ъ'] = 8; russianCharsMap['ы'] = 8 russianCharsMap['ш'] = 8; russianCharsMap['щ'] = 8; russianCharsMap['ъ'] = 8; russianCharsMap['ы'] = 8
russianCharsMap['ь'] = 9; russianCharsMap['э'] = 9; russianCharsMap['ю'] = 9; russianCharsMap['я'] = 9 russianCharsMap['ь'] = 9; russianCharsMap['э'] = 9; russianCharsMap['ю'] = 9; russianCharsMap['я'] = 9
} }
private fun startDialpadTone(char: Char) {
if (config.dialpadBeeps) {
pressedKeys.add(char)
toneGeneratorHelper?.startTone(char)
}
}
private fun stopDialpadTone(char: Char) {
if (config.dialpadBeeps) {
if (!pressedKeys.remove(char)) return
if (pressedKeys.isEmpty()) {
toneGeneratorHelper?.stopTone()
} else {
startDialpadTone(pressedKeys.last())
}
}
}
private fun maybePerformDialpadHapticFeedback(view: View?) {
if (config.dialpadVibration) {
view?.performHapticFeedback()
}
}
private fun performLongClick(view: View, char: Char) {
if (char == '0') {
clearChar(view)
dialpadPressed('+', view)
} else {
val result = speedDial(char.digitToInt())
if (result) {
stopDialpadTone(char)
clearChar(view)
}
}
}
@SuppressLint("ClickableViewAccessibility")
private fun setupCharClick(view: View, char: Char, longClickable: Boolean = true) {
view.isClickable = true
view.isLongClickable = true
view.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
dialpadPressed(char, view)
startDialpadTone(char)
if (longClickable) {
longPressHandler.removeCallbacksAndMessages(null)
longPressHandler.postDelayed({
performLongClick(view, char)
}, longPressTimeout)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
stopDialpadTone(char)
if (longClickable) {
longPressHandler.removeCallbacksAndMessages(null)
}
}
MotionEvent.ACTION_MOVE -> {
val viewContainsTouchEvent = view.boundingBox.contains(event.rawX.roundToInt(), event.rawY.roundToInt())
if (!viewContainsTouchEvent) {
stopDialpadTone(char)
if (longClickable) {
longPressHandler.removeCallbacksAndMessages(null)
}
}
}
}
false
}
}
} }

View File

@ -39,6 +39,8 @@ class SettingsActivity : SimpleActivity() {
setupDialPadOpen() setupDialPadOpen()
setupGroupSubsequentCalls() setupGroupSubsequentCalls()
setupStartNameWithSurname() setupStartNameWithSurname()
setupDialpadVibrations()
setupDialpadBeeps()
setupShowCallConfirmation() setupShowCallConfirmation()
setupDisableProximitySensor() setupDisableProximitySensor()
setupDisableSwipeToAnswer() setupDisableSwipeToAnswer()
@ -206,6 +208,22 @@ class SettingsActivity : SimpleActivity() {
} }
} }
private fun setupDialpadVibrations() {
settings_dialpad_vibration.isChecked = config.dialpadVibration
settings_dialpad_vibration_holder.setOnClickListener {
settings_dialpad_vibration.toggle()
config.dialpadVibration = settings_dialpad_vibration.isChecked
}
}
private fun setupDialpadBeeps() {
settings_dialpad_beeps.isChecked = config.dialpadBeeps
settings_dialpad_beeps_holder.setOnClickListener {
settings_dialpad_beeps.toggle()
config.dialpadBeeps = settings_dialpad_beeps.isChecked
}
}
private fun setupShowCallConfirmation() { private fun setupShowCallConfirmation() {
settings_show_call_confirmation.isChecked = config.showCallConfirmation settings_show_call_confirmation.isChecked = config.showCallConfirmation
settings_show_call_confirmation_holder.setOnClickListener { settings_show_call_confirmation_holder.setOnClickListener {

View File

@ -0,0 +1,8 @@
package com.simplemobiletools.dialer.extensions
import android.graphics.Rect
import android.view.View
val View.boundingBox
get() = Rect().also { getGlobalVisibleRect(it) }

View File

@ -1,9 +1,12 @@
package com.simplemobiletools.dialer.helpers package com.simplemobiletools.dialer.helpers
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.os.Handler
import android.telecom.Call import android.telecom.Call
import android.telecom.InCallService import android.telecom.InCallService
import android.telecom.VideoProfile import android.telecom.VideoProfile
import com.simplemobiletools.dialer.extensions.config
import com.simplemobiletools.dialer.extensions.getStateCompat import com.simplemobiletools.dialer.extensions.getStateCompat
import com.simplemobiletools.dialer.extensions.hasCapability import com.simplemobiletools.dialer.extensions.hasCapability
import com.simplemobiletools.dialer.extensions.isConference import com.simplemobiletools.dialer.extensions.isConference
@ -167,9 +170,15 @@ class CallManager {
fun getState() = getPrimaryCall()?.getStateCompat() fun getState() = getPrimaryCall()?.getStateCompat()
fun keypad(c: Char) { fun keypad(context: Context, char: Char) {
call?.playDtmfTone(c) call?.playDtmfTone(char)
call?.stopDtmfTone() if (context.config.dialpadBeeps) {
Handler().postDelayed({
call?.stopDtmfTone()
}, DIALPAD_TONE_LENGTH_MS)
} else {
call?.stopDtmfTone()
}
} }
} }
} }

View File

@ -71,4 +71,12 @@ class Config(context: Context) : BaseConfig(context) {
var wasOverlaySnackbarConfirmed: Boolean var wasOverlaySnackbarConfirmed: Boolean
get() = prefs.getBoolean(WAS_OVERLAY_SNACKBAR_CONFIRMED, false) get() = prefs.getBoolean(WAS_OVERLAY_SNACKBAR_CONFIRMED, false)
set(wasOverlaySnackbarConfirmed) = prefs.edit().putBoolean(WAS_OVERLAY_SNACKBAR_CONFIRMED, wasOverlaySnackbarConfirmed).apply() set(wasOverlaySnackbarConfirmed) = prefs.edit().putBoolean(WAS_OVERLAY_SNACKBAR_CONFIRMED, wasOverlaySnackbarConfirmed).apply()
var dialpadVibration: Boolean
get() = prefs.getBoolean(DIALPAD_VIBRATION, true)
set(dialpadVibration) = prefs.edit().putBoolean(DIALPAD_VIBRATION, dialpadVibration).apply()
var dialpadBeeps: Boolean
get() = prefs.getBoolean(DIALPAD_BEEPS, false)
set(dialpadBeeps) = prefs.edit().putBoolean(DIALPAD_BEEPS, dialpadBeeps).apply()
} }

View File

@ -1,8 +1,8 @@
package com.simplemobiletools.dialer.helpers package com.simplemobiletools.dialer.helpers
import com.simplemobiletools.commons.helpers.TAB_CALL_HISTORY
import com.simplemobiletools.commons.helpers.TAB_CONTACTS import com.simplemobiletools.commons.helpers.TAB_CONTACTS
import com.simplemobiletools.commons.helpers.TAB_FAVORITES import com.simplemobiletools.commons.helpers.TAB_FAVORITES
import com.simplemobiletools.commons.helpers.TAB_CALL_HISTORY
// shared prefs // shared prefs
const val SPEED_DIAL = "speed_dial" const val SPEED_DIAL = "speed_dial"
@ -15,7 +15,8 @@ const val SHOW_TABS = "show_tabs"
const val FAVORITES_CONTACTS_ORDER = "favorites_contacts_order" const val FAVORITES_CONTACTS_ORDER = "favorites_contacts_order"
const val FAVORITES_CUSTOM_ORDER_SELECTED = "favorites_custom_order_selected" const val FAVORITES_CUSTOM_ORDER_SELECTED = "favorites_custom_order_selected"
const val WAS_OVERLAY_SNACKBAR_CONFIRMED = "was_overlay_snackbar_confirmed" const val WAS_OVERLAY_SNACKBAR_CONFIRMED = "was_overlay_snackbar_confirmed"
const val DIALPAD_VIBRATION = "dialpad_vibration"
const val DIALPAD_BEEPS = "dialpad_beeps"
const val ALL_TABS_MASK = TAB_CONTACTS or TAB_FAVORITES or TAB_CALL_HISTORY const val ALL_TABS_MASK = TAB_CONTACTS or TAB_FAVORITES or TAB_CALL_HISTORY
val tabsList = arrayListOf(TAB_CONTACTS, TAB_FAVORITES, TAB_CALL_HISTORY) val tabsList = arrayListOf(TAB_CONTACTS, TAB_FAVORITES, TAB_CALL_HISTORY)
@ -23,3 +24,5 @@ val tabsList = arrayListOf(TAB_CONTACTS, TAB_FAVORITES, TAB_CALL_HISTORY)
private const val PATH = "com.simplemobiletools.dialer.action." private const val PATH = "com.simplemobiletools.dialer.action."
const val ACCEPT_CALL = PATH + "accept_call" const val ACCEPT_CALL = PATH + "accept_call"
const val DECLINE_CALL = PATH + "decline_call" const val DECLINE_CALL = PATH + "decline_call"
const val DIALPAD_TONE_LENGTH_MS = 150L // The length of DTMF tones in milliseconds

View File

@ -0,0 +1,47 @@
package com.simplemobiletools.dialer.helpers
import android.content.Context
import android.media.AudioManager
import android.media.AudioManager.STREAM_DTMF
import android.media.ToneGenerator
class ToneGeneratorHelper(context: Context) {
private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private val toneGenerator = ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME)
private fun isSilent(): Boolean {
return audioManager.ringerMode in arrayOf(AudioManager.RINGER_MODE_SILENT, AudioManager.RINGER_MODE_VIBRATE)
}
fun startTone(char: Char) {
startTone(charToTone[char] ?: -1)
}
private fun startTone(tone: Int) {
if (tone != -1 && !isSilent()) {
toneGenerator.startTone(tone)
}
}
fun stopTone() = toneGenerator.stopTone()
companion object {
const val TONE_RELATIVE_VOLUME = 80 // The DTMF tone volume relative to other sounds in the stream
const val DIAL_TONE_STREAM_TYPE = STREAM_DTMF
private val charToTone = HashMap<Char, Int>().apply {
put('0', ToneGenerator.TONE_DTMF_0)
put('1', ToneGenerator.TONE_DTMF_1)
put('2', ToneGenerator.TONE_DTMF_2)
put('3', ToneGenerator.TONE_DTMF_3)
put('4', ToneGenerator.TONE_DTMF_4)
put('5', ToneGenerator.TONE_DTMF_5)
put('6', ToneGenerator.TONE_DTMF_6)
put('7', ToneGenerator.TONE_DTMF_7)
put('8', ToneGenerator.TONE_DTMF_8)
put('9', ToneGenerator.TONE_DTMF_9)
put('#', ToneGenerator.TONE_DTMF_P)
put('*', ToneGenerator.TONE_DTMF_S)
}
}
}

View File

@ -190,7 +190,7 @@
style="@style/SettingsHolderCheckboxStyle" style="@style/SettingsHolderCheckboxStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/ripple_bottom_corners"> android:background="@drawable/ripple_background">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox <com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/settings_start_name_with_surname" android:id="@+id/settings_start_name_with_surname"
@ -200,6 +200,38 @@
android:text="@string/start_name_with_surname" /> android:text="@string/start_name_with_surname" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/settings_dialpad_vibration_holder"
style="@style/SettingsHolderCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ripple_background">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/settings_dialpad_vibration"
style="@style/SettingsCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialpad_vibrations" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_dialpad_beeps_holder"
style="@style/SettingsHolderCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ripple_bottom_corners">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/settings_dialpad_beeps"
style="@style/SettingsCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialpad_beeps" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
<TextView <TextView