diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt index 5e87564..a8fa1d5 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt @@ -37,7 +37,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha return EuiccChannelImpl( context.getString(R.string.omapi), port, - intrinsicChannelName = null, OmapiApduInterface( seService!!, port, @@ -68,7 +67,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha return EuiccChannelImpl( context.getString(R.string.usb), FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)), - intrinsicChannelName = usbDevice.productName, UsbApduInterface( conn, bulkIn, diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt index 541f867..4ef6808 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt @@ -16,12 +16,5 @@ interface EuiccChannel { val valid: Boolean - /** - * Intrinsic name of this channel. For device-internal SIM slots, - * this should be null; for USB readers, this should be the name of - * the reader device. - */ - val intrinsicChannelName: String? - fun close() } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt index a281948..79dec34 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt @@ -10,7 +10,6 @@ import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl class EuiccChannelImpl( override val type: String, override val port: UiccPortInfoCompat, - override val intrinsicChannelName: String?, apduInterface: ApduInterface, verboseLoggingFlow: Flow, ignoreTLSCertificateFlow: Flow diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt index 6011f53..ab01f22 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt @@ -31,8 +31,6 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel { override val lpa: LocalProfileAssistant by lpaDelegate override val valid: Boolean get() = channel.valid - override val intrinsicChannelName: String? - get() = channel.intrinsicChannelName override fun close() = channel.close() diff --git a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt index cae7e2e..4b3c3cd 100644 --- a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt +++ b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt @@ -15,5 +15,4 @@ interface AppContainer { val preferenceRepository: PreferenceRepository val uiComponentFactory: UiComponentFactory val euiccChannelFactory: EuiccChannelFactory - val customizableTextProvider: CustomizableTextProvider } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/CustomizableTextProvider.kt b/app-common/src/main/java/im/angry/openeuicc/di/CustomizableTextProvider.kt deleted file mode 100644 index 2c86273..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/di/CustomizableTextProvider.kt +++ /dev/null @@ -1,20 +0,0 @@ -package im.angry.openeuicc.di - -interface CustomizableTextProvider { - /** - * Explanation string for when no eUICC is found on the device. - * This could be different depending on whether the app is privileged or not. - */ - val noEuiccExplanation: String - - /** - * Shown when we timed out switching between profiles. - */ - val profileSwitchingTimeoutMessage: String - - /** - * Format the name of a logical slot; internal only -- not intended for - * other channels such as USB. - */ - fun formatInternalChannelName(logicalSlotId: Int): String -} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt index 9b70099..93fd8b8 100644 --- a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt +++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt @@ -38,8 +38,4 @@ open class DefaultAppContainer(context: Context) : AppContainer { override val euiccChannelFactory by lazy { DefaultEuiccChannelFactory(context) } - - override val customizableTextProvider by lazy { - DefaultCustomizableTextProvider(context) - } } \ No newline at end of file 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 deleted file mode 100644 index b493611..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt +++ /dev/null @@ -1,15 +0,0 @@ -package im.angry.openeuicc.di - -import android.content.Context -import im.angry.openeuicc.common.R - -open class DefaultCustomizableTextProvider(private val context: Context) : CustomizableTextProvider { - override val noEuiccExplanation: String - get() = context.getString(R.string.no_euicc) - - override val profileSwitchingTimeoutMessage: String - get() = context.getString(R.string.enable_disable_timeout) - - override fun formatInternalChannelName(logicalSlotId: Int): String = - context.getString(R.string.channel_name_format, logicalSlotId) -} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt index 36031dd..84b300e 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt @@ -1,18 +1,13 @@ package im.angry.openeuicc.ui import android.annotation.SuppressLint -import android.content.ClipData -import android.content.ClipboardManager -import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.TextView -import android.widget.Toast import androidx.activity.enableEdgeToEdge -import androidx.annotation.StringRes import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -37,13 +32,6 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() { private var logicalSlotId: Int = -1 - data class Item( - @StringRes - val titleResId: Int, - val content: String?, - val copiedToastResId: Int? = null - ) - override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) @@ -53,11 +41,12 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() { supportActionBar!!.setDisplayHomeAsUpEnabled(true) swipeRefresh = requireViewById(R.id.swipe_refresh) - infoList = requireViewById(R.id.recycler_view).also { - it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) - it.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL)) - it.adapter = EuiccInfoAdapter() - } + infoList = requireViewById(R.id.recycler_view) + + infoList.layoutManager = + LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + infoList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL)) + infoList.adapter = EuiccInfoAdapter() logicalSlotId = intent.getIntExtra("logicalSlotId", 0) @@ -92,33 +81,29 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() { lifecycleScope.launch { (infoList.adapter!! as EuiccInfoAdapter).euiccInfoItems = - euiccChannelManager.withEuiccChannel(logicalSlotId, ::buildEuiccInfoItems) + euiccChannelManager.withEuiccChannel(logicalSlotId, ::buildPairs).map { + Pair(getString(it.first), it.second ?: getString(R.string.unknown)) + } swipeRefresh.isRefreshing = false } } - private fun buildEuiccInfoItems(channel: EuiccChannel) = buildList { - add(Item(R.string.euicc_info_access_mode, channel.type)) + private fun buildPairs(channel: EuiccChannel) = buildList { + add(Pair(R.string.euicc_info_access_mode, channel.type)) add( - Item( + Pair( R.string.euicc_info_removable, formatByBoolean(channel.port.card.isRemovable, YES_NO) ) ) - add( - Item( - R.string.euicc_info_eid, - channel.lpa.eID, - copiedToastResId = R.string.toast_eid_copied - ) - ) + add(Pair(R.string.euicc_info_eid, channel.lpa.eID)) channel.lpa.euiccInfo2.let { info -> - add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion)) - add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion)) - add(Item(R.string.euicc_info_pp_version, info?.ppVersion)) - add(Item(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber)) - add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace))) + add(Pair(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion)) + add(Pair(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion)) + add(Pair(R.string.euicc_info_pp_version, info?.ppVersion)) + add(Pair(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber)) + add(Pair(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace))) } channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers -> // SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24) @@ -131,7 +116,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() { PKID_GSMA_TEST_CI.any(signers::contains) -> R.string.euicc_info_ci_gsma_test else -> R.string.euicc_info_ci_unknown } - add(Item(R.string.euicc_info_ci_type, getString(resId))) + add(Pair(R.string.euicc_info_ci_type, getString(resId))) } } @@ -147,34 +132,15 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() { inner class EuiccInfoViewHolder(root: View) : ViewHolder(root) { private val title: TextView = root.requireViewById(R.id.euicc_info_title) private val content: TextView = root.requireViewById(R.id.euicc_info_content) - private var copiedToastResId: Int? = null - init { - root.setOnClickListener { - if (copiedToastResId != null) { - val label = title.text.toString() - getSystemService(ClipboardManager::class.java)!! - .setPrimaryClip(ClipData.newPlainText(label, content.text)) - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - Toast.makeText( - this@EuiccInfoActivity, - copiedToastResId!!, - Toast.LENGTH_SHORT - ).show() - } - } - } - } - - fun bind(item: Item) { - copiedToastResId = item.copiedToastResId - title.setText(item.titleResId) - content.text = item.content ?: getString(R.string.unknown) + fun bind(item: Pair) { + title.text = item.first + content.text = item.second } } inner class EuiccInfoAdapter : RecyclerView.Adapter() { - var euiccInfoItems: List = listOf() + var euiccInfoItems: List> = listOf() @SuppressLint("NotifyDataSetChanged") set(newVal) { field = newVal 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 f806ae0..8e7b158 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 @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Intent -import android.os.Build import android.os.Bundle import android.text.method.PasswordTransformationMethod import android.view.LayoutInflater @@ -262,7 +261,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, invalid = true // Timed out waiting for SIM to come back online, we can no longer assume that the LPA is still valid AlertDialog.Builder(requireContext()).apply { - setMessage(appContainer.customizableTextProvider.profileSwitchingTimeoutMessage) + setMessage(R.string.enable_disable_timeout) setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() requireActivity().finish() @@ -349,8 +348,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, iccid.setOnLongClickListener { requireContext().getSystemService(ClipboardManager::class.java)!! .setPrimaryClip(ClipData.newPlainText("iccid", iccid.text)) - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast - .makeText(requireContext(), R.string.toast_iccid_copied, Toast.LENGTH_SHORT) + Toast.makeText(requireContext(), R.string.toast_iccid_copied, Toast.LENGTH_SHORT) .show() true } diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt index c6ba256..49bfa0f 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt @@ -8,6 +8,7 @@ import android.view.View import android.widget.ScrollView import android.widget.TextView import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout @@ -16,6 +17,7 @@ import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.FileOutputStream import java.util.Date class LogsActivity : AppCompatActivity() { @@ -25,15 +27,15 @@ class LogsActivity : AppCompatActivity() { private lateinit var logStr: String private val saveLogs = - setupLogSaving( - getLogFileName = { - getString( - R.string.logs_filename_template, - SimpleDateFormat.getDateTimeInstance().format(Date()) - ) - }, - getLogText = { logStr } - ) + registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri -> + if (uri == null) return@registerForActivityResult + if (!this::logStr.isInitialized) return@registerForActivityResult + contentResolver.openFileDescriptor(uri, "w")?.use { + FileOutputStream(it.fileDescriptor).use { os -> + os.write(logStr.encodeToByteArray()) + } + } + } override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() @@ -74,7 +76,9 @@ class LogsActivity : AppCompatActivity() { true } R.id.save -> { - saveLogs() + saveLogs.launch(getString(R.string.logs_filename_template, + SimpleDateFormat.getDateTimeInstance().format(Date()) + )) true } else -> super.onOptionsItemSelected(item) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt index 01d0ab2..767e7e0 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt @@ -163,8 +163,7 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { // but it could change in the future euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId) - val channelName = - appContainer.customizableTextProvider.formatInternalChannelName(channel.logicalSlotId) + val channelName = getString(R.string.channel_name_format, channel.logicalSlotId) newPages.add(Page(channel.logicalSlotId, channelName) { appContainer.uiComponentFactory.createEuiccManagementFragment(slotId, portId) }) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/NoEuiccPlaceholderFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/NoEuiccPlaceholderFragment.kt index 7e96af3..e9e44b1 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/NoEuiccPlaceholderFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/NoEuiccPlaceholderFragment.kt @@ -4,20 +4,15 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView import androidx.fragment.app.Fragment import im.angry.openeuicc.common.R -import im.angry.openeuicc.util.* -class NoEuiccPlaceholderFragment : Fragment(), OpenEuiccContextMarker { +class NoEuiccPlaceholderFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - val view = inflater.inflate(R.layout.fragment_no_euicc_placeholder, container, false) - val textView = view.requireViewById(R.id.no_euicc_placeholder) - textView.text = appContainer.customizableTextProvider.noEuiccExplanation - return view + return inflater.inflate(R.layout.fragment_no_euicc_placeholder, container, false) } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt index cb801af..c2cbee3 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt @@ -6,6 +6,7 @@ import android.os.Build import android.os.Bundle import android.provider.Settings import android.widget.Toast +import androidx.datastore.preferences.core.Preferences import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference import androidx.preference.Preference @@ -13,6 +14,7 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import im.angry.openeuicc.common.R import im.angry.openeuicc.util.* +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -58,25 +60,25 @@ open class SettingsFragment: PreferenceFragmentCompat() { } findPreference("pref_notifications_download") - ?.bindBooleanFlow(preferenceRepository.notificationDownloadFlow) + ?.bindBooleanFlow(preferenceRepository.notificationDownloadFlow, PreferenceKeys.NOTIFICATION_DOWNLOAD) findPreference("pref_notifications_delete") - ?.bindBooleanFlow(preferenceRepository.notificationDeleteFlow) + ?.bindBooleanFlow(preferenceRepository.notificationDeleteFlow, PreferenceKeys.NOTIFICATION_DELETE) findPreference("pref_notifications_switch") - ?.bindBooleanFlow(preferenceRepository.notificationSwitchFlow) + ?.bindBooleanFlow(preferenceRepository.notificationSwitchFlow, PreferenceKeys.NOTIFICATION_SWITCH) findPreference("pref_advanced_disable_safeguard_removable_esim") - ?.bindBooleanFlow(preferenceRepository.disableSafeguardFlow) + ?.bindBooleanFlow(preferenceRepository.disableSafeguardFlow, PreferenceKeys.DISABLE_SAFEGUARD_REMOVABLE_ESIM) findPreference("pref_advanced_verbose_logging") - ?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow) + ?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING) findPreference("pref_developer_unfiltered_profile_list") - ?.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow) + ?.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow, PreferenceKeys.UNFILTERED_PROFILE_LIST) findPreference("pref_ignore_tls_certificate") - ?.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow) + ?.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow, PreferenceKeys.IGNORE_TLS_CERTIFICATE) } override fun onStart() { @@ -97,7 +99,10 @@ open class SettingsFragment: PreferenceFragmentCompat() { if (numClicks == 7) { lifecycleScope.launch { - preferenceRepository.developerOptionsEnabledFlow.updatePreference(true) + preferenceRepository.updatePreference( + PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, + true + ) lastToast?.cancel() Toast.makeText( @@ -119,14 +124,14 @@ open class SettingsFragment: PreferenceFragmentCompat() { return true } - private fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper) { + private fun CheckBoxPreference.bindBooleanFlow(flow: Flow, key: Preferences.Key) { lifecycleScope.launch { flow.collect { isChecked = it } } setOnPreferenceChangeListener { _, newValue -> runBlocking { - flow.updatePreference(newValue as Boolean) + preferenceRepository.updatePreference(key, newValue as Boolean) } true } diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SlotSelectFragment.kt new file mode 100644 index 0000000..2c4fe3c --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/ui/SlotSelectFragment.kt @@ -0,0 +1,93 @@ +package im.angry.openeuicc.ui + +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.Spinner +import androidx.appcompat.widget.Toolbar +import im.angry.openeuicc.common.R +import im.angry.openeuicc.core.EuiccChannel +import im.angry.openeuicc.util.* + +class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker { + companion object { + const val TAG = "SlotSelectFragment" + + fun newInstance(slotIds: List, logicalSlotIds: List, portIds: List): SlotSelectFragment { + return SlotSelectFragment().apply { + arguments = Bundle().apply { + putIntArray("slotIds", slotIds.toIntArray()) + putIntArray("logicalSlotIds", logicalSlotIds.toIntArray()) + putIntArray("portIds", portIds.toIntArray()) + } + } + } + } + + interface SlotSelectedListener { + fun onSlotSelected(slotId: Int, portId: Int) + fun onSlotSelectCancelled() + } + + private lateinit var toolbar: Toolbar + private lateinit var spinner: Spinner + private lateinit var adapter: ArrayAdapter + private lateinit var slotIds: IntArray + private lateinit var logicalSlotIds: IntArray + private lateinit var portIds: IntArray + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_slot_select, container, false) + + toolbar = view.requireViewById(R.id.toolbar) + toolbar.setTitle(R.string.slot_select) + toolbar.inflateMenu(R.menu.fragment_slot_select) + + adapter = ArrayAdapter(inflater.context, R.layout.spinner_item) + + spinner = view.requireViewById(R.id.spinner) + spinner.adapter = adapter + + return view + } + + override fun onStart() { + super.onStart() + + slotIds = requireArguments().getIntArray("slotIds")!! + logicalSlotIds = requireArguments().getIntArray("logicalSlotIds")!! + portIds = requireArguments().getIntArray("portIds")!! + + logicalSlotIds.forEach { id -> + adapter.add(getString(R.string.channel_name_format, id)) + } + + toolbar.setNavigationOnClickListener { + (requireActivity() as SlotSelectedListener).onSlotSelectCancelled() + } + toolbar.setOnMenuItemClickListener { + val slotId = slotIds[spinner.selectedItemPosition] + val portId = portIds[spinner.selectedItemPosition] + (requireActivity() as SlotSelectedListener).onSlotSelected(slotId, portId) + dismiss() + true + } + } + + override fun onResume() { + super.onResume() + setWidthPercent(75) + } + + override fun onCancel(dialog: DialogInterface) { + super.onCancel(dialog) + (requireActivity() as SlotSelectedListener).onSlotSelectCancelled() + } +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt index e282196..fbbd1a0 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt @@ -6,8 +6,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts import im.angry.openeuicc.common.R import im.angry.openeuicc.util.* +import java.io.FileOutputStream import java.util.Date class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() { @@ -19,15 +21,14 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS private lateinit var diagnosticTextView: TextView private val saveDiagnostics = - setupLogSaving( - getLogFileName = { - getString( - R.string.download_wizard_diagnostics_file_template, - SimpleDateFormat.getDateTimeInstance().format(Date()) - ) - }, - getLogText = { diagnosticTextView.text.toString() } - ) + registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri -> + if (uri == null) return@registerForActivityResult + requireActivity().contentResolver.openFileDescriptor(uri, "w")?.use { + FileOutputStream(it.fileDescriptor).use { os -> + os.write(diagnosticTextView.text.toString().encodeToByteArray()) + } + } + } override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null @@ -40,7 +41,12 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS ): View? { val view = inflater.inflate(R.layout.fragment_download_diagnostics, container, false) view.requireViewById(R.id.download_wizard_diagnostics_save).setOnClickListener { - saveDiagnostics() + saveDiagnostics.launch( + getString( + R.string.download_wizard_diagnostics_file_template, + SimpleDateFormat.getDateTimeInstance().format(Date()) + ) + ) } diagnosticTextView = view.requireViewById(R.id.download_wizard_diagnostics_text) return view diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt index f16a086..54dbc08 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt @@ -35,8 +35,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt val eID: String, val freeSpace: Int, val imei: String, - val enabledProfileName: String?, - val intrinsicChannelName: String?, + val enabledProfileName: String? ) private var loaded = false @@ -62,9 +61,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt setMessage(R.string.profile_download_low_nvram_message) setCancelable(true) setPositiveButton(android.R.string.ok, null) - setNegativeButton(android.R.string.cancel) { _, _ -> - requireActivity().finish() - } + setNegativeButton(android.R.string.cancel, null) show() } } @@ -109,8 +106,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt } catch (e: Exception) { "" }, - channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName, - channel.intrinsicChannelName, + channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName ) } }.toList().sortedBy { it.logicalSlotId } @@ -181,9 +177,9 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt } title.text = if (item.logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { - item.intrinsicChannelName ?: root.context.getString(R.string.usb) + root.context.getString(R.string.usb) } else { - appContainer.customizableTextProvider.formatInternalChannelName(item.logicalSlotId) + root.context.getString(R.string.download_wizard_slot_title, item.logicalSlotId) } eID.text = item.eID activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown) diff --git a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt index f5e3ca2..e768fa9 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt @@ -19,7 +19,7 @@ val Context.preferenceRepository: PreferenceRepository val Fragment.preferenceRepository: PreferenceRepository get() = requireContext().preferenceRepository -internal object PreferenceKeys { +object PreferenceKeys { // ---- Profile Notifications ---- val NOTIFICATION_DOWNLOAD = booleanPreferencesKey("notification_download") val NOTIFICATION_DELETE = booleanPreferencesKey("notification_delete") @@ -51,22 +51,9 @@ class PreferenceRepository(private val context: Context) { val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false) val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false) - private fun bindFlow(key: Preferences.Key, defaultValue: T): PreferenceFlowWrapper = - PreferenceFlowWrapper(context, key, defaultValue) -} - -class PreferenceFlowWrapper private constructor( - private val context: Context, - private val key: Preferences.Key, - inner: Flow -) : Flow by inner { - internal constructor(context: Context, key: Preferences.Key, defaultValue: T) : this( - context, - key, + private fun bindFlow(key: Preferences.Key, defaultValue: T): Flow = context.dataStore.data.map { it[key] ?: defaultValue } - ) - suspend fun updatePreference(value: T) { + suspend fun updatePreference(key: Preferences.Key, value: T) = context.dataStore.edit { it[key] = value } - } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/util/UiUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/UiUtils.kt index c8e481c..fbede87 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/UiUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/UiUtils.kt @@ -1,23 +1,17 @@ package im.angry.openeuicc.util -import android.content.Context -import android.content.Intent import android.content.res.Resources import android.graphics.Rect import android.view.View import android.view.ViewGroup -import androidx.activity.result.ActivityResultCaller -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment import im.angry.openeuicc.common.R -import java.io.FileOutputStream // Source: /** @@ -75,44 +69,4 @@ fun setupRootViewInsets(view: ViewGroup) { WindowInsetsCompat.CONSUMED } -} - -fun T.setupLogSaving( - getLogFileName: () -> String, - getLogText: () -> String -): () -> Unit { - val launchSaveIntent = - registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri -> - if (uri == null) return@registerForActivityResult - - val context = when (this@setupLogSaving) { - is Context -> this@setupLogSaving - is Fragment -> requireContext() - else -> throw IllegalArgumentException("Must be either Context or Fragment!") - } - - context.contentResolver.openFileDescriptor(uri, "w")?.use { - FileOutputStream(it.fileDescriptor).use { os -> - os.write(getLogText().encodeToByteArray()) - } - } - - AlertDialog.Builder(context).apply { - setMessage(R.string.logs_saved_message) - setNegativeButton(R.string.no) { _, _ -> } - setPositiveButton(R.string.yes) { _, _ -> - val intent = Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(Intent.EXTRA_STREAM, uri) - } - - context.startActivity(Intent.createChooser(intent, null)) - } - }.show() - } - - return { - launchSaveIntent.launch(getLogFileName()) - } } \ 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 7d99cb7..d25af49 100644 --- a/app-common/src/main/res/values-ja/strings.xml +++ b/app-common/src/main/res/values-ja/strings.xml @@ -43,6 +43,7 @@ 戻る 次へ ダウンロードする eSIM を選択または確認: + 論理スロット %d タイプ: リムーバブル 内部 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 551435b..3bb0d04 100644 --- a/app-common/src/main/res/values-zh-rCN/strings.xml +++ b/app-common/src/main/res/values-zh-rCN/strings.xml @@ -81,6 +81,7 @@ 返回 下一步 请选择或确认下载目标 eSIM 卡槽: + 逻辑卡槽 %d 类型: 可插拔 内置 diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index e349c68..61823f7 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -31,7 +31,6 @@ Nickname cannot be longer than 64 characters Confirmation string mismatch ICCID copied to clipboard - EID copied to clipboard Select Slot Select @@ -65,6 +64,7 @@ Back Next Select or confirm the eSIM you would like to download to: + Logical slot %d Type: Removable Internal @@ -95,8 +95,6 @@ Save Diagnostics at %s - Logs have been saved to the selected path. Would you like to share the log through another app? - New nickname Are you sure you want to delete the profile %s? This operation is irreversible. diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/di/UnprivilegedAppContainer.kt b/app-unpriv/src/main/java/im/angry/openeuicc/di/UnprivilegedAppContainer.kt index 4dbfe41..22d5a62 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/di/UnprivilegedAppContainer.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/di/UnprivilegedAppContainer.kt @@ -6,8 +6,4 @@ class UnprivilegedAppContainer(context: Context) : DefaultAppContainer(context) override val uiComponentFactory by lazy { UnprivilegedUiComponentFactory() } - - override val customizableTextProvider by lazy { - UnprivilegedCustomizableTextProvider(context) - } } \ No newline at end of file diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/di/UnprivilegedCustomizableTextProvider.kt b/app-unpriv/src/main/java/im/angry/openeuicc/di/UnprivilegedCustomizableTextProvider.kt deleted file mode 100644 index 929ce84..0000000 --- a/app-unpriv/src/main/java/im/angry/openeuicc/di/UnprivilegedCustomizableTextProvider.kt +++ /dev/null @@ -1,10 +0,0 @@ -package im.angry.openeuicc.di - -import android.content.Context -import im.angry.easyeuicc.R - -class UnprivilegedCustomizableTextProvider(private val context: Context) : - DefaultCustomizableTextProvider(context) { - override fun formatInternalChannelName(logicalSlotId: Int): String = - context.getString(R.string.channel_name_format_unpriv, logicalSlotId) -} \ No newline at end of file diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt index 1ef3e89..1ca9d0f 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt @@ -3,7 +3,6 @@ package im.angry.openeuicc.ui import android.content.ClipData import android.content.ClipboardManager import android.content.pm.PackageManager -import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.preference.Preference @@ -36,8 +35,7 @@ class UnprivilegedSettingsFragment : SettingsFragment() { setOnPreferenceClickListener { requireContext().getSystemService(ClipboardManager::class.java)!! .setPrimaryClip(ClipData.newPlainText("ara-m", summary)) - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast - .makeText(requireContext(), R.string.toast_ara_m_copied, Toast.LENGTH_SHORT) + Toast.makeText(requireContext(), R.string.toast_ara_m_copied, Toast.LENGTH_SHORT) .show() true } diff --git a/app-unpriv/src/main/res/values/strings.xml b/app-unpriv/src/main/res/values/strings.xml index afed295..9d80b0e 100644 --- a/app-unpriv/src/main/res/values/strings.xml +++ b/app-unpriv/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ EasyEUICC - SIM %d + SIM %d Compatibility Check Open SIM Toolkit diff --git a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt index b690c79..1537fc9 100644 --- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt +++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt @@ -30,7 +30,6 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto return EuiccChannelImpl( context.getString(R.string.telephony_manager), port, - intrinsicChannelName = null, TelephonyManagerApduInterface( port, tm, diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt index d821e68..c5896f2 100644 --- a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt @@ -23,8 +23,4 @@ class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) { override val euiccChannelFactory by lazy { PrivilegedEuiccChannelFactory(context) } - - override val customizableTextProvider by lazy { - PrivilegedCustomizableTextProvider(context) - } } \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedCustomizableTextProvider.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedCustomizableTextProvider.kt deleted file mode 100644 index e53832f..0000000 --- a/app/src/main/java/im/angry/openeuicc/di/PrivilegedCustomizableTextProvider.kt +++ /dev/null @@ -1,10 +0,0 @@ -package im.angry.openeuicc.di - -import android.content.Context -import im.angry.openeuicc.R - -class PrivilegedCustomizableTextProvider(private val context: Context) : - DefaultCustomizableTextProvider(context) { - override val noEuiccExplanation: String - get() = context.getString(R.string.no_euicc_priv) -} \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 29f3ef6..5ff6740 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1,6 +1,6 @@ - このデバイスで eUICC が見つかりません。\nデバイスによってはアプリのメニューからデュアル SIM を有効化する必要があります。 + このデバイスで eUICC が見つかりません。\nデバイスによってはアプリのメニューからデュアル SIM を有効化する必要があります。 TelephonyManager (特権) デュアル SIM DSDS の状態が切り替わりました。モデムが再起動するまでお待ちください。 diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 18497b2..9b34bb8 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,6 +1,6 @@ - 在此设备上找不到 eUICC 芯片。\n在某些设备上,您可能需要先在此应用的菜单中启用双卡支持。 + 在此设备上找不到 eUICC 芯片。\n在某些设备上,您可能需要先在此应用的菜单中启用双卡支持。 双卡 双卡支持状态已切换。请等待基带重新启动。 此卡槽支持多个启用配置文件 (MEP)。要启用或禁用此功能,请使用\"卡槽映射\"工具。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 47c88bd..bb2233b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ OpenEUICC - No eUICC found on this device.\nOn some devices, you may need to enable dual SIM first in the menu of this app. + No eUICC found on this device.\nOn some devices, you may need to enable dual SIM first in the menu of this app. TelephonyManager (Privileged) Dual SIM