Simple-Dialer/app/src/main/kotlin/com/simplemobiletools/dialer/activities/DialpadActivity.kt

454 lines
18 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.simplemobiletools.dialer.activities
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Telephony.Sms.Intents.SECRET_CODE_ACTION
import android.telephony.PhoneNumberUtils
import android.telephony.TelephonyManager
import android.util.TypedValue
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isVisible
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.simplemobiletools.commons.dialogs.CallConfirmationDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.contacts.Contact
import com.simplemobiletools.dialer.R
import com.simplemobiletools.dialer.adapters.ContactsAdapter
import com.simplemobiletools.dialer.databinding.ActivityDialpadBinding
import com.simplemobiletools.dialer.extensions.*
import com.simplemobiletools.dialer.helpers.DIALPAD_TONE_LENGTH_MS
import com.simplemobiletools.dialer.helpers.ToneGeneratorHelper
import com.simplemobiletools.dialer.models.SpeedDial
import java.util.*
import kotlin.math.roundToInt
class DialpadActivity : SimpleActivity() {
private val binding by viewBinding(ActivityDialpadBinding::inflate)
private var allContacts = ArrayList<Contact>()
private var speedDialValues = ArrayList<SpeedDial>()
private val russianCharsMap = HashMap<Char, Int>()
private var hasRussianLocale = false
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?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
hasRussianLocale = Locale.getDefault().language == "ru"
binding.apply {
updateMaterialActivityViews(dialpadCoordinator, dialpadHolder, useTransparentNavigation = true, useTopSearchMenu = false)
setupMaterialScrollListener(dialpadList, dialpadToolbar)
}
updateNavigationBarColor(getProperBackgroundColor())
if (checkAppSideloading()) {
return
}
binding.dialpadWrapper.apply {
if (config.hideDialpadNumbers) {
dialpad1Holder.isVisible = false
dialpad2Holder.isVisible = false
dialpad3Holder.isVisible = false
dialpad4Holder.isVisible = false
dialpad5Holder.isVisible = false
dialpad6Holder.isVisible = false
dialpad7Holder.isVisible = false
dialpad8Holder.isVisible = false
dialpad9Holder.isVisible = false
dialpadPlusHolder.isVisible = true
dialpad0Holder.visibility = View.INVISIBLE
}
arrayOf(
dialpad0Holder,
dialpad1Holder,
dialpad2Holder,
dialpad3Holder,
dialpad4Holder,
dialpad5Holder,
dialpad6Holder,
dialpad7Holder,
dialpad8Holder,
dialpad9Holder,
dialpadPlusHolder,
dialpadAsteriskHolder,
dialpadHashtagHolder
).forEach {
it.background = ResourcesCompat.getDrawable(resources, R.drawable.pill_background, theme)
it.background?.alpha = LOWER_ALPHA_INT
}
}
setupOptionsMenu()
speedDialValues = config.getSpeedDialValues()
privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
toneGeneratorHelper = ToneGeneratorHelper(this, DIALPAD_TONE_LENGTH_MS)
binding.dialpadWrapper.apply {
if (hasRussianLocale) {
initRussianChars()
dialpad2Letters.append("\nАБВГ")
dialpad3Letters.append("\nДЕЁЖЗ")
dialpad4Letters.append("\nИЙКЛ")
dialpad5Letters.append("\nМНОП")
dialpad6Letters.append("\nРСТУ")
dialpad7Letters.append("\nФХЦЧ")
dialpad8Letters.append("\nШЩЪЫ")
dialpad9Letters.append("\nЬЭЮЯ")
val fontSize = resources.getDimension(R.dimen.small_text_size)
arrayOf(
dialpad2Letters, dialpad3Letters, dialpad4Letters, dialpad5Letters, dialpad6Letters, dialpad7Letters, dialpad8Letters,
dialpad9Letters
).forEach {
it.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
}
}
setupCharClick(dialpad1Holder, '1')
setupCharClick(dialpad2Holder, '2')
setupCharClick(dialpad3Holder, '3')
setupCharClick(dialpad4Holder, '4')
setupCharClick(dialpad5Holder, '5')
setupCharClick(dialpad6Holder, '6')
setupCharClick(dialpad7Holder, '7')
setupCharClick(dialpad8Holder, '8')
setupCharClick(dialpad9Holder, '9')
setupCharClick(dialpad0Holder, '0')
setupCharClick(dialpadPlusHolder, '+', longClickable = false)
setupCharClick(dialpadAsteriskHolder, '*', longClickable = false)
setupCharClick(dialpadHashtagHolder, '#', longClickable = false)
}
binding.apply {
dialpadClearChar.setOnClickListener { clearChar(it) }
dialpadClearChar.setOnLongClickListener { clearInput(); true }
dialpadCallButton.setOnClickListener { initCall(dialpadInput.value, 0) }
dialpadInput.onTextChangeListener { dialpadValueChanged(it) }
dialpadInput.requestFocus()
dialpadInput.disableKeyboard()
}
ContactsHelper(this).getContacts(showOnlyContactsWithNumbers = true) { allContacts ->
gotContacts(allContacts)
}
val properPrimaryColor = getProperPrimaryColor()
val callIconId = if (areMultipleSIMsAvailable()) {
val callIcon = resources.getColoredDrawableWithColor(R.drawable.ic_phone_two_vector, properPrimaryColor.getContrastColor())
binding.apply {
dialpadCallTwoButton.setImageDrawable(callIcon)
dialpadCallTwoButton.background.applyColorFilter(properPrimaryColor)
dialpadCallTwoButton.beVisible()
dialpadCallTwoButton.setOnClickListener {
initCall(dialpadInput.value, 1)
}
}
R.drawable.ic_phone_one_vector
} else {
R.drawable.ic_phone_vector
}
binding.apply {
val callIcon = resources.getColoredDrawableWithColor(callIconId, properPrimaryColor.getContrastColor())
dialpadCallButton.setImageDrawable(callIcon)
dialpadCallButton.background.applyColorFilter(properPrimaryColor)
letterFastscroller.textColor = getProperTextColor().getColorStateList()
letterFastscroller.pressedTextColor = properPrimaryColor
letterFastscrollerThumb.setupWithFastScroller(letterFastscroller)
letterFastscrollerThumb.textColor = properPrimaryColor.getContrastColor()
letterFastscrollerThumb.thumbColor = properPrimaryColor.getColorStateList()
}
}
override fun onResume() {
super.onResume()
updateTextColors(binding.dialpadHolder)
binding.dialpadClearChar.applyColorFilter(getProperTextColor())
updateNavigationBarColor(getProperBackgroundColor())
setupToolbar(binding.dialpadToolbar, NavigationIcon.Arrow)
}
private fun setupOptionsMenu() {
binding.dialpadToolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.add_number_to_contact -> addNumberToContact()
else -> return@setOnMenuItemClickListener false
}
return@setOnMenuItemClickListener true
}
}
private fun checkDialIntent(): Boolean {
return if ((intent.action == Intent.ACTION_DIAL || intent.action == Intent.ACTION_VIEW) && intent.data != null && intent.dataString?.contains("tel:") == true) {
val number = Uri.decode(intent.dataString).substringAfter("tel:")
binding.dialpadInput.setText(number)
binding.dialpadInput.setSelection(number.length)
true
} else {
false
}
}
private fun addNumberToContact() {
Intent().apply {
action = Intent.ACTION_INSERT_OR_EDIT
type = "vnd.android.cursor.item/contact"
putExtra(KEY_PHONE, binding.dialpadInput.value)
launchActivityIntent(this)
}
}
private fun dialpadPressed(char: Char, view: View?) {
binding.dialpadInput.addCharacter(char)
maybePerformDialpadHapticFeedback(view)
}
private fun clearChar(view: View) {
binding.dialpadInput.dispatchKeyEvent(binding.dialpadInput.getKeyEvent(KeyEvent.KEYCODE_DEL))
maybePerformDialpadHapticFeedback(view)
}
private fun clearInput() {
binding.dialpadInput.setText("")
}
private fun gotContacts(newContacts: ArrayList<Contact>) {
allContacts = newContacts
val privateContacts = MyContactsContentProvider.getContacts(this, privateCursor)
if (privateContacts.isNotEmpty()) {
allContacts.addAll(privateContacts)
allContacts.sort()
}
runOnUiThread {
if (!checkDialIntent() && binding.dialpadInput.value.isEmpty()) {
dialpadValueChanged("")
}
}
}
@TargetApi(Build.VERSION_CODES.O)
private fun dialpadValueChanged(text: String) {
val len = text.length
if (len > 8 && text.startsWith("*#*#") && text.endsWith("#*#*")) {
val secretCode = text.substring(4, text.length - 4)
if (isOreoPlus()) {
if (isDefaultDialer()) {
getSystemService(TelephonyManager::class.java)?.sendDialerSpecialCode(secretCode)
} else {
launchSetDefaultDialerIntent()
}
} else {
val intent = Intent(SECRET_CODE_ACTION, Uri.parse("android_secret_code://$secretCode"))
sendBroadcast(intent)
}
return
}
(binding.dialpadList.adapter as? ContactsAdapter)?.finishActMode()
val filtered = allContacts.filter {
var convertedName = PhoneNumberUtils.convertKeypadLettersToDigits(it.name.normalizeString())
if (hasRussianLocale) {
var currConvertedName = ""
convertedName.lowercase(Locale.getDefault()).forEach { char ->
val convertedChar = russianCharsMap.getOrElse(char) { char }
currConvertedName += convertedChar
}
convertedName = currConvertedName
}
it.doesContainPhoneNumber(text) || (convertedName.contains(text, true))
}.sortedWith(compareBy {
!it.doesContainPhoneNumber(text)
}).toMutableList() as ArrayList<Contact>
binding.letterFastscroller.setupWithRecyclerView(binding.dialpadList, { position ->
try {
val name = filtered[position].getNameToDisplay()
val character = if (name.isNotEmpty()) name.substring(0, 1) else ""
FastScrollItemIndicator.Text(character.uppercase(Locale.getDefault()))
} catch (e: Exception) {
FastScrollItemIndicator.Text("")
}
})
ContactsAdapter(
activity = this,
contacts = filtered,
recyclerView = binding.dialpadList,
highlightText = text
) {
val contact = it as Contact
if (config.showCallConfirmation) {
CallConfirmationDialog(this@DialpadActivity, contact.getNameToDisplay()) {
startCallIntent(contact.getPrimaryNumber() ?: return@CallConfirmationDialog)
}
} else {
startCallIntent(contact.getPrimaryNumber() ?: return@ContactsAdapter)
}
}.apply {
binding.dialpadList.adapter = this
}
binding.dialpadPlaceholder.beVisibleIf(filtered.isEmpty())
binding.dialpadList.beVisibleIf(filtered.isNotEmpty())
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (requestCode == REQUEST_CODE_SET_DEFAULT_DIALER && isDefaultDialer()) {
dialpadValueChanged(binding.dialpadInput.value)
}
}
private fun initCall(number: String = binding.dialpadInput.value, handleIndex: Int) {
if (number.isNotEmpty()) {
if (handleIndex != -1 && areMultipleSIMsAvailable()) {
if (config.showCallConfirmation) {
CallConfirmationDialog(this, number) {
callContactWithSim(number, handleIndex == 0)
}
} else {
callContactWithSim(number, handleIndex == 0)
}
} else {
if (config.showCallConfirmation) {
CallConfirmationDialog(this, number) {
startCallIntent(number)
}
} else {
startCallIntent(number)
}
}
}
}
private fun speedDial(id: Int): Boolean {
if (binding.dialpadInput.value.length == 1) {
val speedDial = speedDialValues.firstOrNull { it.id == id }
if (speedDial?.isValid() == true) {
initCall(speedDial.number, -1)
return true
}
}
return false
}
private fun initRussianChars() {
russianCharsMap['а'] = 2; russianCharsMap['б'] = 2; russianCharsMap['в'] = 2; russianCharsMap['г'] = 2
russianCharsMap['д'] = 3; russianCharsMap['е'] = 3; russianCharsMap['ё'] = 3; russianCharsMap['ж'] = 3; russianCharsMap['з'] = 3
russianCharsMap['и'] = 4; russianCharsMap['й'] = 4; russianCharsMap['к'] = 4; russianCharsMap['л'] = 4
russianCharsMap['м'] = 5; russianCharsMap['н'] = 5; russianCharsMap['о'] = 5; russianCharsMap['п'] = 5
russianCharsMap['р'] = 6; russianCharsMap['с'] = 6; russianCharsMap['т'] = 6; russianCharsMap['у'] = 6
russianCharsMap['ф'] = 7; russianCharsMap['х'] = 7; russianCharsMap['ц'] = 7; russianCharsMap['ч'] = 7
russianCharsMap['ш'] = 8; russianCharsMap['щ'] = 8; russianCharsMap['ъ'] = 8; russianCharsMap['ы'] = 8
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 = if (event.rawX.isNaN() || event.rawY.isNaN()) {
false
} else {
view.boundingBox.contains(event.rawX.roundToInt(), event.rawY.roundToInt())
}
if (!viewContainsTouchEvent) {
stopDialpadTone(char)
if (longClickable) {
longPressHandler.removeCallbacksAndMessages(null)
}
}
}
}
false
}
}
}