feat: Notification handling [2/2]

This commit is contained in:
Peter Cai 2023-12-31 20:17:17 -05:00
parent 2fc84146da
commit 3357a90f91
10 changed files with 143 additions and 1 deletions

View file

@ -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'

View file

@ -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)
}
}

View file

@ -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) {

View file

@ -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))

View file

@ -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))

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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>

View file

@ -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

View file

@ -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)
}
}