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() =
68         if (SubscriptionUtil.isSimHardwareVisible(mContext)) AVAILABLE else UNSUPPORTED_ON_DEVICE
69 
70     override fun displayPreference(screen: PreferenceScreen) {
71         super.displayPreference(screen)
72         preference = screen.findPreference(preferenceKey)!!
73     }
74 
onViewCreatednull75     override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
76         coroutineScope = viewLifecycleOwner.lifecycleScope
77         coroutineScope?.launch {
78             viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
79                 update()
80             }
81         }
82     }
83 
updatenull84     private suspend fun update() {
85         val isAvailable = withContext(Dispatchers.Default) {
86             getIsAvailableAndUpdateEid()
87         }
88         preference.isVisible = isAvailable
89         if (isAvailable) {
90             val title = withContext(Dispatchers.Default) {
91                 getTitle()
92             }
93             preference.title = title
94             preference.dialogTitle = title
95             preference.summary = eid
96             updateDialog()
97         }
98     }
99 
getIsAvailableAndUpdateEidnull100     private fun getIsAvailableAndUpdateEid(): Boolean {
101         if (!mContext.userManager.isAdminUser || Utils.isWifiOnly(mContext)) return false
102         eid = eidStatus?.eid ?: ""
103         return eid.isNotEmpty()
104     }
105 
106     /** Constructs title string. */
getTitlenull107     private fun getTitle(): String {
108         val slotSize = slotSimStatus?.size() ?: 0
109         if (slotSize <= 1) {
110             return mContext.getString(R.string.status_eid)
111         }
112         // Only append slot index to title when more than 1 is available
113         for (idxSlot in 0 until slotSize) {
114             val subInfo = slotSimStatus?.getSubscriptionInfo(idxSlot)
115             if (subInfo != null && subInfo.isEmbedded) {
116                 return mContext.getString(R.string.eid_multi_sim, idxSlot + 1)
117             }
118         }
119         return mContext.getString(R.string.status_eid)
120     }
121 
updateDialognull122     private suspend fun updateDialog() {
123         val dialog = preference.dialog ?: return
124         dialog.window?.setFlags(
125             WindowManager.LayoutParams.FLAG_SECURE,
126             WindowManager.LayoutParams.FLAG_SECURE
127         )
128         dialog.setCanceledOnTouchOutside(false)
129         val textView = dialog.requireViewById<TextView>(R.id.esim_id_value)
130         textView.text = PhoneNumberUtil.expandByTts(eid)
131 
132         val qrCodeView = dialog.requireViewById<ImageView>(R.id.esim_id_qrcode)
133         qrCodeView.setImageBitmap(getEidQrCode(eid))
134     }
135 
handlePreferenceTreeClicknull136     override fun handlePreferenceTreeClick(preference: Preference): Boolean {
137         if (preference.key != preferenceKey) return false
138         this.preference.setOnShowListener {
139             coroutineScope?.launch { updateDialog() }
140         }
141         return true
142     }
143 
updateNonIndexableKeysnull144     override fun updateNonIndexableKeys(keys: MutableList<String>) {
145         if (!isAvailable() || !getIsAvailableAndUpdateEid()) {
146             keys += preferenceKey
147         }
148     }
149 
150     companion object {
151         private const val TAG = "SimEidPreferenceController"
152         private const val QR_CODE_SIZE = 600
153 
154         /**
155          * Gets the QR code for EID
156          * @param eid is the EID string
157          * @return a Bitmap of QR code
158          */
<lambda>null159         private suspend fun getEidQrCode(eid: String): Bitmap? = withContext(Dispatchers.Default) {
160             try {
161                 QrCodeGenerator.encodeQrCode(contents = eid, size = QR_CODE_SIZE)
162             } catch (exception: Exception) {
163                 Log.w(TAG, "Error when creating QR code width $QR_CODE_SIZE", exception)
164                 null
165             }
166         }
167     }
168 }
169