Contact Whitelist added
This commit is contained in:
parent
663463cd38
commit
1691891a4d
13 changed files with 227 additions and 31 deletions
|
@ -6,7 +6,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
class AboutActivity : AppCompatActivity() {
|
class AboutActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// placeholder until real UI is added
|
// placeholder
|
||||||
setContentView(androidx.appcompat.R.layout.abc_action_bar_title_item)
|
setContentView(androidx.appcompat.R.layout.abc_action_bar_title_item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package partisan.weforge.xyz.pulse
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
class ContactAdapter(
|
||||||
|
private val prefs: Preferences,
|
||||||
|
private val contacts: List<ContactEntry>
|
||||||
|
) : RecyclerView.Adapter<ContactAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
inner class ViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
|
||||||
|
RecyclerView.ViewHolder(inflater.inflate(R.layout.item_contact, parent, false)) {
|
||||||
|
val contactName: TextView = itemView.findViewById(R.id.contactName)
|
||||||
|
val contactAllowed: CheckBox = itemView.findViewById(R.id.contactAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
return ViewHolder(LayoutInflater.from(parent.context), parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val contact = contacts[position]
|
||||||
|
holder.contactName.text = contact.name
|
||||||
|
holder.contactAllowed.isChecked = prefs.isContactWhitelisted(contact.phoneNumber)
|
||||||
|
|
||||||
|
holder.contactAllowed.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
prefs.setContactWhitelisted(contact.phoneNumber, isChecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = contacts.size
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package partisan.weforge.xyz.pulse
|
||||||
|
|
||||||
|
data class ContactEntry(
|
||||||
|
val name: String,
|
||||||
|
val phoneNumber: String
|
||||||
|
)
|
|
@ -0,0 +1,75 @@
|
||||||
|
package partisan.weforge.xyz.pulse
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import partisan.weforge.xyz.pulse.databinding.FragmentContactsBinding
|
||||||
|
|
||||||
|
class ContactsFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentContactsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private lateinit var prefs: Preferences
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentContactsBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
prefs = Preferences(requireContext())
|
||||||
|
|
||||||
|
val contacts = getContacts()
|
||||||
|
val adapter = ContactAdapter(prefs, contacts)
|
||||||
|
binding.contactRecycler.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
binding.contactRecycler.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getContacts(): List<ContactEntry> {
|
||||||
|
val resolver: ContentResolver = requireContext().contentResolver
|
||||||
|
val projection = arrayOf(
|
||||||
|
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
|
||||||
|
ContactsContract.CommonDataKinds.Phone.NUMBER
|
||||||
|
)
|
||||||
|
|
||||||
|
val cursor = resolver.query(
|
||||||
|
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||||
|
projection,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} ASC"
|
||||||
|
)
|
||||||
|
|
||||||
|
val results = mutableListOf<ContactEntry>()
|
||||||
|
|
||||||
|
cursor?.use {
|
||||||
|
val nameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
|
||||||
|
val numberIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
|
||||||
|
|
||||||
|
while (it.moveToNext()) {
|
||||||
|
val name = it.getString(nameIndex) ?: continue
|
||||||
|
val number = it.getString(numberIndex) ?: continue
|
||||||
|
results.add(ContactEntry(name, number))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
binding.drawerLayout.addDrawerListener(drawerToggle)
|
binding.drawerLayout.addDrawerListener(drawerToggle)
|
||||||
drawerToggle.syncState()
|
drawerToggle.syncState()
|
||||||
|
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragmentContainer, MainFragment())
|
.replace(R.id.fragmentContainer, MainFragment())
|
||||||
.commit()
|
.commit()
|
||||||
|
@ -42,17 +43,21 @@ class MainActivity : AppCompatActivity() {
|
||||||
R.id.action_popup_settings -> {
|
R.id.action_popup_settings -> {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragmentContainer, PopupSettingsFragment())
|
.replace(R.id.fragmentContainer, PopupSettingsFragment())
|
||||||
|
.addToBackStack(null)
|
||||||
.commit()
|
.commit()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.action_settings -> {
|
|
||||||
startActivity(Intent(this, SettingsActivity::class.java))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.action_about -> {
|
R.id.action_about -> {
|
||||||
startActivity(Intent(this, AboutActivity::class.java))
|
startActivity(Intent(this, AboutActivity::class.java))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.action_contacts -> {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainer, ContactsFragment())
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}.also {
|
}.also {
|
||||||
binding.drawerLayout.closeDrawers()
|
binding.drawerLayout.closeDrawers()
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package partisan.weforge.xyz.pulse
|
package partisan.weforge.xyz.pulse
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
@ -25,4 +24,4 @@ fun hasDrawOverlays(context: Context): Boolean {
|
||||||
fun hasCallRedirectionRole(context: Context): Boolean {
|
fun hasCallRedirectionRole(context: Context): Boolean {
|
||||||
val roleManager = context.getSystemService(RoleManager::class.java)
|
val roleManager = context.getSystemService(RoleManager::class.java)
|
||||||
return roleManager?.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION) ?: false
|
return roleManager?.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION) ?: false
|
||||||
}
|
}
|
|
@ -9,13 +9,12 @@ class Preferences(ctx: Context) {
|
||||||
private const val ENABLED = "enabled"
|
private const val ENABLED = "enabled"
|
||||||
private const val REDIRECTION_DELAY = "redirection_delay"
|
private const val REDIRECTION_DELAY = "redirection_delay"
|
||||||
private const val POPUP_POSITION = "popup_position_y"
|
private const val POPUP_POSITION = "popup_position_y"
|
||||||
|
private const val POPUP_ENABLED = "popup_enabled"
|
||||||
|
private const val BLACKLISTED_CONTACTS = "blacklisted_contacts"
|
||||||
|
|
||||||
private const val DEFAULT_REDIRECTION_DELAY = 2000L
|
private const val DEFAULT_REDIRECTION_DELAY = 2000L
|
||||||
private const val DEFAULT_POPUP_POSITION = 333
|
private const val DEFAULT_POPUP_POSITION = 333
|
||||||
|
|
||||||
private const val SERVICE_ENABLED = "service_enabled"
|
private const val SERVICE_ENABLED = "service_enabled"
|
||||||
|
|
||||||
private const val POPUP_ENABLED = "popup_enabled"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(ctx)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
@ -39,23 +38,37 @@ class Preferences(ctx: Context) {
|
||||||
private fun makeKeyEnabled(mimetype: String) = "enabled_$mimetype"
|
private fun makeKeyEnabled(mimetype: String) = "enabled_$mimetype"
|
||||||
private fun makeKeyPriority(mimetype: String) = "priority_$mimetype"
|
private fun makeKeyPriority(mimetype: String) = "priority_$mimetype"
|
||||||
|
|
||||||
/** Whether this service is enabled */
|
|
||||||
fun isServiceEnabled(mimetype: String): Boolean {
|
fun isServiceEnabled(mimetype: String): Boolean {
|
||||||
return prefs.getBoolean(makeKeyEnabled(mimetype), true)
|
return prefs.getBoolean(makeKeyEnabled(mimetype), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Current priority for this service (lower = higher priority) */
|
|
||||||
fun getServicePriority(mimetype: String): Int {
|
fun getServicePriority(mimetype: String): Int {
|
||||||
return prefs.getInt(makeKeyPriority(mimetype), Int.MAX_VALUE)
|
return prefs.getInt(makeKeyPriority(mimetype), Int.MAX_VALUE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Enable or disable individual service */
|
|
||||||
fun setServiceEnabled(mimetype: String, enabled: Boolean) {
|
fun setServiceEnabled(mimetype: String, enabled: Boolean) {
|
||||||
prefs.edit().putBoolean(makeKeyEnabled(mimetype), enabled).apply()
|
prefs.edit().putBoolean(makeKeyEnabled(mimetype), enabled).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Change priority for an individual service */
|
|
||||||
fun setServicePriority(mimetype: String, priority: Int) {
|
fun setServicePriority(mimetype: String, priority: Int) {
|
||||||
prefs.edit().putInt(makeKeyPriority(mimetype), priority).apply()
|
prefs.edit().putInt(makeKeyPriority(mimetype), priority).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var blacklistedContacts: Set<String>
|
||||||
|
get() = prefs.getStringSet(BLACKLISTED_CONTACTS, emptySet()) ?: emptySet()
|
||||||
|
set(value) = prefs.edit { putStringSet(BLACKLISTED_CONTACTS, value) }
|
||||||
|
|
||||||
|
fun isContactWhitelisted(phoneNumber: String): Boolean {
|
||||||
|
return !blacklistedContacts.contains(phoneNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setContactWhitelisted(phoneNumber: String, allowed: Boolean) {
|
||||||
|
val current = blacklistedContacts.toMutableSet()
|
||||||
|
if (!allowed) {
|
||||||
|
current.add(phoneNumber)
|
||||||
|
} else {
|
||||||
|
current.remove(phoneNumber)
|
||||||
|
}
|
||||||
|
blacklistedContacts = current
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package partisan.weforge.xyz.pulse
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
// placeholder until real UI is added
|
|
||||||
setContentView(androidx.appcompat.R.layout.abc_action_bar_title_item)
|
|
||||||
}
|
|
||||||
}
|
|
10
app/src/main/res/drawable/group_24px.xml
Normal file
10
app/src/main/res/drawable/group_24px.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M40,800L40,688Q40,654 57.5,625.5Q75,597 104,582Q166,551 230,535.5Q294,520 360,520Q426,520 490,535.5Q554,551 616,582Q645,597 662.5,625.5Q680,654 680,688L680,800L40,800ZM760,800L760,680Q760,636 735.5,595.5Q711,555 666,526Q717,532 762,546.5Q807,561 846,582Q882,602 901,626.5Q920,651 920,680L920,800L760,800ZM360,480Q294,480 247,433Q200,386 200,320Q200,254 247,207Q294,160 360,160Q426,160 473,207Q520,254 520,320Q520,386 473,433Q426,480 360,480ZM760,320Q760,386 713,433Q666,480 600,480Q589,480 572,477.5Q555,475 544,472Q571,440 585.5,401Q600,362 600,320Q600,278 585.5,239Q571,200 544,168Q558,163 572,161.5Q586,160 600,160Q666,160 713,207Q760,254 760,320ZM120,720L600,720L600,688Q600,677 594.5,668Q589,659 580,654Q526,627 471,613.5Q416,600 360,600Q304,600 249,613.5Q194,627 140,654Q131,659 125.5,668Q120,677 120,688L120,720ZM360,400Q393,400 416.5,376.5Q440,353 440,320Q440,287 416.5,263.5Q393,240 360,240Q327,240 303.5,263.5Q280,287 280,320Q280,353 303.5,376.5Q327,400 360,400ZM360,720L360,720L360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720ZM360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/info_24px.xml
Normal file
10
app/src/main/res/drawable/info_24px.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M440,680L520,680L520,440L440,440L440,680ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||||
|
</vector>
|
19
app/src/main/res/layout/fragment_contacts.xml
Normal file
19
app/src/main/res/layout/fragment_contacts.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/contactRecycler"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="8dp" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
20
app/src/main/res/layout/item_contact.xml
Normal file
20
app/src/main/res/layout/item_contact.xml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contactName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Name" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/contactAllowed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
|
@ -1,14 +1,30 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<!-- Settings section -->
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/section_settings"
|
||||||
android:title="Settings" />
|
android:title="Settings"
|
||||||
<item
|
android:enabled="false" />
|
||||||
android:id="@+id/action_about"
|
|
||||||
android:title="About" />
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_popup_settings"
|
android:id="@+id/action_popup_settings"
|
||||||
android:title="Popup Settings"
|
android:title="Popup Settings"
|
||||||
android:icon="@drawable/tooltip_24px"
|
android:icon="@drawable/tooltip_24px"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_contacts"
|
||||||
|
android:title="Contacts"
|
||||||
|
android:icon="@drawable/group_24px"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<!-- About section -->
|
||||||
|
<item
|
||||||
|
android:id="@+id/section_about"
|
||||||
|
android:title="About"
|
||||||
|
android:enabled="false" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_about"
|
||||||
|
android:title="About"
|
||||||
|
android:icon="@drawable/info_24px"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue