[Settings] Present EID in About phone page

Present EID within about phone page.

Bug: 260540995
Test: local

Change-Id: If5f512c1da6b4b3b1adc1d13dbe11226b7ecad41
diff --git a/res/layout/dialog_eid_status.xml b/res/layout/dialog_eid_status.xml
new file mode 100644
index 0000000..95d4872
--- /dev/null
+++ b/res/layout/dialog_eid_status.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="@dimen/sim_content_padding">
+
+    <TextView
+        style="@style/device_info_dialog_value"
+        android:id="@+id/esim_id_value"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textIsSelectable="true"
+        android:text="@string/device_info_not_available"/>
+
+    <ImageView
+        android:id="@+id/esim_id_qrcode"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        tools:ignore="ContentDescription" />
+
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a97c3d5..2a2cccf 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2415,6 +2415,10 @@
     <string name="storage_settings_for_app" >Storage &amp; cache</string>
     <!-- Storage settings screen title -->
     <string name="storage_settings_title">Storage settings</string>
+    <!-- About phone, title of EID -->
+    <string name="status_eid">EID</string>
+    <!-- About phone, title of EID for multi-sim devices -->
+    <string name="eid_multi_sim">EID (sim slot %1$d)</string>
     <!-- About phone screen, title for IMEI for multi-sim devices -->
     <string name="imei_multi_sim">IMEI (sim slot %1$d)</string>
     <!-- About phone screen, summary of the MAC address [CHAR LIMIT=80] -->
diff --git a/res/xml/my_device_info.xml b/res/xml/my_device_info.xml
index 9f14026..4cbe13f 100644
--- a/res/xml/my_device_info.xml
+++ b/res/xml/my_device_info.xml
@@ -105,7 +105,7 @@
         <!-- Model & hardware -->
         <Preference
             android:key="device_model"
-            android:order="31"
+            android:order="30"
             android:title="@string/model_info"
             android:summary="@string/summary_placeholder"
             android:fragment="com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFragment"
@@ -113,6 +113,18 @@
             settings:keywords="@string/keywords_model_and_hardware"
             settings:controller="com.android.settings.deviceinfo.HardwareInfoPreferenceController"/>
 
+        <!-- EID -->
+        <com.android.settings.network.telephony.TelephonyPreferenceDialog
+            android:key="eid_info"
+            android:order="31"
+            android:title="@string/status_eid"
+            android:summary="@string/device_info_protected_single_press"
+            android:positiveButtonText="@string/dlg_ok"
+            android:dialogLayout="@layout/dialog_eid_status"
+            settings:isPreferenceVisible="@bool/config_show_sim_info"
+            settings:enableCopying="true"
+            settings:controller="com.android.settings.deviceinfo.simstatus.SimEidPreferenceController"/>
+
         <!-- IMEI -->
         <com.android.settings.deviceinfo.PhoneNumberSummaryPreference
             android:key="imei_info"
diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
index 22b38d6..991ad25 100644
--- a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
+++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
@@ -40,6 +40,8 @@
 import com.android.settings.deviceinfo.UptimePreferenceController;
 import com.android.settings.deviceinfo.WifiMacAddressPreferenceController;
 import com.android.settings.deviceinfo.imei.ImeiInfoPreferenceController;
+import com.android.settings.deviceinfo.simstatus.EidStatus;
+import com.android.settings.deviceinfo.simstatus.SimEidPreferenceController;
 import com.android.settings.deviceinfo.simstatus.SimStatusPreferenceController;
 import com.android.settings.deviceinfo.simstatus.SlotSimStatus;
 import com.android.settings.search.BaseSearchIndexProvider;
@@ -59,6 +61,7 @@
         implements DeviceNamePreferenceController.DeviceNamePreferenceHost {
 
     private static final String LOG_TAG = "MyDeviceInfoFragment";
+    private static final String KEY_EID_INFO = "eid_info";
     private static final String KEY_MY_DEVICE_INFO_HEADER = "my_device_info_header";
 
     private BuildNumberPreferenceController mBuildNumberPreferenceController;
@@ -130,6 +133,12 @@
             slotRecord.init(fragment, slotSimStatus);
             controllers.add(slotRecord);
         }
+
+        EidStatus eidStatus = new EidStatus(slotSimStatus, context, executor);
+        SimEidPreferenceController simEid = new SimEidPreferenceController(context, KEY_EID_INFO);
+        simEid.init(slotSimStatus, eidStatus);
+        controllers.add(simEid);
+
         if (executor != null) {
             executor.shutdown();
         }
diff --git a/src/com/android/settings/deviceinfo/simstatus/EidStatus.java b/src/com/android/settings/deviceinfo/simstatus/EidStatus.java
new file mode 100644
index 0000000..2f986a3
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/simstatus/EidStatus.java
@@ -0,0 +1,151 @@
+/*
+ * 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.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
+import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.IntStream;
+
+/**
+ * A class for query EID.
+ */
+public class EidStatus {
+
+    private static final String TAG = "EidStatus";
+    private final SlotSimStatus mSlotSimStatus;
+    private final Phaser mBlocker = new Phaser(1);
+    private final AtomicReference<String> mEid = new AtomicReference<String>();
+
+    /**
+     * Construct of class.
+     * @param slotSimStatus SlotSimStatus
+     * @param context Context
+     */
+    public EidStatus(SlotSimStatus slotSimStatus, Context context) {
+        this(slotSimStatus, context, null);
+    }
+
+    /**
+     * Construct of class.
+     * @param slotSimStatus SlotSimStatus
+     * @param context Context
+     * @param executor executor for offload to thread
+     */
+    public EidStatus(SlotSimStatus slotSimStatus, Context context, Executor executor) {
+        mSlotSimStatus = slotSimStatus;
+
+        if (executor == null) {
+            getEidOperation(context);
+        } else {
+            executor.execute(() -> getEidOperation(context));
+        }
+    }
+
+    /**
+     * Get the EID
+     * @return EID string
+     */
+    public String getEid() {
+        mBlocker.awaitAdvance(0);
+        return mEid.get();
+    }
+
+    protected TelephonyManager getTelephonyManager(Context context) {
+        return context.getSystemService(TelephonyManager.class);
+    }
+
+    protected EuiccManager getEuiccManager(Context context) {
+        return context.getSystemService(EuiccManager.class);
+    }
+
+    protected String getDefaultEid(EuiccManager euiccMgr) {
+        if ((euiccMgr == null) || (!euiccMgr.isEnabled())) {
+            return null;
+        }
+        return euiccMgr.getEid();
+    }
+
+    protected void getEidOperation(Context context) {
+        EuiccManager euiccMgr = getEuiccManager(context);
+        String eid = getEidPerSlot(context, euiccMgr);
+        if (eid == null) {
+            eid = getDefaultEid(euiccMgr);
+        }
+        mEid.set(eid);
+        mBlocker.arrive();
+    }
+
+    protected String getEidPerSlot(Context context, EuiccManager euiccMgr) {
+        if (mSlotSimStatus.size() <= SimStatusDialogController.MAX_PHONE_COUNT_SINGLE_SIM) {
+            return null;
+        }
+
+        TelephonyManager telMgr = getTelephonyManager(context);
+        if (telMgr == null) {
+            return null;
+        }
+
+        List<UiccCardInfo> uiccCardInfoList = telMgr.getUiccCardsInfo();
+        if (uiccCardInfoList == null) {
+            return null;
+        }
+
+        // Collects all card ID from all eSIM(s) reported from SubscsriptionManager
+        final int [] cardIdList = IntStream.range(0, mSlotSimStatus.size())
+                .mapToObj(slotIdx -> mSlotSimStatus.getSubscriptionInfo(slotIdx))
+                .filter(Objects::nonNull)
+                .filter(SubscriptionInfo::isEmbedded)
+                .mapToInt(SubscriptionInfo::getCardId)
+                .sorted()
+                .distinct()
+                .toArray();
+        if (cardIdList.length == 0) {
+            return null;
+        }
+
+        /**
+         * Find EID from first slot which contains an eSIM and with card ID listed within
+         * the eSIM card ID provided by SubscsriptionManager.
+         */
+        return uiccCardInfoList.stream()
+                .filter(UiccCardInfo::isEuicc)
+                .filter(cardInfo -> {
+                    int cardId = cardInfo.getCardId();
+                    return Arrays.binarySearch(cardIdList, cardId) >= 0;
+                })
+                .map(cardInfo -> {
+                    String eid = cardInfo.getEid();
+                    if (TextUtils.isEmpty(eid)) {
+                        eid = euiccMgr.createForCardId(cardInfo.getCardId()).getEid();
+                    }
+                    return eid;
+                })
+                .findFirst()
+                .orElse(null);
+    }
+
+}
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.java b/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.java
new file mode 100644
index 0000000..e0a8af6
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.java
@@ -0,0 +1,178 @@
+/*
+ * 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.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.os.UserManager;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
+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.settings.network.telephony.TelephonyPreferenceDialog;
+import com.android.settingslib.Utils;
+import com.android.settingslib.qrcode.QrCodeGenerator;
+
+/**
+ * This is to show a preference regarding EID of SIM card.
+ */
+public class SimEidPreferenceController extends BasePreferenceController
+        implements DialogInterface.OnShowListener {
+
+    private static final String TAG = "SimEidPreferenceController";
+
+    private SlotSimStatus mSlotSimStatus;
+    private EidStatus mEidStatus;
+    private boolean mShowEidOnSummary;
+    private TelephonyPreferenceDialog mPreference;
+
+    /**
+     * Constructer.
+     * @param context Context
+     * @param preferenceKey is the key for Preference
+     */
+    public SimEidPreferenceController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    /**
+     * Update status.
+     *
+     * @param slotSimStatus sim status per slot
+     * @param eidStatus status of EID
+     */
+    public void init(SlotSimStatus slotSimStatus, EidStatus eidStatus) {
+        mSlotSimStatus = slotSimStatus;
+        mEidStatus = eidStatus;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        if ((!SubscriptionUtil.isSimHardwareVisible(mContext)) || (mSlotSimStatus == null)) {
+            return;
+        }
+        TelephonyPreferenceDialog preference = (TelephonyPreferenceDialog)
+                screen.findPreference(getPreferenceKey());
+        if (preference == null) {
+            return;
+        }
+
+        preference.setTitle(getTitle());
+        mPreference = preference;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+            TelephonyPreferenceDialog preferenceDialog = (TelephonyPreferenceDialog)preference;
+            preferenceDialog.setDialogTitle(getTitle());
+            preferenceDialog.setDialogMessage(mEidStatus.getEid());
+            preferenceDialog.setOnShowListener(this);
+            return true;
+        }
+        return super.handlePreferenceTreeClick(preference);
+    }
+
+    /**
+     * Construct title string.
+     * @return title string
+     */
+    @VisibleForTesting
+    protected CharSequence getTitle() {
+        int slotSize = (mSlotSimStatus == null) ? 0 : mSlotSimStatus.size();
+        if (slotSize <= SimStatusDialogController.MAX_PHONE_COUNT_SINGLE_SIM) {
+            return mContext.getString(R.string.status_eid);
+        }
+        // Only append slot index to title when more than 1 is available
+        for (int idxSlot = 0; idxSlot < slotSize; idxSlot++) {
+            SubscriptionInfo subInfo = mSlotSimStatus.getSubscriptionInfo(idxSlot);
+            if ((subInfo != null) && subInfo.isEmbedded()) {
+                return mContext.getString(R.string.eid_multi_sim, idxSlot+1);
+            }
+        }
+        return mContext.getString(R.string.status_eid);
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        String summary = mShowEidOnSummary ? mEidStatus.getEid() : null;
+        if (TextUtils.isEmpty(summary)) {
+            summary = mContext.getString(R.string.device_info_protected_single_press);
+        }
+        return summary;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (!SubscriptionUtil.isSimHardwareVisible(mContext)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        boolean isAvailable = SubscriptionUtil.isSimHardwareVisible(mContext) &&
+                mContext.getSystemService(UserManager.class).isAdminUser() &&
+                !Utils.isWifiOnly(mContext);
+        return isAvailable ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    /**
+     * Callback when dialog end of show().
+     */
+    public void onShow(DialogInterface dialog) {
+        Dialog dialogShwon = mPreference.getDialog();
+
+        dialogShwon.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
+                WindowManager.LayoutParams.FLAG_SECURE);
+        dialogShwon.setCanceledOnTouchOutside(false);
+
+        TextView textView = dialogShwon.findViewById(R.id.esim_id_value);
+        textView.setText(PhoneNumberUtil.expandByTts(mEidStatus.getEid()));
+        textView.setTextIsSelectable(true);
+
+        ImageView qrCodeView = dialogShwon.findViewById(R.id.esim_id_qrcode);
+        qrCodeView.setImageBitmap(getEidQRcode(mEidStatus.getEid().toString(),
+                qrCodeView.getWidth()));
+
+        mShowEidOnSummary = true;
+    }
+
+    /**
+     * Get the QR code for EID
+     * @param eid is the EID string
+     * @param widthInPixel is the width of Bitmap in pixel
+     * @return a Bitmap of QR code
+     */
+    public Bitmap getEidQRcode(String eid, int widthInPixel) {
+        Bitmap qrCodeBitmap = null;
+        try {
+            qrCodeBitmap = QrCodeGenerator.encodeQrCode(eid, widthInPixel);
+        } catch (Exception exception) {
+            Log.w(TAG, "Error when creating QR code width " + widthInPixel, exception);
+        }
+        return qrCodeBitmap;
+    }
+}