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.lifecycle:lifecycle-runtime-ktx:2.6.2'
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.cardview:cardview:1.0.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'
|
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Application
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
|
import im.angry.openeuicc.util.PreferenceRepository
|
||||||
|
|
||||||
open class OpenEuiccApplication : Application() {
|
open class OpenEuiccApplication : Application() {
|
||||||
val telephonyManager by lazy {
|
val telephonyManager by lazy {
|
||||||
|
@ -17,4 +18,8 @@ open class OpenEuiccApplication : Application() {
|
||||||
val subscriptionManager by lazy {
|
val subscriptionManager by lazy {
|
||||||
getSystemService(SubscriptionManager::class.java)!!
|
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.common.R
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
|
|
||||||
open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener {
|
open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener {
|
||||||
|
@ -152,11 +154,17 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi
|
||||||
private suspend fun doEnableProfile(iccid: String) =
|
private suspend fun doEnableProfile(iccid: String) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
channel.lpa.enableProfile(iccid)
|
channel.lpa.enableProfile(iccid)
|
||||||
|
if (preferenceRepository.notificationEnableFlow.first()) {
|
||||||
|
channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Enable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doDisableProfile(iccid: String) =
|
private suspend fun doDisableProfile(iccid: String) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
channel.lpa.disableProfile(iccid)
|
channel.lpa.disableProfile(iccid)
|
||||||
|
if (preferenceRepository.notificationDisableFlow.first()) {
|
||||||
|
channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Disable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {
|
sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {
|
||||||
|
|
|
@ -7,9 +7,12 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.util.preferenceRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
|
|
||||||
class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker {
|
class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker {
|
||||||
|
@ -58,6 +61,9 @@ class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
doDelete()
|
doDelete()
|
||||||
|
if (preferenceRepository.notificationDeleteFlow.first()) {
|
||||||
|
channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Delete)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(ProfileDownloadFragment.TAG, "Error deleting profile")
|
Log.d(ProfileDownloadFragment.TAG, "Error deleting profile")
|
||||||
Log.d(ProfileDownloadFragment.TAG, Log.getStackTraceString(e))
|
Log.d(ProfileDownloadFragment.TAG, Log.getStackTraceString(e))
|
||||||
|
|
|
@ -18,10 +18,13 @@ import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.util.openEuiccApplication
|
import im.angry.openeuicc.util.openEuiccApplication
|
||||||
|
import im.angry.openeuicc.util.preferenceRepository
|
||||||
import im.angry.openeuicc.util.setWidthPercent
|
import im.angry.openeuicc.util.setWidthPercent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||||
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
||||||
import kotlin.Exception
|
import kotlin.Exception
|
||||||
|
|
||||||
|
@ -168,6 +171,9 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
doDownloadProfile(server, code, confirmationCode, imei)
|
doDownloadProfile(server, code, confirmationCode, imei)
|
||||||
|
if (preferenceRepository.notificationDownloadFlow.first()) {
|
||||||
|
channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Install)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(TAG, "Error downloading profile")
|
Log.d(TAG, "Error downloading profile")
|
||||||
Log.d(TAG, Log.getStackTraceString(e))
|
Log.d(TAG, Log.getStackTraceString(e))
|
||||||
|
|
|
@ -3,10 +3,16 @@ package im.angry.openeuicc.ui
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
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.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class SettingsFragment: PreferenceFragmentCompat() {
|
class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
@ -20,5 +26,30 @@ class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.summary.toString())))
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.summary.toString())))
|
||||||
true
|
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_settings">Settings</string>
|
||||||
<string name="pref_notifications">Notifications</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_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">Info</string>
|
||||||
<string name="pref_info_app_version">App Version</string>
|
<string name="pref_info_app_version">App Version</string>
|
||||||
<string name="pref_info_source_code">Source Code</string>
|
<string name="pref_info_source_code">Source Code</string>
|
||||||
|
|
|
@ -4,7 +4,26 @@
|
||||||
app:title="@string/pref_notifications"
|
app:title="@string/pref_notifications"
|
||||||
app:summary="@string/pref_notifications_desc"
|
app:summary="@string/pref_notifications_desc"
|
||||||
app:iconSpaceReserved="false">
|
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>
|
</im.angry.openeuicc.ui.preference.LongSummaryPreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package net.typeblog.lpac_jni.impl
|
package net.typeblog.lpac_jni.impl
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import net.typeblog.lpac_jni.LpacJni
|
import net.typeblog.lpac_jni.LpacJni
|
||||||
import net.typeblog.lpac_jni.ApduInterface
|
import net.typeblog.lpac_jni.ApduInterface
|
||||||
import net.typeblog.lpac_jni.EuiccInfo2
|
import net.typeblog.lpac_jni.EuiccInfo2
|
||||||
|
@ -13,6 +14,10 @@ class LocalProfileAssistantImpl(
|
||||||
apduInterface: ApduInterface,
|
apduInterface: ApduInterface,
|
||||||
httpInterface: HttpInterface
|
httpInterface: HttpInterface
|
||||||
): LocalProfileAssistant {
|
): LocalProfileAssistant {
|
||||||
|
companion object {
|
||||||
|
val TAG = "LocalProfileAssistantImpl"
|
||||||
|
}
|
||||||
|
|
||||||
private val contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface)
|
private val contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface)
|
||||||
init {
|
init {
|
||||||
if (LpacJni.es10xInit(contextHandle) < 0) {
|
if (LpacJni.es10xInit(contextHandle) < 0) {
|
||||||
|
@ -59,6 +64,7 @@ class LocalProfileAssistantImpl(
|
||||||
|
|
||||||
override fun handleLatestNotification(operation: LocalProfileNotification.Operation) {
|
override fun handleLatestNotification(operation: LocalProfileNotification.Operation) {
|
||||||
notifications.find { it.profileManagementOperation == operation }?.let {
|
notifications.find { it.profileManagementOperation == operation }?.let {
|
||||||
|
Log.d(TAG, "handleLatestNotification: $it")
|
||||||
handleNotification(it.seqNumber)
|
handleNotification(it.seqNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue