| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- package im.angry.openeuicc.ui
- import android.annotation.SuppressLint
- import android.os.Bundle
- import android.text.method.PasswordTransformationMethod
- import android.util.Log
- import android.view.LayoutInflater
- import android.view.MenuItem
- import android.view.View
- import android.view.ViewGroup
- import android.widget.PopupMenu
- import android.widget.Toast
- import androidx.fragment.app.Fragment
- import androidx.lifecycle.lifecycleScope
- import androidx.recyclerview.widget.LinearLayoutManager
- import androidx.recyclerview.widget.RecyclerView
- import com.truphone.lpa.LocalProfileInfo
- import com.truphone.lpad.progress.Progress
- import im.angry.openeuicc.R
- import im.angry.openeuicc.databinding.EuiccProfileBinding
- import im.angry.openeuicc.databinding.FragmentEuiccBinding
- import im.angry.openeuicc.util.openEuiccApplication
- import im.angry.openeuicc.util.tryRefreshCachedEuiccInfo
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.launch
- import kotlinx.coroutines.withContext
- import java.lang.Exception
- class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener {
- companion object {
- const val TAG = "EuiccManagementFragment"
- fun newInstance(slotId: Int): EuiccManagementFragment =
- newInstanceEuicc(EuiccManagementFragment::class.java, slotId)
- }
- private var _binding: FragmentEuiccBinding? = null
- private val binding get() = _binding!!
- private val adapter = EuiccProfileAdapter(listOf())
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = FragmentEuiccBinding.inflate(inflater, container, false)
- return binding.root
- }
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- binding.swipeRefresh.setOnRefreshListener { refresh() }
- binding.profileList.adapter = adapter
- binding.profileList.layoutManager =
- LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
- binding.fab.setOnClickListener {
- ProfileDownloadFragment.newInstance(slotId)
- .show(childFragmentManager, ProfileDownloadFragment.TAG)
- }
- }
- override fun onStart() {
- super.onStart()
- refresh()
- }
- override fun onEuiccProfilesChanged() {
- refresh()
- }
- @SuppressLint("NotifyDataSetChanged")
- private fun refresh() {
- binding.swipeRefresh.isRefreshing = true
- lifecycleScope.launch {
- val profiles = withContext(Dispatchers.IO) {
- openEuiccApplication.subscriptionManager.tryRefreshCachedEuiccInfo(channel.cardId)
- channel.lpa.profiles
- }
- withContext(Dispatchers.Main) {
- adapter.profiles = profiles.filter { it.profileClass != LocalProfileInfo.Clazz.Testing }
- adapter.notifyDataSetChanged()
- binding.swipeRefresh.isRefreshing = false
- }
- }
- }
- private fun enableOrDisableProfile(iccid: String, enable: Boolean) {
- binding.swipeRefresh.isRefreshing = true
- binding.swipeRefresh.isEnabled = false
- binding.fab.isEnabled = false
- lifecycleScope.launch {
- try {
- if (enable) {
- doEnableProfile(iccid)
- } else {
- doDisableProfile(iccid)
- }
- Toast.makeText(context, R.string.toast_profile_enabled, Toast.LENGTH_LONG).show()
- // The APDU channel will be invalid when the SIM reboots. For now, just exit the app
- euiccChannelManager.invalidate()
- requireActivity().finish()
- } catch (e: Exception) {
- Log.d(TAG, "Failed to enable / disable profile $iccid")
- Log.d(TAG, Log.getStackTraceString(e))
- binding.fab.isEnabled = true
- binding.swipeRefresh.isEnabled = true
- Toast.makeText(context, R.string.toast_profile_enable_failed, Toast.LENGTH_LONG).show()
- }
- }
- }
- private suspend fun doEnableProfile(iccid: String) =
- withContext(Dispatchers.IO) {
- channel.lpa.enableProfile(iccid, Progress())
- }
- private suspend fun doDisableProfile(iccid: String) =
- withContext(Dispatchers.IO) {
- channel.lpa.disableProfile(iccid, Progress())
- }
- inner class ViewHolder(private val binding: EuiccProfileBinding) : RecyclerView.ViewHolder(binding.root) {
- init {
- binding.iccid.setOnClickListener {
- if (binding.iccid.transformationMethod == null) {
- binding.iccid.transformationMethod = PasswordTransformationMethod.getInstance()
- } else {
- binding.iccid.transformationMethod = null
- }
- }
- binding.profileMenu.setOnClickListener { showOptionsMenu() }
- }
- private lateinit var profile: LocalProfileInfo
- fun setProfile(profile: LocalProfileInfo) {
- this.profile = profile
- binding.name.text = getName()
- binding.state.setText(
- if (isEnabled()) {
- R.string.enabled
- } else {
- R.string.disabled
- }
- )
- binding.provider.text = profile.providerName
- binding.iccid.text = profile.iccidLittleEndian
- binding.iccid.transformationMethod = PasswordTransformationMethod.getInstance()
- }
- private fun isEnabled(): Boolean =
- profile.state == LocalProfileInfo.State.Enabled
- private fun getName(): String =
- profile.nickName.ifEmpty {
- profile.name
- }
- private fun showOptionsMenu() {
- PopupMenu(binding.root.context, binding.profileMenu).apply {
- setOnMenuItemClickListener(::onMenuItemClicked)
- inflate(R.menu.profile_options)
- if (isEnabled()) {
- menu.findItem(R.id.enable).isVisible = false
- menu.findItem(R.id.delete).isVisible = false
- } else {
- menu.findItem(R.id.disable).isVisible = false
- }
- show()
- }
- }
- private fun onMenuItemClicked(item: MenuItem): Boolean =
- when (item.itemId) {
- R.id.enable -> {
- enableOrDisableProfile(profile.iccid, true)
- true
- }
- R.id.disable -> {
- enableOrDisableProfile(profile.iccid, false)
- true
- }
- R.id.rename -> {
- ProfileRenameFragment.newInstance(slotId, profile.iccid, getName())
- .show(childFragmentManager, ProfileRenameFragment.TAG)
- true
- }
- R.id.delete -> {
- ProfileDeleteFragment.newInstance(slotId, profile.iccid, getName())
- .show(childFragmentManager, ProfileDeleteFragment.TAG)
- true
- }
- else -> false
- }
- }
- inner class EuiccProfileAdapter(var profiles: List<LocalProfileInfo>) : RecyclerView.Adapter<ViewHolder>() {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val binding =
- EuiccProfileBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return ViewHolder(binding)
- }
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- holder.setProfile(profiles[position])
- }
- override fun getItemCount(): Int = profiles.size
- }
- }
|