feat: Notification handling [2/2]
This commit is contained in:
parent
2fc84146da
commit
3357a90f91
|
@ -39,6 +39,7 @@ dependencies {
|
|||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.datastore:datastore-preferences:1.0.0"
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Application
|
|||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.TelephonyManager
|
||||
import im.angry.openeuicc.core.EuiccChannelManager
|
||||
import im.angry.openeuicc.util.PreferenceRepository
|
||||
|
||||
open class OpenEuiccApplication : Application() {
|
||||
val telephonyManager by lazy {
|
||||
|
@ -17,4 +18,8 @@ open class OpenEuiccApplication : Application() {
|
|||
val subscriptionManager by lazy {
|
||||
getSystemService(SubscriptionManager::class.java)!!
|
||||
}
|
||||
|
||||
val preferenceRepository by lazy {
|
||||
PreferenceRepository(this)
|
||||
}
|
||||
}
|
|
@ -26,8 +26,10 @@ import net.typeblog.lpac_jni.LocalProfileInfo
|
|||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||
import java.lang.Exception
|
||||
|
||||
open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener {
|
||||
|
@ -152,11 +154,17 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi
|
|||
private suspend fun doEnableProfile(iccid: String) =
|
||||
withContext(Dispatchers.IO) {
|
||||
channel.lpa.enableProfile(iccid)
|
||||
if (preferenceRepository.notificationEnableFlow.first()) {
|
||||
channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Enable)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doDisableProfile(iccid: String) =
|
||||
withContext(Dispatchers.IO) {
|
||||
channel.lpa.disableProfile(iccid)
|
||||
if (preferenceRepository.notificationDisableFlow.first()) {
|
||||
channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Disable)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {
|
||||
|
|
|
@ -7,9 +7,12 @@ import androidx.appcompat.app.AlertDialog
|
|||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.util.preferenceRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||
import java.lang.Exception
|
||||
|
||||
class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker {
|
||||
|
@ -58,6 +61,9 @@ class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker {
|
|||
lifecycleScope.launch {
|
||||
try {
|
||||
doDelete()
|
||||
if (preferenceRepository.notificationDeleteFlow.first()) {
|
||||
channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Delete)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(ProfileDownloadFragment.TAG, "Error deleting profile")
|
||||
Log.d(ProfileDownloadFragment.TAG, Log.getStackTraceString(e))
|
||||
|
|
|
@ -18,10 +18,13 @@ import com.journeyapps.barcodescanner.ScanContract
|
|||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.util.openEuiccApplication
|
||||
import im.angry.openeuicc.util.preferenceRepository
|
||||
import im.angry.openeuicc.util.setWidthPercent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
||||
import kotlin.Exception
|
||||
|
||||
|
@ -168,6 +171,9 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
|
|||
lifecycleScope.launch {
|
||||
try {
|
||||
doDownloadProfile(server, code, confirmationCode, imei)
|
||||
if (preferenceRepository.notificationDownloadFlow.first()) {
|
||||
channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Install)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error downloading profile")
|
||||
Log.d(TAG, Log.getStackTraceString(e))
|
||||
|
|
|
@ -3,10 +3,16 @@ package im.angry.openeuicc.ui
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class SettingsFragment: PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
@ -20,5 +26,30 @@ class SettingsFragment: PreferenceFragmentCompat() {
|
|||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.summary.toString())))
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<CheckBoxPreference>("pref_notifications_download")
|
||||
?.bindBooleanFlow(preferenceRepository.notificationDownloadFlow, PreferenceKeys.NOTIFICATION_DOWNLOAD)
|
||||
|
||||
findPreference<CheckBoxPreference>("pref_notifications_delete")
|
||||
?.bindBooleanFlow(preferenceRepository.notificationDeleteFlow, PreferenceKeys.NOTIFICATION_DELETE)
|
||||
|
||||
findPreference<CheckBoxPreference>("pref_notifications_enable")
|
||||
?.bindBooleanFlow(preferenceRepository.notificationEnableFlow, PreferenceKeys.NOTIFICATION_ENABLE)
|
||||
|
||||
findPreference<CheckBoxPreference>("pref_notifications_disable")
|
||||
?.bindBooleanFlow(preferenceRepository.notificationDisableFlow, PreferenceKeys.NOTIFICATION_DISABLE)
|
||||
}
|
||||
|
||||
private fun CheckBoxPreference.bindBooleanFlow(flow: Flow<Boolean>, key: Preferences.Key<Boolean>) {
|
||||
lifecycleScope.launch {
|
||||
flow.collect { isChecked = it }
|
||||
}
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
runBlocking {
|
||||
preferenceRepository.updatePreference(key, newValue as Boolean)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package im.angry.openeuicc.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.angry.openeuicc.OpenEuiccApplication
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "prefs")
|
||||
|
||||
val Context.preferenceRepository: PreferenceRepository
|
||||
get() = (applicationContext as OpenEuiccApplication).preferenceRepository
|
||||
|
||||
val Fragment.preferenceRepository: PreferenceRepository
|
||||
get() = requireContext().preferenceRepository
|
||||
|
||||
object PreferenceKeys {
|
||||
val NOTIFICATION_DOWNLOAD = booleanPreferencesKey("notification_download")
|
||||
val NOTIFICATION_DELETE = booleanPreferencesKey("notification_delete")
|
||||
val NOTIFICATION_ENABLE = booleanPreferencesKey("notification_enable")
|
||||
val NOTIFICATION_DISABLE = booleanPreferencesKey("notification_disable")
|
||||
}
|
||||
|
||||
class PreferenceRepository(context: Context) {
|
||||
private val dataStore = context.dataStore
|
||||
|
||||
// Expose flows so that we can also handle default values
|
||||
// ---- Profile Notifications ----
|
||||
val notificationDownloadFlow: Flow<Boolean> =
|
||||
dataStore.data.map { it[PreferenceKeys.NOTIFICATION_DOWNLOAD] ?: true }
|
||||
|
||||
val notificationDeleteFlow: Flow<Boolean> =
|
||||
dataStore.data.map { it[PreferenceKeys.NOTIFICATION_DELETE] ?: true }
|
||||
|
||||
// Enabling / disabling notifications are not sent by default
|
||||
val notificationEnableFlow: Flow<Boolean> =
|
||||
dataStore.data.map { it[PreferenceKeys.NOTIFICATION_ENABLE] ?: false }
|
||||
|
||||
val notificationDisableFlow: Flow<Boolean> =
|
||||
dataStore.data.map { it[PreferenceKeys.NOTIFICATION_DISABLE] ?: false }
|
||||
|
||||
suspend fun <T> updatePreference(key: Preferences.Key<T>, value: T) {
|
||||
dataStore.edit {
|
||||
it[key] = value
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,14 @@
|
|||
<string name="pref_settings">Settings</string>
|
||||
<string name="pref_notifications">Notifications</string>
|
||||
<string name="pref_notifications_desc">eSIM profile operations send notifications to the carrier. Fine-tune this behavior as needed here.</string>
|
||||
<string name="pref_notifications_download">Downloads</string>
|
||||
<string name="pref_notifications_download_desc">Send notifications for <i>downloading</i> profiles</string>
|
||||
<string name="pref_notifications_delete">Deletion</string>
|
||||
<string name="pref_notifications_delete_desc">Send notifications for <i>deleting</i> profiles</string>
|
||||
<string name="pref_notifications_enable">Enabling</string>
|
||||
<string name="pref_notifications_enable_desc">Send notifications for <i>enabling</i> profiles\nNote that this type of notification is unreliable.</string>
|
||||
<string name="pref_notifications_disable">Disabling</string>
|
||||
<string name="pref_notifications_disable_desc">Send notifications for <i>disabling</i> profiles\nNote that this type of notification is unreliable.</string>
|
||||
<string name="pref_info">Info</string>
|
||||
<string name="pref_info_app_version">App Version</string>
|
||||
<string name="pref_info_source_code">Source Code</string>
|
||||
|
|
|
@ -4,7 +4,26 @@
|
|||
app:title="@string/pref_notifications"
|
||||
app:summary="@string/pref_notifications_desc"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<CheckBoxPreference
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref_notifications_download"
|
||||
app:summary="@string/pref_notifications_download_desc"
|
||||
app:key="pref_notifications_download" />
|
||||
<CheckBoxPreference
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref_notifications_delete"
|
||||
app:summary="@string/pref_notifications_delete_desc"
|
||||
app:key="pref_notifications_delete" />
|
||||
<CheckBoxPreference
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref_notifications_enable"
|
||||
app:summary="@string/pref_notifications_enable_desc"
|
||||
app:key="pref_notifications_enable" />
|
||||
<CheckBoxPreference
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref_notifications_disable"
|
||||
app:summary="@string/pref_notifications_disable_desc"
|
||||
app:key="pref_notifications_disable" />
|
||||
</im.angry.openeuicc.ui.preference.LongSummaryPreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package net.typeblog.lpac_jni.impl
|
||||
|
||||
import android.util.Log
|
||||
import net.typeblog.lpac_jni.LpacJni
|
||||
import net.typeblog.lpac_jni.ApduInterface
|
||||
import net.typeblog.lpac_jni.EuiccInfo2
|
||||
|
@ -13,6 +14,10 @@ class LocalProfileAssistantImpl(
|
|||
apduInterface: ApduInterface,
|
||||
httpInterface: HttpInterface
|
||||
): LocalProfileAssistant {
|
||||
companion object {
|
||||
val TAG = "LocalProfileAssistantImpl"
|
||||
}
|
||||
|
||||
private val contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface)
|
||||
init {
|
||||
if (LpacJni.es10xInit(contextHandle) < 0) {
|
||||
|
@ -59,6 +64,7 @@ class LocalProfileAssistantImpl(
|
|||
|
||||
override fun handleLatestNotification(operation: LocalProfileNotification.Operation) {
|
||||
notifications.find { it.profileManagementOperation == operation }?.let {
|
||||
Log.d(TAG, "handleLatestNotification: $it")
|
||||
handleNotification(it.seqNumber)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue