1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settings.deviceinfo.simstatus 17 18 import android.content.Context 19 import android.graphics.Bitmap 20 import android.util.Log 21 import android.view.WindowManager 22 import android.widget.ImageView 23 import android.widget.TextView 24 import androidx.lifecycle.Lifecycle 25 import androidx.lifecycle.LifecycleOwner 26 import androidx.lifecycle.lifecycleScope 27 import androidx.lifecycle.repeatOnLifecycle 28 import androidx.preference.Preference 29 import androidx.preference.PreferenceScreen 30 import com.android.settings.R 31 import com.android.settings.core.BasePreferenceController 32 import com.android.settings.deviceinfo.PhoneNumberUtil 33 import com.android.settings.network.SubscriptionUtil 34 import com.android.settingslib.CustomDialogPreferenceCompat 35 import com.android.settingslib.Utils 36 import com.android.settingslib.qrcode.QrCodeGenerator 37 import com.android.settingslib.spaprivileged.framework.common.userManager 38 import kotlinx.coroutines.CoroutineScope 39 import kotlinx.coroutines.Dispatchers 40 import kotlinx.coroutines.launch 41 import kotlinx.coroutines.withContext 42 43 /** 44 * This is to show a preference regarding EID of SIM card. 45 * 46 * @param preferenceKey is the key for Preference 47 */ 48 class SimEidPreferenceController(context: Context, preferenceKey: String) : 49 BasePreferenceController(context, preferenceKey) { 50 private var slotSimStatus: SlotSimStatus? = null 51 private var eidStatus: EidStatus? = null 52 private lateinit var preference: CustomDialogPreferenceCompat 53 private var coroutineScope: CoroutineScope? = null 54 private lateinit var eid: String 55 initnull56 fun init(slotSimStatus: SlotSimStatus?, eidStatus: EidStatus?) { 57 this.slotSimStatus = slotSimStatus 58 this.eidStatus = eidStatus 59 } 60 61 /** 62 * Returns available here, if SIM hardware is visible. 63 * 64 * Also check [getIsAvailableAndUpdateEid] for other availability check which retrieved 65 * asynchronously later. 66 */ getAvailabilityStatusnull67 override fun getAvailabilityStatus() = when { 68 !SubscriptionUtil.isSimHardwareVisible(mContext) 69 || Utils.isWifiOnly(mContext) -> UNSUPPORTED_ON_DEVICE 70 !mContext.userManager.isAdminUser -> DISABLED_FOR_USER 71 else -> AVAILABLE 72 } 73 displayPreferencenull74 override fun displayPreference(screen: PreferenceScreen) { 75 super.displayPreference(screen) 76 preference = screen.findPreference(preferenceKey)!! 77 } 78 onViewCreatednull79 override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { 80 coroutineScope = viewLifecycleOwner.lifecycleScope 81 coroutineScope?.launch { 82 viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 83 update() 84 } 85 } 86 } 87 updatenull88 private suspend fun update() { 89 val isAvailable = withContext(Dispatchers.Default) { 90 getIsAvailableAndUpdateEid() 91 } 92 preference.isVisible = isAvailable 93 if (isAvailable) { 94 val title = withContext(Dispatchers.Default) { 95 getTitle() 96 } 97 preference.title = title 98 preference.dialogTitle = title 99 preference.summary = eid 100 updateDialog() 101 } 102 } 103 getIsAvailableAndUpdateEidnull104 private fun getIsAvailableAndUpdateEid(): Boolean { 105 eid = eidStatus?.eid ?: "" 106 return eid.isNotEmpty() 107 } 108 109 /** Constructs title string. */ getTitlenull110 private fun getTitle(): String { 111 val slotSize = slotSimStatus?.size() ?: 0 112 if (slotSize <= 1) { 113 return mContext.getString(R.string.status_eid) 114 } 115 // Only append slot index to title when more than 1 is available 116 for (idxSlot in 0 until slotSize) { 117 val subInfo = slotSimStatus?.getSubscriptionInfo(idxSlot) 118 if (subInfo != null && subInfo.isEmbedded) { 119 return mContext.getString(R.string.eid_multi_sim, idxSlot + 1) 120 } 121 } 122 return mContext.getString(R.string.status_eid) 123 } 124 updateDialognull125 private suspend fun updateDialog() { 126 val dialog = preference.dialog ?: return 127 dialog.window?.setFlags( 128 WindowManager.LayoutParams.FLAG_SECURE, 129 WindowManager.LayoutParams.FLAG_SECURE 130 ) 131 dialog.setCanceledOnTouchOutside(false) 132 val textView = dialog.requireViewById<TextView>(R.id.esim_id_value) 133 textView.text = PhoneNumberUtil.expandByTts(eid) 134 135 val qrCodeView = dialog.requireViewById<ImageView>(R.id.esim_id_qrcode) 136 qrCodeView.setImageBitmap(getEidQrCode(eid)) 137 } 138 handlePreferenceTreeClicknull139 override fun handlePreferenceTreeClick(preference: Preference): Boolean { 140 if (preference.key != preferenceKey) return false 141 this.preference.setOnShowListener { 142 coroutineScope?.launch { updateDialog() } 143 } 144 return true 145 } 146 updateNonIndexableKeysnull147 override fun updateNonIndexableKeys(keys: MutableList<String>) { 148 if (!isAvailable() || !getIsAvailableAndUpdateEid()) { 149 keys += preferenceKey 150 } 151 } 152 153 companion object { 154 private const val TAG = "SimEidPreferenceController" 155 private const val QR_CODE_SIZE = 600 156 157 /** 158 * Gets the QR code for EID 159 * @param eid is the EID string 160 * @return a Bitmap of QR code 161 */ <lambda>null162 private suspend fun getEidQrCode(eid: String): Bitmap? = withContext(Dispatchers.Default) { 163 try { 164 QrCodeGenerator.encodeQrCode(contents = eid, size = QR_CODE_SIZE) 165 } catch (exception: Exception) { 166 Log.w(TAG, "Error when creating QR code width $QR_CODE_SIZE", exception) 167 null 168 } 169 } 170 } 171 } 172