feat: Notification handling [1/2]
This commit is contained in:
parent
a4aaa9bb1a
commit
608dadd65d
|
@ -12,6 +12,10 @@
|
|||
android:name="im.angry.openeuicc.ui.SettingsActivity"
|
||||
android:label="@string/pref_settings" />
|
||||
|
||||
<activity
|
||||
android:name="im.angry.openeuicc.ui.NotificationsActivity"
|
||||
android:label="@string/profile_notifications" />
|
||||
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
android:screenOrientation="fullSensor"
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.method.PasswordTransformationMethod
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -41,6 +44,11 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi
|
|||
|
||||
private val adapter = EuiccProfileAdapter()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -77,6 +85,23 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi
|
|||
refresh()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.fragment_euicc, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
||||
when (item.itemId) {
|
||||
R.id.show_notifications -> {
|
||||
Intent(requireContext(), NotificationsActivity::class.java).apply {
|
||||
putExtra("logicalSlotId", channel.logicalSlotId)
|
||||
startActivity(this)
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
protected open suspend fun onCreateFooterViews(parent: ViewGroup): List<View> = listOf()
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.ContextMenu
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MenuItem.OnMenuItemClickListener
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.forEach
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import im.angry.openeuicc.OpenEuiccApplication
|
||||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.core.EuiccChannel
|
||||
import im.angry.openeuicc.util.displayName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||
|
||||
class NotificationsActivity: AppCompatActivity() {
|
||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||
private lateinit var notificationList: RecyclerView
|
||||
private val notificationAdapter = NotificationAdapter()
|
||||
|
||||
private lateinit var euiccChannel: EuiccChannel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_notifications)
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
euiccChannel = (application as OpenEuiccApplication).euiccChannelManager
|
||||
.findEuiccChannelBySlotBlocking(intent.getIntExtra("logicalSlotId", 0))!!
|
||||
|
||||
swipeRefresh = findViewById(R.id.swipe_refresh)
|
||||
notificationList = findViewById(R.id.recycler_view)
|
||||
|
||||
notificationList.layoutManager =
|
||||
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||
notificationList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
|
||||
notificationList.adapter = notificationAdapter
|
||||
registerForContextMenu(notificationList)
|
||||
|
||||
swipeRefresh.setOnRefreshListener {
|
||||
refresh()
|
||||
}
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.activity_notifications, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
R.id.help -> {
|
||||
AlertDialog.Builder(this, R.style.AlertDialogTheme).apply {
|
||||
setMessage(R.string.profile_notifications_help)
|
||||
setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
show()
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun launchTask(task: suspend () -> Unit) {
|
||||
swipeRefresh.isRefreshing = true
|
||||
|
||||
lifecycleScope.launch {
|
||||
task()
|
||||
|
||||
swipeRefresh.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
launchTask {
|
||||
val profiles = withContext(Dispatchers.IO) {
|
||||
euiccChannel.lpa.profiles
|
||||
}
|
||||
|
||||
notificationAdapter.notifications =
|
||||
withContext(Dispatchers.IO) {
|
||||
euiccChannel.lpa.notifications.map {
|
||||
val profile = profiles.find { p -> p.iccid == it.iccid }
|
||||
LocalProfileNotificationWrapper(it, profile?.displayName ?: "???")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class LocalProfileNotificationWrapper(
|
||||
val inner: LocalProfileNotification,
|
||||
val profileName: String
|
||||
)
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
inner class NotificationViewHolder(private val root: View):
|
||||
RecyclerView.ViewHolder(root), View.OnCreateContextMenuListener, OnMenuItemClickListener {
|
||||
private val address: TextView = root.findViewById(R.id.notification_address)
|
||||
private val profileName: TextView = root.findViewById(R.id.notification_profile_name)
|
||||
|
||||
private lateinit var notification: LocalProfileNotificationWrapper
|
||||
|
||||
private var lastTouchX = 0f
|
||||
private var lastTouchY = 0f
|
||||
|
||||
init {
|
||||
root.isClickable = true
|
||||
root.setOnCreateContextMenuListener(this)
|
||||
root.setOnTouchListener { _, event ->
|
||||
lastTouchX = event.x
|
||||
lastTouchY = event.y
|
||||
false
|
||||
}
|
||||
root.setOnLongClickListener {
|
||||
root.showContextMenu(lastTouchX, lastTouchY)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun operationToLocalizedText(operation: LocalProfileNotification.Operation) =
|
||||
root.context.getText(
|
||||
when (operation) {
|
||||
LocalProfileNotification.Operation.Install -> R.string.profile_notification_operation_download
|
||||
LocalProfileNotification.Operation.Delete -> R.string.profile_notification_operation_delete
|
||||
LocalProfileNotification.Operation.Enable -> R.string.profile_notification_operation_enable
|
||||
LocalProfileNotification.Operation.Disable -> R.string.profile_notification_operation_disable
|
||||
})
|
||||
|
||||
fun updateNotification(value: LocalProfileNotificationWrapper) {
|
||||
notification = value
|
||||
|
||||
address.text = value.inner.notificationAddress
|
||||
profileName.text = Html.fromHtml(
|
||||
root.context.getString(R.string.profile_notification_name_format,
|
||||
operationToLocalizedText(value.inner.profileManagementOperation),
|
||||
value.profileName, value.inner.iccid),
|
||||
Html.FROM_HTML_MODE_COMPACT)
|
||||
}
|
||||
|
||||
override fun onCreateContextMenu(
|
||||
menu: ContextMenu?,
|
||||
v: View?,
|
||||
menuInfo: ContextMenu.ContextMenuInfo?
|
||||
) {
|
||||
menuInflater.inflate(R.menu.notification_options, menu)
|
||||
|
||||
menu!!.forEach {
|
||||
it.setOnMenuItemClickListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean =
|
||||
when (item.itemId) {
|
||||
R.id.notification_process -> {
|
||||
launchTask {
|
||||
euiccChannel.lpa.handleNotification(notification.inner.seqNumber)
|
||||
}
|
||||
refresh()
|
||||
true
|
||||
}
|
||||
R.id.notification_delete -> {
|
||||
launchTask {
|
||||
euiccChannel.lpa.deleteNotification(notification.inner.seqNumber)
|
||||
}
|
||||
refresh()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
inner class NotificationAdapter: RecyclerView.Adapter<NotificationViewHolder>() {
|
||||
var notifications: List<LocalProfileNotificationWrapper> = listOf()
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationViewHolder {
|
||||
val root = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.notification_item, parent, false)
|
||||
return NotificationViewHolder(root)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = notifications.size
|
||||
|
||||
override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) =
|
||||
holder.updateNotification(notifications[position])
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package im.angry.openeuicc.ui.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
|
||||
@Suppress("unused")
|
||||
class LongSummaryPreferenceCategory: PreferenceCategory {
|
||||
constructor(ctx: Context): super(ctx)
|
||||
constructor(ctx: Context, attrs: AttributeSet): super(ctx, attrs)
|
||||
constructor(ctx: Context, attrs: AttributeSet, defStyle: Int): super(ctx, attrs, defStyle)
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
val summaryText = holder.findViewById(android.R.id.summary) as TextView
|
||||
summaryText.isSingleLine = false
|
||||
summaryText.maxLines = 10
|
||||
}
|
||||
}
|
5
app-common/src/main/res/drawable/ic_help_black.xml
Normal file
5
app-common/src/main/res/drawable/ic_help_black.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
|
||||
</vector>
|
18
app-common/src/main/res/layout/activity_notifications.xml
Normal file
18
app-common/src/main/res/layout/activity_notifications.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</FrameLayout>
|
31
app-common/src/main/res/layout/notification_item.xml
Normal file
31
app-common/src/main/res/layout/notification_item.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginVertical="12dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_profile_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="marquee"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/notification_address"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
9
app-common/src/main/res/menu/activity_notifications.xml
Normal file
9
app-common/src/main/res/menu/activity_notifications.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/help"
|
||||
android:icon="@drawable/ic_help_black"
|
||||
android:title="@string/help"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
8
app-common/src/main/res/menu/fragment_euicc.xml
Normal file
8
app-common/src/main/res/menu/fragment_euicc.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/show_notifications"
|
||||
android:title="@string/profile_notifications_show"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
10
app-common/src/main/res/menu/notification_options.xml
Normal file
10
app-common/src/main/res/menu/notification_options.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/notification_process"
|
||||
android:title="@string/profile_notification_process" />
|
||||
|
||||
<item
|
||||
android:id="@+id/notification_delete"
|
||||
android:title="@string/profile_notification_delete" />
|
||||
</menu>
|
|
@ -2,6 +2,7 @@
|
|||
<resources>
|
||||
<string name="no_euicc">No eUICC card on this device is accessible by this app.\nInsert a supported eUICC card, or try out the privileged OpenEUICC app instead.</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="help">Help</string>
|
||||
|
||||
<string name="channel_name_format">Logical Slot %d</string>
|
||||
|
||||
|
@ -33,7 +34,20 @@
|
|||
|
||||
<string name="profile_delete_confirm">Are you sure you want to delete the profile %s? This operation is irreversible.</string>
|
||||
|
||||
<string name="profile_notifications">Profile Notifications</string>
|
||||
<string name="profile_notifications_show">Manage Notifications</string>
|
||||
<string name="profile_notifications_help">eSIM profiles can send notifications to the carrier when they are downloaded, deleted, enabled, or disabled. The queue of these notifications to be sent is listed here.\n\nIn Settings, you can specify whether to send each type of notification automatically. Note that even if a notification has been sent, it will not be deleted automatically from the record, unless the queue runs out of space.\n\nHere, you can manually send or delete each pending notification.</string>
|
||||
<string name="profile_notification_operation_download">Downloaded</string>
|
||||
<string name="profile_notification_operation_delete">Deleted</string>
|
||||
<string name="profile_notification_operation_enable">Enabled</string>
|
||||
<string name="profile_notification_operation_disable">Disabled</string>
|
||||
<string name="profile_notification_name_format"><b>%s</b> %s (%s)</string>
|
||||
<string name="profile_notification_process">Process</string>
|
||||
<string name="profile_notification_delete">Delete</string>
|
||||
|
||||
<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_info">Info</string>
|
||||
<string name="pref_info_app_version">App Version</string>
|
||||
<string name="pref_info_source_code">Source Code</string>
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<im.angry.openeuicc.ui.preference.LongSummaryPreferenceCategory
|
||||
app:title="@string/pref_notifications"
|
||||
app:summary="@string/pref_notifications_desc"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
</im.angry.openeuicc.ui.preference.LongSummaryPreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="@string/pref_info">
|
||||
app:title="@string/pref_info"
|
||||
app:iconSpaceReserved="false">
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref_info_app_version"
|
||||
app:key="pref_info_app_version" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref_info_source_code"
|
||||
app:summary="@string/pref_info_source_code_url"
|
||||
app:key="pref_info_source_code"/>
|
||||
|
|
|
@ -2,6 +2,7 @@ package net.typeblog.lpac_jni
|
|||
|
||||
interface LocalProfileAssistant {
|
||||
val profiles: List<LocalProfileInfo>
|
||||
val notifications: List<LocalProfileNotification>
|
||||
val eID: String
|
||||
// Extended EuiccInfo for use with LUIs, containing information such as firmware version
|
||||
val euiccInfo2: EuiccInfo2?
|
||||
|
@ -13,6 +14,12 @@ interface LocalProfileAssistant {
|
|||
fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
|
||||
confirmationCode: String?, callback: ProfileDownloadCallback): Boolean
|
||||
|
||||
fun deleteNotification(seqNumber: Long): Boolean
|
||||
fun handleNotification(seqNumber: Long): Boolean
|
||||
// Handle the latest entry of a particular type of notification
|
||||
// Note that this is not guaranteed to always be reliable and no feedback will be provided on errors.
|
||||
fun handleLatestNotification(operation: LocalProfileNotification.Operation)
|
||||
|
||||
fun setNickname(
|
||||
iccid: String, nickname: String
|
||||
): Boolean
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package net.typeblog.lpac_jni
|
||||
|
||||
data class LocalProfileNotification(
|
||||
val seqNumber: Long,
|
||||
val profileManagementOperation: Operation,
|
||||
val notificationAddress: String,
|
||||
val iccid: String,
|
||||
) {
|
||||
enum class Operation {
|
||||
Install,
|
||||
Enable,
|
||||
Disable,
|
||||
Delete;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromString(str: String?) =
|
||||
when (str?.lowercase()) {
|
||||
"install" -> Install
|
||||
"enable" -> Enable
|
||||
"disable" -> Disable
|
||||
"delete" -> Delete
|
||||
else -> throw IllegalArgumentException("Unknown operation $str")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,15 @@ internal object LpacJni {
|
|||
external fun es10cDeleteProfile(handle: Long, iccid: String): Int
|
||||
external fun es10cSetNickname(handle: Long, iccid: String, nick: String): Int
|
||||
|
||||
// es10b
|
||||
external fun es10bListNotification(handle: Long): Array<LocalProfileNotification>?
|
||||
external fun es10bDeleteNotification(handle: Long, seqNumber: Long): Int
|
||||
|
||||
// es9p + es10b
|
||||
// We do not expose all of the functions because of tediousness :)
|
||||
external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?,
|
||||
confirmationCode: String?, callback: ProfileDownloadCallback): Int
|
||||
external fun handleNotification(handle: Long, seqNumber: Long): Int
|
||||
|
||||
// es10cex (actually part of es10b)
|
||||
external fun es10cexGetEuiccInfo2(handle: Long): EuiccInfo2?
|
||||
|
|
|
@ -6,6 +6,7 @@ import net.typeblog.lpac_jni.EuiccInfo2
|
|||
import net.typeblog.lpac_jni.HttpInterface
|
||||
import net.typeblog.lpac_jni.LocalProfileAssistant
|
||||
import net.typeblog.lpac_jni.LocalProfileInfo
|
||||
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
||||
|
||||
class LocalProfileAssistantImpl(
|
||||
|
@ -22,6 +23,11 @@ class LocalProfileAssistantImpl(
|
|||
override val profiles: List<LocalProfileInfo>
|
||||
get() = LpacJni.es10cGetProfilesInfo(contextHandle)!!.asList()
|
||||
|
||||
override val notifications: List<LocalProfileNotification>
|
||||
get() =
|
||||
(LpacJni.es10bListNotification(contextHandle) ?: arrayOf())
|
||||
.sortedBy { it.seqNumber }.reversed()
|
||||
|
||||
override val eID: String
|
||||
get() = LpacJni.es10cGetEid(contextHandle)!!
|
||||
|
||||
|
@ -45,6 +51,18 @@ class LocalProfileAssistantImpl(
|
|||
return LpacJni.downloadProfile(contextHandle, smdp, matchingId, imei, confirmationCode, callback) == 0
|
||||
}
|
||||
|
||||
override fun deleteNotification(seqNumber: Long): Boolean =
|
||||
LpacJni.es10bDeleteNotification(contextHandle, seqNumber) == 0
|
||||
|
||||
override fun handleNotification(seqNumber: Long): Boolean =
|
||||
LpacJni.handleNotification(contextHandle, seqNumber) == 0
|
||||
|
||||
override fun handleLatestNotification(operation: LocalProfileNotification.Operation) {
|
||||
notifications.find { it.profileManagementOperation == operation }?.let {
|
||||
handleNotification(it.seqNumber)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setNickname(iccid: String, nickname: String): Boolean {
|
||||
return LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <syslog.h>
|
||||
#include "lpac-jni.h"
|
||||
#include "lpac-download.h"
|
||||
#include "lpac-notifications.h"
|
||||
#include "interface-wrapper.h"
|
||||
|
||||
JavaVM *jvm = NULL;
|
||||
|
@ -30,6 +31,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
|||
jvm = vm;
|
||||
interface_wrapper_init();
|
||||
lpac_download_init();
|
||||
lpac_notifications_init();
|
||||
|
||||
LPAC_JNI_SETUP_ENV;
|
||||
string_class = (*env)->FindClass(env, "java/lang/String");
|
||||
|
|
95
libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c
Normal file
95
libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
#include "lpac-notifications.h"
|
||||
#include <euicc/es9p.h>
|
||||
#include <euicc/es10b.h>
|
||||
#include <malloc.h>
|
||||
|
||||
jclass local_profile_notification_class;
|
||||
jmethodID local_profile_notification_constructor;
|
||||
|
||||
jclass local_profile_notification_operation_class;
|
||||
jmethodID local_profile_notification_operation_from_string;
|
||||
|
||||
void lpac_notifications_init() {
|
||||
LPAC_JNI_SETUP_ENV;
|
||||
|
||||
local_profile_notification_class =
|
||||
(*env)->FindClass(env, "net/typeblog/lpac_jni/LocalProfileNotification");
|
||||
local_profile_notification_class =
|
||||
(*env)->NewGlobalRef(env, local_profile_notification_class);
|
||||
local_profile_notification_constructor =
|
||||
(*env)->GetMethodID(env, local_profile_notification_class, "<init>",
|
||||
"(JLnet/typeblog/lpac_jni/LocalProfileNotification$Operation;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
|
||||
local_profile_notification_operation_class =
|
||||
(*env)->FindClass(env, "net/typeblog/lpac_jni/LocalProfileNotification$Operation");
|
||||
local_profile_notification_operation_class =
|
||||
(*env)->NewGlobalRef(env, local_profile_notification_operation_class);
|
||||
local_profile_notification_operation_from_string =
|
||||
(*env)->GetStaticMethodID(env, local_profile_notification_operation_class, "fromString",
|
||||
"(Ljava/lang/String;)Lnet/typeblog/lpac_jni/LocalProfileNotification$Operation;");
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_net_typeblog_lpac_1jni_LpacJni_es10bListNotification(JNIEnv *env, jobject thiz, jlong handle) {
|
||||
struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
|
||||
struct es10b_notification_metadata *info;
|
||||
jobject notification = NULL;
|
||||
jobject operation = NULL;
|
||||
jobjectArray ret = NULL;
|
||||
int count;
|
||||
|
||||
if (es10b_list_notification(ctx, &info, &count) < 0)
|
||||
return NULL;
|
||||
|
||||
ret = (*env)->NewObjectArray(env, count, local_profile_notification_class, NULL);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
operation =
|
||||
(*env)->CallStaticObjectMethod(env, local_profile_notification_operation_class,
|
||||
local_profile_notification_operation_from_string,
|
||||
toJString(env, info[i].profileManagementOperation));
|
||||
|
||||
notification =
|
||||
(*env)->NewObject(env, local_profile_notification_class,
|
||||
local_profile_notification_constructor, info[i].seqNumber, operation,
|
||||
toJString(env, info[i].notificationAddress),
|
||||
toJString(env, info[i].iccid));
|
||||
|
||||
(*env)->SetObjectArrayElement(env, ret, i, notification);
|
||||
|
||||
(*env)->DeleteLocalRef(env, operation);
|
||||
(*env)->DeleteLocalRef(env, notification);
|
||||
}
|
||||
|
||||
es10b_notification_metadata_free_all(info, count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_net_typeblog_lpac_1jni_LpacJni_handleNotification(JNIEnv *env, jobject thiz, jlong handle,
|
||||
jlong seq_number) {
|
||||
struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
|
||||
char *b64_payload = NULL;
|
||||
char *receiver = NULL;
|
||||
int res;
|
||||
|
||||
res = es10b_retrieve_notification(ctx, &b64_payload, &receiver, (unsigned long) seq_number);
|
||||
if (res < 0)
|
||||
goto out;
|
||||
|
||||
res = es9p_handle_notification(ctx, receiver, b64_payload);
|
||||
if (res < 0)
|
||||
goto out;
|
||||
|
||||
out:
|
||||
free(b64_payload);
|
||||
free(receiver);
|
||||
return res;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_net_typeblog_lpac_1jni_LpacJni_es10bDeleteNotification(JNIEnv *env, jobject thiz, jlong handle,
|
||||
jlong seq_number) {
|
||||
struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
|
||||
return es10b_remove_notification_from_list(ctx, (unsigned long) seq_number);
|
||||
}
|
6
libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.h
Normal file
6
libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
#include "lpac-jni.h"
|
||||
|
||||
void lpac_notifications_init();
|
Loading…
Reference in a new issue