/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.deviceinfo.simstatus import android.content.Context import android.graphics.Bitmap import android.util.Log import android.view.WindowManager import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settings.core.BasePreferenceController import com.android.settings.deviceinfo.PhoneNumberUtil import com.android.settings.network.SubscriptionUtil import com.android.settingslib.CustomDialogPreferenceCompat import com.android.settingslib.Utils import com.android.settingslib.qrcode.QrCodeGenerator import com.android.settingslib.spaprivileged.framework.common.userManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** * This is to show a preference regarding EID of SIM card. * * @param preferenceKey is the key for Preference */ class SimEidPreferenceController(context: Context, preferenceKey: String) : BasePreferenceController(context, preferenceKey) { private var slotSimStatus: SlotSimStatus? = null private var eidStatus: EidStatus? = null private lateinit var preference: CustomDialogPreferenceCompat private var coroutineScope: CoroutineScope? = null private lateinit var eid: String fun init(slotSimStatus: SlotSimStatus?, eidStatus: EidStatus?) { this.slotSimStatus = slotSimStatus this.eidStatus = eidStatus } /** * Returns available here, if SIM hardware is visible. * * Also check [getIsAvailableAndUpdateEid] for other availability check which retrieved * asynchronously later. */ override fun getAvailabilityStatus() = if (SubscriptionUtil.isSimHardwareVisible(mContext)) AVAILABLE else UNSUPPORTED_ON_DEVICE override fun displayPreference(screen: PreferenceScreen) { super.displayPreference(screen) preference = screen.findPreference(preferenceKey)!! } override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { coroutineScope = viewLifecycleOwner.lifecycleScope coroutineScope?.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { update() } } } private suspend fun update() { val isAvailable = withContext(Dispatchers.Default) { getIsAvailableAndUpdateEid() } preference.isVisible = isAvailable if (isAvailable) { val title = withContext(Dispatchers.Default) { getTitle() } preference.title = title preference.dialogTitle = title preference.summary = eid updateDialog() } } private fun getIsAvailableAndUpdateEid(): Boolean { if (!mContext.userManager.isAdminUser || Utils.isWifiOnly(mContext)) return false eid = eidStatus?.eid ?: "" return eid.isNotEmpty() } /** Constructs title string. */ private fun getTitle(): String { val slotSize = slotSimStatus?.size() ?: 0 if (slotSize <= 1) { return mContext.getString(R.string.status_eid) } // Only append slot index to title when more than 1 is available for (idxSlot in 0 until slotSize) { val subInfo = slotSimStatus?.getSubscriptionInfo(idxSlot) if (subInfo != null && subInfo.isEmbedded) { return mContext.getString(R.string.eid_multi_sim, idxSlot + 1) } } return mContext.getString(R.string.status_eid) } private suspend fun updateDialog() { val dialog = preference.dialog ?: return dialog.window?.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) dialog.setCanceledOnTouchOutside(false) val textView = dialog.requireViewById(R.id.esim_id_value) textView.text = PhoneNumberUtil.expandByTts(eid) val qrCodeView = dialog.requireViewById(R.id.esim_id_qrcode) qrCodeView.setImageBitmap(getEidQrCode(eid)) } override fun handlePreferenceTreeClick(preference: Preference): Boolean { if (preference.key != preferenceKey) return false this.preference.setOnShowListener { coroutineScope?.launch { updateDialog() } } return true } override fun updateNonIndexableKeys(keys: MutableList) { if (!isAvailable() || !getIsAvailableAndUpdateEid()) { keys += preferenceKey } } companion object { private const val TAG = "SimEidPreferenceController" private const val QR_CODE_SIZE = 600 /** * Gets the QR code for EID * @param eid is the EID string * @return a Bitmap of QR code */ private suspend fun getEidQrCode(eid: String): Bitmap? = withContext(Dispatchers.Default) { try { QrCodeGenerator.encodeQrCode(contents = eid, size = QR_CODE_SIZE) } catch (exception: Exception) { Log.w(TAG, "Error when creating QR code width $QR_CODE_SIZE", exception) null } } } }