| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- 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.ImageButton
- import android.widget.PopupMenu
- import android.widget.TextView
- import android.widget.Toast
- import androidx.fragment.app.Fragment
- import androidx.lifecycle.lifecycleScope
- import androidx.recyclerview.widget.LinearLayoutManager
- import androidx.recyclerview.widget.RecyclerView
- import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- import com.google.android.material.floatingactionbutton.FloatingActionButton
- import net.typeblog.lpac_jni.LocalProfileInfo
- import im.angry.openeuicc.common.R
- import im.angry.openeuicc.util.*
- 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 lateinit var swipeRefresh: SwipeRefreshLayout
- private lateinit var fab: FloatingActionButton
- private lateinit var profileList: RecyclerView
- private val adapter = EuiccProfileAdapter(listOf())
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- val view = inflater.inflate(R.layout.fragment_euicc, container, false)
- swipeRefresh = view.findViewById(R.id.swipe_refresh)
- fab = view.findViewById(R.id.fab)
- profileList = view.findViewById(R.id.profile_list)
- return view
- }
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- swipeRefresh.setOnRefreshListener { refresh() }
- profileList.adapter = adapter
- profileList.layoutManager =
- LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
- fab.setOnClickListener {
- ProfileDownloadFragment.newInstance(slotId)
- .show(childFragmentManager, ProfileDownloadFragment.TAG)
- }
- }
- override fun onStart() {
- super.onStart()
- refresh()
- }
- override fun onEuiccProfilesChanged() {
- refresh()
- }
- @SuppressLint("NotifyDataSetChanged")
- private fun refresh() {
- swipeRefresh.isRefreshing = true
- lifecycleScope.launch {
- val profiles = withContext(Dispatchers.IO) {
- euiccChannelManager.notifyEuiccProfilesChanged(slotId)
- channel.lpa.profiles
- }
- withContext(Dispatchers.Main) {
- adapter.profiles = profiles.operational
- adapter.notifyDataSetChanged()
- swipeRefresh.isRefreshing = false
- }
- }
- }
- private fun enableOrDisableProfile(iccid: String, enable: Boolean) {
- swipeRefresh.isRefreshing = true
- swipeRefresh.isEnabled = false
- 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))
- fab.isEnabled = true
- 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)
- }
- private suspend fun doDisableProfile(iccid: String) =
- withContext(Dispatchers.IO) {
- channel.lpa.disableProfile(iccid)
- }
- inner class ViewHolder(private val root: View) : RecyclerView.ViewHolder(root) {
- private val iccid: TextView = root.findViewById(R.id.iccid)
- private val name: TextView = root.findViewById(R.id.name)
- private val state: TextView = root.findViewById(R.id.state)
- private val provider: TextView = root.findViewById(R.id.provider)
- private val profileMenu: ImageButton = root.findViewById(R.id.profile_menu)
- init {
- iccid.setOnClickListener {
- if (iccid.transformationMethod == null) {
- iccid.transformationMethod = PasswordTransformationMethod.getInstance()
- } else {
- iccid.transformationMethod = null
- }
- }
- profileMenu.setOnClickListener { showOptionsMenu() }
- }
- private lateinit var profile: LocalProfileInfo
- fun setProfile(profile: LocalProfileInfo) {
- this.profile = profile
- name.text = profile.displayName
- state.setText(
- if (isEnabled()) {
- R.string.enabled
- } else {
- R.string.disabled
- }
- )
- provider.text = profile.providerName
- iccid.text = profile.iccid
- iccid.transformationMethod = PasswordTransformationMethod.getInstance()
- }
- private fun isEnabled(): Boolean =
- profile.state == LocalProfileInfo.State.Enabled
- private fun showOptionsMenu() {
- PopupMenu(root.context, 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, profile.displayName)
- .show(childFragmentManager, ProfileRenameFragment.TAG)
- true
- }
- R.id.delete -> {
- ProfileDeleteFragment.newInstance(slotId, profile.iccid, profile.displayName)
- .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 view = LayoutInflater.from(parent.context).inflate(R.layout.euicc_profile, parent, false)
- return ViewHolder(view)
- }
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- holder.setProfile(profiles[position])
- }
- override fun getItemCount(): Int = profiles.size
- }
- }
|