Added API to verify that SubscriptionInfo is converted and to update carriers that support pSIM conversion.

Bug: b/315884067
Test: atest SubscriptionDatabaseManagerTest, SubscriptionInfoInternalTest, SubscriptionInfoTest
Change-Id: Ia45a4fd99f95c00f95d2280e6b6025e1ebd8220a
diff --git a/flags/subscription.aconfig b/flags/subscription.aconfig
index d2c36d6..fa77fc3 100644
--- a/flags/subscription.aconfig
+++ b/flags/subscription.aconfig
@@ -26,4 +26,11 @@
   namespace: "telephony"
   description: "Support emergency call only for data only cellular service."
   bug: "296097429"
+}
+
+flag {
+  name: "support_psim_to_esim_conversion"
+  namespace: "telephony"
+  description: "Support the psim to esim conversion."
+  bug: "315073761"
 }
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index a24ab43..400f8be 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -72,6 +72,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Stack;
@@ -79,6 +80,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 /** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
 public class EuiccController extends IEuiccController.Stub {
@@ -121,6 +123,7 @@
     // the phone process, 3) values are updated remotely by server flags.
     private List<String> mSupportedCountries;
     private List<String> mUnsupportedCountries;
+    private List<Integer> mPsimConversionSupportedCarrierIds;
 
     /** Initialize the instance. Should only be called once. */
     public static EuiccController init(Context context, FeatureFlags featureFlags) {
@@ -2073,6 +2076,34 @@
         return changeEnabled;
     }
 
+
+    @Override
+    public void setPsimConversionSupportedCarriers(int[] carrierIds) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to "
+                            + "set pSIM conversion supported carriers");
+        }
+        mPsimConversionSupportedCarrierIds = Arrays.stream(carrierIds).boxed()
+                .collect(Collectors.toList());
+    }
+
+
+
+    @Override
+    public boolean isPsimConversionSupported(int carrierId) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS "
+                            + "to check if the carrier is supported pSIM conversion");
+        }
+        if (mPsimConversionSupportedCarrierIds == null
+                || mPsimConversionSupportedCarrierIds.isEmpty()) {
+            return false;
+        }
+        return mPsimConversionSupportedCarrierIds.contains(carrierId);
+    }
+
     /**
      * Make sure the device has required telephony feature
      *
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index bbe88a8..e20b295 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -284,7 +284,10 @@
                     SubscriptionInfoInternal::getOnlyNonTerrestrialNetwork),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SERVICE_CAPABILITIES,
-                    SubscriptionInfoInternal::getServiceCapabilities)
+                    SubscriptionInfoInternal::getServiceCapabilities),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_TRANSFER_STATUS,
+                    SubscriptionInfoInternal::getTransferStatus)
     );
 
     /**
@@ -418,7 +421,10 @@
                     SubscriptionDatabaseManager::setNtn),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SERVICE_CAPABILITIES,
-                    SubscriptionDatabaseManager::setServiceCapabilities)
+                    SubscriptionDatabaseManager::setServiceCapabilities),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_TRANSFER_STATUS,
+                    SubscriptionDatabaseManager::setTransferStatus)
     );
 
     /**
@@ -2335,6 +2341,10 @@
             builder.setOnlyNonTerrestrialNetwork(cursor.getInt(cursor.getColumnIndexOrThrow(
                     SimInfo.COLUMN_IS_NTN)));
         }
+        if (mFeatureFlags.supportPsimToEsimConversion()) {
+            builder.setTransferStatus(cursor.getInt(cursor.getColumnIndexOrThrow(
+                    SimInfo.COLUMN_TRANSFER_STATUS)));
+        }
         return builder.build();
     }
 
@@ -2409,6 +2419,25 @@
     }
 
     /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     *
+     * @param subId Subscription ID.
+     * @param status The transfer status to change.
+     *
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setTransferStatus(int subId, int status) {
+        if (!mFeatureFlags.supportPsimToEsimConversion()) {
+            log("SubscriptionDatabaseManager:supportPsimToEsimConversion is false");
+            return;
+        }
+
+        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_TRANSFER_STATUS,
+                status,
+                SubscriptionInfoInternal.Builder::setTransferStatus);
+    }
+
+    /**
      * Log debug messages.
      *
      * @param s debug messages
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
index aa460d5..e646418 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
@@ -474,6 +474,11 @@
     private final int mServiceCapabilities;
 
     /**
+     * The transfer status of the subscription
+     */
+    private final int mTransferStatus;
+
+    /**
      * Constructor from builder.
      *
      * @param builder Builder of {@link SubscriptionInfoInternal}.
@@ -549,6 +554,7 @@
         this.mCardId = builder.mCardId;
         this.mIsGroupDisabled = builder.mIsGroupDisabled;
         this.mServiceCapabilities = builder.mServiceCapabilities;
+        this.mTransferStatus = builder.mTransferStatus;
     }
 
     /**
@@ -1205,6 +1211,12 @@
     public int getServiceCapabilities() {
         return mServiceCapabilities;
     }
+    /**
+     * @return Transfer status.
+     */
+    public int getTransferStatus() {
+        return mTransferStatus;
+    }
 
     /** @return converted {@link SubscriptionInfo}. */
     @NonNull
@@ -1244,6 +1256,7 @@
                 .setOnlyNonTerrestrialNetwork(mIsOnlyNonTerrestrialNetwork == 1)
                 .setServiceCapabilities(
                         SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities))
+                .setTransferStatus(mTransferStatus)
                 .build();
     }
 
@@ -1304,6 +1317,7 @@
                 + " getOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
                 + " isGroupDisabled=" + mIsGroupDisabled
                 + " serviceCapabilities=" + mServiceCapabilities
+                + " transferStatus=" + mTransferStatus
                 + "]";
     }
 
@@ -1361,7 +1375,8 @@
                 that.mNumberFromCarrier) && mNumberFromIms.equals(that.mNumberFromIms)
                 && mIsSatelliteAttachEnabledForCarrier == that.mIsSatelliteAttachEnabledForCarrier
                 && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
-                && mServiceCapabilities == that.mServiceCapabilities;
+                && mServiceCapabilities == that.mServiceCapabilities
+                && mTransferStatus == that.mTransferStatus;
     }
 
     @Override
@@ -1384,7 +1399,7 @@
                 mNumberFromIms, mPortIndex, mUsageSetting, mLastUsedTPMessageReference, mUserId,
                 mIsSatelliteEnabled, mCardId, mIsGroupDisabled,
                 mIsSatelliteAttachEnabledForCarrier, mIsOnlyNonTerrestrialNetwork,
-                mServiceCapabilities);
+                mServiceCapabilities, mTransferStatus);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
         result = 31 * result + Arrays.hashCode(mCarrierConfigAccessRules);
         result = 31 * result + Arrays.hashCode(mRcsConfig);
@@ -1777,6 +1792,11 @@
         private int mServiceCapabilities;
 
         /**
+         * The transfer status of the subscription
+         */
+        private int mTransferStatus;
+
+        /**
          * Default constructor.
          */
         public Builder() {
@@ -1855,6 +1875,7 @@
             mCardId = info.mCardId;
             mIsGroupDisabled = info.mIsGroupDisabled;
             mServiceCapabilities = info.mServiceCapabilities;
+            mTransferStatus = info.mTransferStatus;
         }
 
         /**
@@ -2787,6 +2808,18 @@
         }
 
         /**
+         * Set the transfer status of the subscription.
+         *
+         * @param status The transfer status
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setTransferStatus(int status) {
+            mTransferStatus = status;
+            return this;
+        }
+
+        /**
          * Build the {@link SubscriptionInfoInternal}.
          *
          * @return The {@link SubscriptionInfoInternal} instance.
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index ddf80a8..0d03f91 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -22,6 +22,7 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -1219,7 +1220,11 @@
                         builder.setCardString(mUiccController.convertToCardString(cardId));
                     }
 
+                    if (mFeatureFlags.supportPsimToEsimConversion()) {
+                        builder.setTransferStatus(subInfo.getTransferStatus());
+                    }
                     embeddedSubs.add(subInfo.getSubscriptionId());
+
                     subInfo = builder.build();
                     log("updateEmbeddedSubscriptions: update subscription " + subInfo);
                     mSubscriptionDatabaseManager.updateSubscription(subInfo);
@@ -4299,6 +4304,36 @@
         }
     }
 
+
+
+    /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     * @param subId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change. This value must be one of the following.
+     * {@link SubscriptionManager#TRANSFER_STATUS_NONE},
+     * {@link SubscriptionManager#TRANSFER_STATUS_TRANSFERRED_OUT} or
+     * {@link SubscriptionManager#TRANSFER_STATUS_CONVERTED}
+     *
+     */
+    @Override
+    @EnforcePermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setTransferStatus(int subId, int status) {
+        setTransferStatus_enforcePermission();
+        if (mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to"
+                    + "setTransferStatus");
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            mSubscriptionDatabaseManager.setTransferStatus(subId, status);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+
     /**
      * Get the current calling package name.
      * @return the current calling package name
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index 7dd4093..d52e4ee 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -132,7 +132,8 @@
                     + Telephony.SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
                     + " INTEGER DEFAULT 1, "
                     + Telephony.SimInfo.COLUMN_IS_NTN + " INTEGER DEFAULT 0,"
-                    + Telephony.SimInfo.COLUMN_SERVICE_CAPABILITIES + " INTEGER DEFAULT 7"
+                    + Telephony.SimInfo.COLUMN_SERVICE_CAPABILITIES + " INTEGER DEFAULT 7,"
+                    + Telephony.SimInfo.COLUMN_TRANSFER_STATUS + " INTEGER DEFAULT 0"
                     + ");";
         }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
index 4afdfe9..ac92b8f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
@@ -68,6 +68,7 @@
                 .setOnlyNonTerrestrialNetwork(true)
                 .setServiceCapabilities(SubscriptionManager.getServiceCapabilitiesSet(
                     SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK))
+                .setTransferStatus(1)
                 .build();
     }
 
@@ -92,6 +93,9 @@
         }
         assertThat(mSubscriptionInfoUT.getServiceCapabilities()).isEqualTo(
                 Set.of(SubscriptionManager.SERVICE_CAPABILITY_DATA));
+        if (Flags.supportPsimToEsimConversion()) {
+            assertThat(mSubscriptionInfoUT.getTransferStatus()).isEqualTo(1);
+        }
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
index 1a89c15..130ebef 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -134,6 +134,9 @@
 
     private FeatureFlags mFeatureFlags;
 
+    static final int FAKE_TRANSFER_STATUS_TRANSFERRED_OUT = 1;
+    static final int FAKE_TRANSFER_STATUS_CONVERTED = 2;
+
     static final SubscriptionInfoInternal FAKE_SUBSCRIPTION_INFO1 =
             new SubscriptionInfoInternal.Builder()
                     .setId(1)
@@ -204,6 +207,7 @@
                     .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_DISABLED)
                     .setGroupDisabled(false)
                     .setServiceCapabilities(FAKE_SERVICE_CAPABILITIES_1)
+                    .setTransferStatus(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT)
                     .build();
 
     static final SubscriptionInfoInternal FAKE_SUBSCRIPTION_INFO2 =
@@ -276,6 +280,7 @@
                     .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_ENABLED)
                     .setGroupDisabled(false)
                     .setServiceCapabilities(FAKE_SERVICE_CAPABILITIES_2)
+                    .setTransferStatus(FAKE_TRANSFER_STATUS_CONVERTED)
                     .build();
 
     private SubscriptionDatabaseManager mDatabaseManagerUT;
@@ -436,6 +441,7 @@
         doReturn(2).when(mUiccController).convertToPublicCardId(eq(FAKE_ICCID2));
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         when(mFeatureFlags.dataOnlyCellularService()).thenReturn(true);
+        when(mFeatureFlags.supportPsimToEsimConversion()).thenReturn(true);
         mDatabaseManagerUT = new SubscriptionDatabaseManager(mContext, Looper.myLooper(),
                 mFeatureFlags, mSubscriptionDatabaseManagerCallback);
         logd("SubscriptionDatabaseManagerTest -Setup!");
@@ -2239,4 +2245,37 @@
                 mDatabaseManagerUT.getSubscriptionInfoInternal(1).getServiceCapabilities())
                 .isEqualTo(FAKE_SERVICE_CAPABILITIES_1);
     }
+
+    @Test
+    public void testSetTransferStatus() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setTransferStatus(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_TRANSFER_STATUS_TRANSFERRED_OUT));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setTransferStatus(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setTransferStatus(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(1)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_TRANSFER_STATUS)).isEqualTo(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_TRANSFER_STATUS, FAKE_TRANSFER_STATUS_CONVERTED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getTransferStatus())
+                .isEqualTo(FAKE_TRANSFER_STATUS_CONVERTED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getTransferStatus())
+                .isNotEqualTo(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
index d9addd1..ff45bf9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
@@ -115,6 +115,7 @@
                     .setOnlyNonTerrestrialNetwork(1)
                     .setServiceCapabilities(
                             SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK)
+                    .setTransferStatus(1)
                     .build();
 
     private final SubscriptionInfoInternal mSubInfoNull =
@@ -141,6 +142,7 @@
                     .setRcsConfig(new byte[0])
                     .setAllowedNetworkTypesForReasons("")
                     .setDeviceToDeviceStatusSharingContacts("")
+                    .setTransferStatus(1)
                     .build();
 
     @Rule
@@ -237,6 +239,7 @@
         assertThat(mSubInfo.getOnlyNonTerrestrialNetwork()).isEqualTo(1);
         assertThat(mSubInfo.getServiceCapabilities()).isEqualTo(
                 SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK);
+        assertThat(mSubInfo.getTransferStatus()).isEqualTo(1);
     }
 
     @Test
@@ -303,6 +306,7 @@
         assertThat(subInfo.isOnlyNonTerrestrialNetwork()).isTrue();
         assertThat(subInfo.getServiceCapabilities()).isEqualTo(
                 Set.of(SubscriptionManager.SERVICE_CAPABILITY_DATA));
+        assertThat(mSubInfo.getTransferStatus()).isEqualTo(1);
     }
 
     @Test