diff --git a/.gitmodules b/.gitmodules
index f888959..863f185 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
[submodule "libs/lpac-jni/src/main/jni/lpac"]
path = libs/lpac-jni/src/main/jni/lpac
- url = https://github.com/estkme/lpac
+ url = https://github.com/estkme-group/lpac.git
diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt
index b493611..76227fd 100644
--- a/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt
@@ -8,7 +8,7 @@ open class DefaultCustomizableTextProvider(private val context: Context) : Custo
get() = context.getString(R.string.no_euicc)
override val profileSwitchingTimeoutMessage: String
- get() = context.getString(R.string.enable_disable_timeout)
+ get() = context.getString(R.string.profile_switch_timeout)
override fun formatInternalChannelName(logicalSlotId: Int): String =
context.getString(R.string.channel_name_format, logicalSlotId)
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
index 3c94c6c..016e96f 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
@@ -253,7 +253,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
if (!isUsb) {
withContext(Dispatchers.Main) {
AlertDialog.Builder(requireContext()).apply {
- setMessage(R.string.switch_did_not_refresh)
+ setMessage(R.string.profile_switch_did_not_refresh)
setPositiveButton(android.R.string.ok) { dialog, _ ->
dialog.dismiss()
requireActivity().finish()
@@ -380,9 +380,9 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
state.setText(
if (profile.isEnabled) {
- R.string.enabled
+ R.string.profile_state_enabled
} else {
- R.string.disabled
+ R.string.profile_state_disabled
}
)
provider.text = profile.providerName
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt
index c588254..281e625 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt
@@ -65,7 +65,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
super.onViewCreated(view, savedInstanceState)
profileRenameNewName.editText!!.setText(currentName)
toolbar.apply {
- setTitle(R.string.rename)
+ setTitle(R.string.profile_rename)
setNavigationOnClickListener {
if (!renaming) dismiss()
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/SimplifiedErrorHandling.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/SimplifiedErrorHandling.kt
new file mode 100644
index 0000000..14a4f57
--- /dev/null
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/SimplifiedErrorHandling.kt
@@ -0,0 +1,145 @@
+package im.angry.openeuicc.ui.wizard
+
+import androidx.annotation.StringRes
+import im.angry.openeuicc.common.R
+import net.typeblog.lpac_jni.LocalProfileAssistant
+import org.json.JSONObject
+import java.net.NoRouteToHostException
+import java.net.PortUnreachableException
+import java.net.SocketException
+import java.net.SocketTimeoutException
+import java.net.UnknownHostException
+import javax.net.ssl.SSLException
+
+object SimplifiedErrorHandling {
+ enum class ErrorCode(@StringRes val titleResId: Int, @StringRes val suggestResId: Int?) {
+ ICCIDAlready(
+ R.string.download_wizard_error_iccid_already,
+ R.string.download_wizard_error_suggest_profile_installed
+ ),
+ InsufficientMemory(
+ R.string.download_wizard_error_insufficient_memory,
+ R.string.download_wizard_error_suggest_insufficient_memory
+ ),
+ UnsupportedProfile(
+ R.string.download_wizard_error_unsupported_profile,
+ null
+ ),
+ CardInternalError(
+ R.string.download_wizard_error_card_internal_error,
+ null
+ ),
+ EIDMismatch(
+ R.string.download_wizard_error_eid_mismatch,
+ R.string.download_wizard_error_suggest_contact_reissue
+ ),
+ UnreleasedProfile(
+ R.string.download_wizard_error_profile_unreleased,
+ R.string.download_wizard_error_suggest_contact_reissue
+ ),
+ MatchingIDRefused(
+ R.string.download_wizard_error_matching_id_refused,
+ R.string.download_wizard_error_suggest_contact_carrier
+ ),
+ ProfileRetriesExceeded(
+ R.string.download_wizard_error_profile_retries_exceeded,
+ R.string.download_wizard_error_suggest_contact_carrier
+ ),
+ ConfirmationCodeMissing(
+ R.string.download_wizard_error_confirmation_code_missing,
+ R.string.download_wizard_error_suggest_contact_carrier
+ ),
+ ConfirmationCodeRefused(
+ R.string.download_wizard_error_confirmation_code_refused,
+ R.string.download_wizard_error_suggest_contact_carrier
+ ),
+ ConfirmationCodeRetriesExceeded(
+ R.string.download_wizard_error_confirmation_code_retries_exceeded,
+ R.string.download_wizard_error_suggest_contact_carrier
+ ),
+ ProfileExpired(
+ R.string.download_wizard_error_profile_expired,
+ R.string.download_wizard_error_suggest_contact_carrier
+ ),
+ UnknownHost(
+ R.string.download_wizard_error_unknown_hostname,
+ null
+ ),
+ NetworkUnreachable(
+ R.string.download_wizard_error_network_unreachable,
+ R.string.download_wizard_error_suggest_network_unreachable
+ ),
+ TLSError(
+ R.string.download_wizard_error_tls_certificate,
+ null
+ )
+ }
+
+ private val httpErrors = buildMap {
+ // Stage: AuthenticateClient
+ put("8.1" to "4.8", ErrorCode.InsufficientMemory)
+ put("8.1.1" to "3.8", ErrorCode.EIDMismatch)
+ put("8.2" to "1.2", ErrorCode.UnreleasedProfile)
+ put("8.2.6" to "3.8", ErrorCode.MatchingIDRefused)
+ put("8.8.5" to "6.4", ErrorCode.ProfileRetriesExceeded)
+
+ // Stage: GetBoundProfilePackage
+ put("8.2.7" to "2.2", ErrorCode.ConfirmationCodeMissing)
+ put("8.2.7" to "3.8", ErrorCode.ConfirmationCodeRefused)
+ put("8.2.7" to "6.4", ErrorCode.ConfirmationCodeRetriesExceeded)
+
+ // Stage: AuthenticateClient, GetBoundProfilePackage
+ put("8.8.5" to "4.10", ErrorCode.ProfileExpired)
+ }
+
+ fun toSimplifiedDownloadError(exc: LocalProfileAssistant.ProfileDownloadException) = when {
+ exc.lpaErrorReason != "ES10B_ERROR_REASON_UNDEFINED" -> toSimplifiedLPAErrorReason(exc.lpaErrorReason)
+ exc.lastHttpResponse?.rcode == 200 -> toSimplifiedHTTPResponse(exc.lastHttpResponse!!)
+ exc.lastHttpException != null -> toSimplifiedHTTPException(exc.lastHttpException!!)
+ exc.lastApduResponse != null -> toSimplifiedAPDUResponse(exc.lastApduResponse!!)
+ else -> null
+ }
+
+ private fun toSimplifiedLPAErrorReason(reason: String) = when (reason) {
+ "ES10B_ERROR_REASON_UNSUPPORTED_CRT_VALUES" -> ErrorCode.UnsupportedProfile
+ "ES10B_ERROR_REASON_UNSUPPORTED_REMOTE_OPERATION_TYPE" -> ErrorCode.UnsupportedProfile
+ "ES10B_ERROR_REASON_UNSUPPORTED_PROFILE_CLASS" -> ErrorCode.UnsupportedProfile
+ "ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_ICCID_ALREADY_EXISTS_ON_EUICC" -> ErrorCode.ICCIDAlready
+ "ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_INSUFFICIENT_MEMORY_FOR_PROFILE" -> ErrorCode.InsufficientMemory
+ "ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_INTERRUPTION" -> ErrorCode.CardInternalError
+ "ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_PE_PROCESSING_ERROR" -> ErrorCode.CardInternalError
+ else -> null
+ }
+
+ private fun toSimplifiedHTTPResponse(response: net.typeblog.lpac_jni.HttpInterface.HttpResponse): ErrorCode? {
+ if (response.data.first().toInt() != '{'.code) return null
+ val response = JSONObject(response.data.decodeToString())
+ val statusCodeData = response.optJSONObject("header")
+ ?.optJSONObject("functionExecutionStatus")
+ ?.optJSONObject("statusCodeData")
+ ?: return null
+ val subjectCode = statusCodeData.optString("subjectCode")
+ val reasonCode = statusCodeData.optString("reasonCode")
+ return httpErrors[subjectCode to reasonCode]
+ }
+
+ private fun toSimplifiedHTTPException(exc: Exception) = when (exc) {
+ is SSLException -> ErrorCode.TLSError
+ is UnknownHostException -> ErrorCode.UnknownHost
+ is NoRouteToHostException -> ErrorCode.NetworkUnreachable
+ is PortUnreachableException -> ErrorCode.NetworkUnreachable
+ is SocketTimeoutException -> ErrorCode.NetworkUnreachable
+ is SocketException -> exc.message
+ ?.contains("Connection reset", ignoreCase = true)
+ ?.let { if (it) ErrorCode.NetworkUnreachable else null }
+ else -> null
+ }
+
+ private fun toSimplifiedAPDUResponse(resp: ByteArray): ErrorCode? {
+ val isSuccess = resp.size >= 2 &&
+ resp[resp.size - 2] == 0x90.toByte() &&
+ resp[resp.size - 1] == 0x00.toByte()
+ if (isSuccess) return null
+ return ErrorCode.CardInternalError
+ }
+}
diff --git a/app-common/src/main/res/layout/euicc_profile.xml b/app-common/src/main/res/layout/euicc_profile.xml
index 74c1d7a..021c53b 100644
--- a/app-common/src/main/res/layout/euicc_profile.xml
+++ b/app-common/src/main/res/layout/euicc_profile.xml
@@ -54,7 +54,7 @@
\ No newline at end of file
diff --git a/app-common/src/main/res/menu/profile_options.xml b/app-common/src/main/res/menu/profile_options.xml
index 6add53d..60722d6 100644
--- a/app-common/src/main/res/menu/profile_options.xml
+++ b/app-common/src/main/res/menu/profile_options.xml
@@ -2,18 +2,18 @@
\ No newline at end of file
diff --git a/app-common/src/main/res/values-ja/strings.xml b/app-common/src/main/res/values-ja/strings.xml
index d51e2c7..e631d52 100644
--- a/app-common/src/main/res/values-ja/strings.xml
+++ b/app-common/src/main/res/values-ja/strings.xml
@@ -7,19 +7,19 @@
ヘルプ
スロットを再読み込み
論理スロット %d
- 有効済み
- 無効済み
- プロバイダー:
+ 有効済み
+ 無効済み
+ プロバイダー:
クラス:
テスト中
プロビジョニング
稼働中
- 有効化
- 無効化
- 削除
- 名前を変更
- eSIM チップがプロファイルの切り替えの待機中にタイムアウトしました。これはデバイスのモデムファームウェアのバグの可能性があります。機内モードに切り替えるかアプリを再起動、デバイスを再起動してください。
- 操作は成功しましたが、デバイスのモデムが更新を拒否しました。新しいプロファイルを使用するには機内モードに切り替えるか、再起動する必要があります。
+ 有効化
+ 無効化
+ 削除
+ 名前を変更
+ eSIM チップがプロファイルの切り替えの待機中にタイムアウトしました。これはデバイスのモデムファームウェアのバグの可能性があります。機内モードに切り替えるかアプリを再起動、デバイスを再起動してください。
+ 操作は成功しましたが、デバイスのモデムが更新を拒否しました。新しいプロファイルを使用するには機内モードに切り替えるか、再起動する必要があります。
新しい eSIM プロファイルに切り替えることができません。
確認文字列が一致しません
ICCID をクリップボードにコピーしました
diff --git a/app-common/src/main/res/values-zh-rCN/strings.xml b/app-common/src/main/res/values-zh-rCN/strings.xml
index 32ced90..b0938d3 100644
--- a/app-common/src/main/res/values-zh-rCN/strings.xml
+++ b/app-common/src/main/res/values-zh-rCN/strings.xml
@@ -6,16 +6,16 @@
帮助
重新加载卡槽
逻辑卡槽 %d
- 已启用
- 已禁用
- 提供商:
+ 已启用
+ 已禁用
+ 提供商:
类型:
- 启用
- 禁用
- 删除
- 重命名
- 等待 eSIM 芯片切换配置文件时超时。这可能是您手机基带固件中的一个错误。请尝试切换飞行模式、重新启动应用程序或重新启动手机
- 操作成功, 但是您手机的基带拒绝刷新。您可能需要切换飞行模式或重新启动,以便使用新的配置文件。
+ 启用
+ 禁用
+ 删除
+ 重命名
+ 等待 eSIM 芯片切换配置文件时超时。这可能是您手机基带固件中的一个错误。请尝试切换飞行模式、重新启动应用程序或重新启动手机
+ 操作成功, 但是您手机的基带拒绝刷新。您可能需要切换飞行模式或重新启动,以便使用新的配置文件。
无法切换到新的 eSIM 配置文件。
输入的确认文本不匹配
已复制 ICCID 到剪贴板
diff --git a/app-common/src/main/res/values-zh-rTW/strings.xml b/app-common/src/main/res/values-zh-rTW/strings.xml
index 5136bf7..e0334c6 100644
--- a/app-common/src/main/res/values-zh-rTW/strings.xml
+++ b/app-common/src/main/res/values-zh-rTW/strings.xml
@@ -6,16 +6,16 @@
幫助
重新載入卡槽
虛擬卡槽 %d
- 已啟用
- 已停用
- 電信業者:
+ 已啟用
+ 已停用
+ 電信業者:
類型:
- 啟用
- 停用
- 刪除
- 重新命名
- 等待 eSIM 切換設定檔時逾時。這可能是您手機基頻處理器韌體中的一個錯誤。請嘗試切換飛航模式、重新啟動應用程式或重新啟動手機
- 操作成功, 但是您手機的基頻處理器沒有重新整理。您可能需要切換飛航模式或重新啟動,以便使用新的設定檔。
+ 啟用
+ 停用
+ 刪除
+ 重新命名
+ 等待 eSIM 切換設定檔時逾時。這可能是您手機基頻處理器韌體中的一個錯誤。請嘗試切換飛航模式、重新啟動應用程式或重新啟動手機
+ 操作成功, 但是您手機的基頻處理器沒有重新整理。您可能需要切換飛航模式或重新啟動,以便使用新的設定檔。
無法切換到新的 eSIM 設定檔。
輸入的確認文字不匹配
已複製 ICCID 到剪貼簿
diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml
index 38bb976..f28318f 100644
--- a/app-common/src/main/res/values/strings.xml
+++ b/app-common/src/main/res/values/strings.xml
@@ -11,23 +11,24 @@
USB
OpenMobile API (OMAPI)
- Enabled
- Disabled
- Provider:
+
+ Enabled
+ Disabled
+ Provider:
Class:
Testing
Provisioning
Operational
- ICCID:
+ ICCID:
#%d
- Enable
- Disable
- Delete
- Rename
+ Enable
+ Disable
+ Delete
+ Rename
- Timed out waiting for the eSIM chip to switch profiles. This may be a bug in your phone\'s modem firmware. Try toggling airplane mode, restarting the application, or rebooting the phone.
- The operation was successful, but your phone\'s modem refused to refresh. You might need to toggle airplane mode or reboot in order to use the new profile.
+ Timed out waiting for the eSIM chip to switch profiles. This may be a bug in your phone\'s modem firmware. Try toggling airplane mode, restarting the application, or rebooting the phone.
+ The operation was successful, but your phone\'s modem refused to refresh. You might need to toggle airplane mode or reboot in order to use the new profile.
Cannot switch to new eSIM profile.
Confirmation string mismatch
@@ -102,6 +103,26 @@
Last APDU exception:
Save
Diagnostics at %s
+ This eSIM profile is installed, Cannot be reinstalled.
+ Sorry, The remaining capacity of this eSIM chip cannot be used to install this eSIM profile.
+ Sorry, This eSIM profile is unsupported.
+ An error occurred inside the card.
+ This eSIM profile has been installed on another device.
+ This eSIM profile has been unreleased.
+ This eSIM activation code is invalid.
+ The maximum number of retries for the eSIM profile has been exceeded.
+ Please enter the confirmation code to continue.
+ The confirmation code you entered is invalid.
+ This eSIM profile has been expired.
+ The maximum number of retries for the Confirmation Code has been exceeded.
+ Unknown SM-DP+ address
+ The current network is unreachable
+ TLS certificate error, this eSIM profile is not supported
+ You are trying to reinstall an already installed eSIM profile
+ Please delete an eSIM profile and try again
+ Please contact your carrier for assistance.
+ Please contact your carrier to reissue this eSIM profile.
+ The current network is unavailable. Please try again after changing the network.
Logs have been saved to the selected path. Would you like to share the log through another app?
diff --git a/app-unpriv/src/main/AndroidManifest.xml b/app-unpriv/src/main/AndroidManifest.xml
index ce985cd..9c14163 100644
--- a/app-unpriv/src/main/AndroidManifest.xml
+++ b/app-unpriv/src/main/AndroidManifest.xml
@@ -24,9 +24,9 @@
+ android:label="@string/quick_compatibility" />
by lazy { getCompatibilityChecks(this) }
- private val adapter = CompatibilityChecksAdapter()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- enableEdgeToEdge()
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_compatibility_check)
- setSupportActionBar(requireViewById(im.angry.openeuicc.common.R.id.toolbar))
- setupToolbarInsets()
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
-
- compatibilityCheckList = requireViewById(R.id.recycler_view).also {
- it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
- it.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
- it.adapter = adapter
- }
-
- setupRootViewInsets(compatibilityCheckList)
- }
-
- @SuppressLint("NotifyDataSetChanged")
- override fun onStart() {
- super.onStart()
- lifecycleScope.launch {
- compatibilityChecks.executeAll { adapter.notifyDataSetChanged() }
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean =
- when (item.itemId) {
- android.R.id.home -> {
- finish()
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
-
- inner class ViewHolder(private val root: View): RecyclerView.ViewHolder(root) {
- private val titleView: TextView = root.requireViewById(R.id.compatibility_check_title)
- private val descView: TextView = root.requireViewById(R.id.compatibility_check_desc)
- private val statusContainer: ViewGroup = root.requireViewById(R.id.compatibility_check_status_container)
-
- fun bindItem(item: CompatibilityCheck) {
- titleView.text = item.title
- descView.text = Html.fromHtml(item.description, Html.FROM_HTML_MODE_COMPACT)
-
- statusContainer.children.forEach {
- it.isVisible = false
- }
-
- val viewId = when (item.state) {
- CompatibilityCheck.State.SUCCESS -> R.id.compatibility_check_checkmark
- CompatibilityCheck.State.FAILURE -> R.id.compatibility_check_error
- CompatibilityCheck.State.FAILURE_UNKNOWN -> R.id.compatibility_check_unknown
- else -> R.id.compatibility_check_progress_bar
- }
- root.requireViewById(viewId).isVisible = true
- }
- }
-
- inner class CompatibilityChecksAdapter: RecyclerView.Adapter() {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
- ViewHolder(layoutInflater.inflate(R.layout.compatibility_check_item, parent, false))
-
- override fun getItemCount(): Int =
- compatibilityChecks.indexOfLast { it.state != CompatibilityCheck.State.NOT_STARTED } + 1
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- holder.bindItem(compatibilityChecks[position])
- }
- }
-}
\ No newline at end of file
diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityActivity.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityActivity.kt
new file mode 100644
index 0000000..d5e599f
--- /dev/null
+++ b/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityActivity.kt
@@ -0,0 +1,24 @@
+package im.angry.openeuicc.ui
+
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import im.angry.easyeuicc.R
+import im.angry.openeuicc.di.UnprivilegedUiComponentFactory
+import im.angry.openeuicc.util.OpenEuiccContextMarker
+
+class QuickCompatibilityActivity : AppCompatActivity(), OpenEuiccContextMarker {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_quick_compatibility)
+
+ val quickCompatibilityFragment =
+ (appContainer.uiComponentFactory as UnprivilegedUiComponentFactory)
+ .createQuickCompatibilityFragment()
+
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.quick_compatibility_container, quickCompatibilityFragment)
+ .commit()
+ }
+}
diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityFragment.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityFragment.kt
new file mode 100644
index 0000000..9b41730
--- /dev/null
+++ b/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityFragment.kt
@@ -0,0 +1,186 @@
+package im.angry.openeuicc.ui
+
+import android.content.pm.PackageManager
+import android.icu.text.ListFormatter
+import android.os.Build
+import android.os.Bundle
+import android.se.omapi.Reader
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.CheckBox
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import im.angry.easyeuicc.R
+import im.angry.openeuicc.util.EUICC_DEFAULT_ISDR_AID
+import im.angry.openeuicc.util.UnprivilegedEuiccContextMarker
+import im.angry.openeuicc.util.connectSEService
+import im.angry.openeuicc.util.decodeHex
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+
+open class QuickCompatibilityFragment : Fragment(), UnprivilegedEuiccContextMarker {
+ companion object {
+ enum class Compatibility {
+ COMPATIBLE,
+ NOT_COMPATIBLE,
+ }
+
+ data class CompatibilityResult(
+ val compatibility: Compatibility,
+ val slotsOmapi: List = emptyList(),
+ val slotsIsdr: List = emptyList()
+ )
+ }
+
+ private val conclusion: TextView by lazy {
+ requireView().requireViewById(R.id.quick_compatibility_conclusion)
+ }
+
+ private val resultSlots: TextView by lazy {
+ requireView().requireViewById(R.id.quick_compatibility_result_slots)
+ }
+
+ private val resultSlotsIsdr: TextView by lazy {
+ requireView().requireViewById(R.id.quick_compatibility_result_slots_isdr)
+ }
+
+ private val resultNotes: TextView by lazy {
+ requireView().requireViewById(R.id.quick_compatibility_result_notes)
+ }
+
+ private val skipCheckBox: CheckBox by lazy {
+ requireView().requireViewById(R.id.quick_compatibility_skip)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = inflater.inflate(R.layout.fragment_quick_compatibility, container, false).apply {
+ requireViewById(R.id.quick_compatibility_device_information)
+ .text = formatDeviceInformation()
+ requireViewById