Merge "Do not send update to carrier config if SIM card is still locked" into sc-dev
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index 362b128..02530da 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -29,6 +29,8 @@
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -109,6 +111,8 @@
     protected RegistrantList mEmergencyNumberListRegistrants = new RegistrantList();
     protected RegistrantList mUiccApplicationsEnablementRegistrants = new RegistrantList();
     protected RegistrantList mBarringInfoChangedRegistrants = new RegistrantList();
+    protected RegistrantList mSimPhonebookChangedRegistrants = new RegistrantList();
+    protected RegistrantList mSimPhonebookRecordsReceivedRegistrants = new RegistrantList();
 
     @UnsupportedAppUsage
     protected Registrant mGsmSmsRegistrant;
@@ -1085,4 +1089,36 @@
     public void unregisterForBarringInfoChanged(Handler h) {
         mBarringInfoChangedRegistrants.remove(h);
     }
+
+    @Override
+    public void registerForSimPhonebookChanged(Handler h, int what, Object obj) {
+        mSimPhonebookChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSimPhonebookChanged(Handler h) {
+        mSimPhonebookChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSimPhonebookRecordsReceived(Handler h, int what, Object obj) {
+        mSimPhonebookRecordsReceivedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSimPhonebookRecordsReceived(Handler h) {
+        mSimPhonebookRecordsReceivedRegistrants.remove(h);
+    }
+
+    @Override
+    public void getSimPhonebookRecords(Message result) {
+    }
+
+    @Override
+    public void getSimPhonebookCapacity(Message result) {
+    }
+
+    @Override
+    public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result) {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
index 9afb0fa..6bc2450 100644
--- a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
+++ b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
@@ -102,7 +102,8 @@
     }
 
     /**
-     * @return {@code true} if the SMS was handled by carrier services.
+     * @return {@code true} if the SMS was handled by a carrier application or an ImsService
+     * implementing RCS features.
      */
     @VisibleForTesting
     public boolean filter() {
@@ -111,10 +112,10 @@
         if (carrierAppForFiltering.isPresent()) {
             smsFilterPackages.add(carrierAppForFiltering.get());
         }
-        String carrierImsPackage = CarrierSmsUtils.getCarrierImsPackageForIntent(mContext, mPhone,
+        String imsRcsPackage = CarrierSmsUtils.getImsRcsPackageForIntent(mContext, mPhone,
                 new Intent(CarrierMessagingService.SERVICE_INTERFACE));
-        if (carrierImsPackage != null) {
-            smsFilterPackages.add(carrierImsPackage);
+        if (imsRcsPackage != null) {
+            smsFilterPackages.add(imsRcsPackage);
         }
 
         if (mFilterAggregator != null) {
diff --git a/src/java/com/android/internal/telephony/CarrierSmsUtils.java b/src/java/com/android/internal/telephony/CarrierSmsUtils.java
index f78d147..76a0c23 100644
--- a/src/java/com/android/internal/telephony/CarrierSmsUtils.java
+++ b/src/java/com/android/internal/telephony/CarrierSmsUtils.java
@@ -22,9 +22,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
-import android.os.PersistableBundle;
-import android.telephony.CarrierConfigManager;
+import android.telephony.ims.feature.ImsFeature;
 
+import com.android.internal.telephony.ims.ImsResolver;
 import com.android.telephony.Rlog;
 
 import java.util.List;
@@ -36,23 +36,20 @@
     protected static final boolean VDBG = false;
     protected static final String TAG = CarrierSmsUtils.class.getSimpleName();
 
-    private static final String CARRIER_IMS_PACKAGE_KEY =
-            CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING;
-
-    /** Return a Carrier-overridden IMS package, if it exists and is a CarrierSmsFilter
-     *
+    /**
+     * Return the package name of the ImsService that is implementing RCS features for the device.
      * @param context calling context
      * @param phone object from telephony
      * @param intent that should match a CarrierSmsFilter
-     * @return the name of the IMS CarrierService package
+     * @return the name of the ImsService implementing RCS features on the device.
      */
     @Nullable
-    public static String getCarrierImsPackageForIntent(
+    public static String getImsRcsPackageForIntent(
             Context context, Phone phone, Intent intent) {
 
-        String carrierImsPackage = getCarrierImsPackage(context, phone);
+        String carrierImsPackage = getImsRcsPackage(phone);
         if (carrierImsPackage == null) {
-            if (VDBG) Rlog.v(TAG, "No CarrierImsPackage override found");
+            if (VDBG) Rlog.v(TAG, "No ImsService found implementing RCS.");
             return null;
         }
 
@@ -71,23 +68,22 @@
         return null;
     }
 
+    /**
+     * @return the package name of the ImsService that is configured to implement RCS, or null if
+     * there is none configured/available.
+     */
     @Nullable
-    private static String getCarrierImsPackage(Context context, Phone phone) {
-        CarrierConfigManager cm = (CarrierConfigManager) context.getSystemService(
-                Context.CARRIER_CONFIG_SERVICE);
-        if (cm == null) {
-            Rlog.e(TAG, "Failed to retrieve CarrierConfigManager");
+    private static String getImsRcsPackage(Phone phone) {
+        ImsResolver resolver = ImsResolver.getInstance();
+        if (resolver == null) {
+            Rlog.i(TAG, "getImsRcsPackage: Device does not support IMS - skipping");
             return null;
         }
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            PersistableBundle config = cm.getConfigForSubId(phone.getSubId());
-            if (config == null) {
-                if (VDBG) Rlog.v(TAG, "No CarrierConfig for subId:" + phone.getSubId());
-                return null;
-            }
-            return config.getString(CARRIER_IMS_PACKAGE_KEY, null);
+            return resolver.getConfiguredImsServicePackageName(phone.getPhoneId(),
+                    ImsFeature.FEATURE_RCS);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java b/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
index 7045171..30d6129 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
@@ -325,6 +325,17 @@
         pw.println("CellBroadcastServiceManager:");
         pw.println(" mEnabled=" + mEnabled);
         pw.println(" mCellBroadcastServicePackage=" + mCellBroadcastServicePackage);
+        if (mEnabled) {
+            try {
+                if (sServiceConnection != null && sServiceConnection.mService != null) {
+                    sServiceConnection.mService.dump(fd, args);
+                } else {
+                    pw.println(" sServiceConnection is null");
+                }
+            } catch (RemoteException e) {
+                pw.println(" mService.dump() threw RemoteException e: " + e.toString());
+            }
+        }
         mLocalLog.dump(fd, pw, args);
         pw.flush();
     }
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 6ae0b1f..a6ff3fa 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -42,6 +42,7 @@
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus;
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
 
 import java.util.List;
 
@@ -2657,4 +2658,57 @@
      * @param result Message that will be sent back to handler.
      */
     default void getSlicingConfig(Message result) {};
+
+   /**
+     * Request the SIM phonebook records of all activated UICC applications
+     *
+     * @param result Callback message containing the count of ADN valid record.
+     */
+    public void getSimPhonebookRecords(Message result);
+
+   /**
+     * Request the SIM phonebook Capacity of all activated UICC applications
+     *
+     */
+    public void getSimPhonebookCapacity(Message result);
+
+    /**
+     * Request to insert/delete/update the SIM phonebook record
+     *
+     * @param phonebookRecordInfo adn record information to be updated
+     * @param result Callback message containing the SIM phonebook record index.
+     */
+    public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecordInfo, Message result);
+
+    /**
+     * Registers the handler when the SIM phonebook is changed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object .
+     */
+    public void registerForSimPhonebookChanged(Handler h, int what, Object obj);
+
+    /**
+     * Unregister for notifications when SIM phonebook has already init done.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSimPhonebookChanged(Handler h);
+
+    /**
+     * Registers the handler when a group of SIM phonebook records received.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSimPhonebookRecordsReceived(Handler h, int what, Object obj);
+
+    /**
+     * Unregister for notifications when a group of SIM phonebook records received.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+     public void unregisterForSimPhonebookRecordsReceived(Handler h);
 }
diff --git a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
index dc990de..f6e8107 100644
--- a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
+++ b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony;
 
+import android.content.ContentValues;
+
+import com.android.internal.telephony.uicc.AdnCapacity;
 import com.android.internal.telephony.uicc.AdnRecord;
 
 /**
@@ -68,49 +71,20 @@
             String newTag, String newPhoneNumber,
             String pin2);
 
-
-
     /**
      * Replace oldAdn with newAdn in ADN-like record in EF
      *
      * getAdnRecordsInEf must be called at least once before this function,
      * otherwise an error will be returned
      *
-     * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
-     * @param oldTag adn tag to be replaced
-     * @param oldPhoneNumber adn number to be replaced
-     *        Set both oldTag and oldPhoneNubmer to "" means to replace an
-     *        empty record, aka, insert new record
-     * @param newTag adn tag to be stored
-     * @param newPhoneNumber adn number ot be stored
-     *        Set both newTag and newPhoneNubmer to "" means to replace the old
-     *        record with empty one, aka, delete old record
-     * @param pin2 required to update EF_FDN, otherwise must be null
      * @param subId user preferred subId
-     * @return true for success
-     */
-    boolean updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid,
-            String oldTag, String oldPhoneNumber,
-            String newTag, String newPhoneNumber,
-            String pin2);
-    /**
-     * Update an ADN-like EF record by record index
-     *
-     * This is useful for iteration the whole ADN file, such as write the whole
-     * phone book or erase/format the whole phonebook
-     *
      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
-     * @param newTag adn tag to be stored
-     * @param newPhoneNumber adn number to be stored
-     *        Set both newTag and newPhoneNubmer to "" means to replace the old
-     *        record with empty one, aka, delete old record
-     * @param index is 1-based adn record index to be updated
+     * @param values including ADN,EMAIL,ANR to be updated
      * @param pin2 required to update EF_FDN, otherwise must be null
      * @return true for success
      */
-    boolean updateAdnRecordsInEfByIndex(int efid, String newTag,
-            String newPhoneNumber, int index,
-            String pin2);
+    boolean updateAdnRecordsInEfBySearchForSubscriber(int subId,
+            int efid, in ContentValues values, String pin2);
 
     /**
      * Update an ADN-like EF record by record index
@@ -118,19 +92,15 @@
      * This is useful for iteration the whole ADN file, such as write the whole
      * phone book or erase/format the whole phonebook
      *
+     * @param subId user preferred subId
      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
-     * @param newTag adn tag to be stored
-     * @param newPhoneNumber adn number to be stored
-     *        Set both newTag and newPhoneNubmer to "" means to replace the old
-     *        record with empty one, aka, delete old record
+     * @param values including ADN,EMAIL,ANR to be updated
      * @param index is 1-based adn record index to be updated
      * @param pin2 required to update EF_FDN, otherwise must be null
-     * @param subId user preferred subId
      * @return true for success
      */
-    boolean updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid, String newTag,
-            String newPhoneNumber, int index,
-            String pin2);
+    boolean updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid, in ContentValues values,
+            int index, String pin2);
 
     /**
      * Get the max munber of records in efid
@@ -157,4 +127,11 @@
     @UnsupportedAppUsage
     int[] getAdnRecordsSizeForSubscriber(int subId, int efid);
 
+    /**
+     * Get the capacity of ADN records
+     *
+     * @param subId user preferred subId
+     * @return AdnCapacity
+     */
+    AdnCapacity getAdnRecordsCapacityForSubscriber(int subId);
 }
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index 84a9d26..b78d1ab 100644
--- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -17,26 +17,32 @@
 package com.android.internal.telephony;
 
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentValues;
 import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.text.TextUtils;
 
+import com.android.internal.telephony.uicc.AdnCapacity;
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.AdnRecordCache;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccConstants;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.SimPhonebookRecordCache;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.telephony.Rlog;
 
-import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.List;
 
 /**
- * SimPhoneBookInterfaceManager to provide an inter-process communication to
+ * IccPhoneBookInterfaceManager to provide an inter-process communication to
  * access ADN-like SIM records.
  */
 public class IccPhoneBookInterfaceManager {
@@ -48,6 +54,7 @@
     protected Phone mPhone;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected AdnRecordCache mAdnCache;
+    protected SimPhonebookRecordCache mSimPbRecordCache;
 
     protected static final int EVENT_GET_SIZE_DONE = 1;
     protected static final int EVENT_LOAD_DONE = 2;
@@ -118,9 +125,13 @@
         if (r != null) {
             mAdnCache = r.getAdnCache();
         }
+
+        mSimPbRecordCache = new SimPhonebookRecordCache(
+                phone.getContext(), phone.getPhoneId(), phone.mCi);
     }
 
     public void dispose() {
+        mSimPbRecordCache.dispose();
     }
 
     public void updateIccRecords(IccRecords iccRecords) {
@@ -141,60 +152,81 @@
         Rlog.e(LOG_TAG, "[IccPbInterfaceManager] " + msg);
     }
 
+    private AdnRecord generateAdnRecordWithOldTagByContentValues(ContentValues values) {
+        if (values == null) {
+            return null;
+        }
+        final String oldTag = values.getAsString(IccProvider.STR_TAG);
+        final String oldPhoneNumber = values.getAsString(IccProvider.STR_NUMBER);
+        final String oldEmail = values.getAsString(IccProvider.STR_EMAILS);
+        final String oldAnr = values.getAsString(IccProvider.STR_ANRS);;
+        String[] oldEmailArray = TextUtils.isEmpty(oldEmail)
+                ? null : getEmailStringArray(oldEmail);
+        String[] oldAnrArray = TextUtils.isEmpty(oldAnr) ? null : getAnrStringArray(oldAnr);
+        return new AdnRecord(oldTag, oldPhoneNumber, oldEmailArray, oldAnrArray);
+    }
+
+    private AdnRecord generateAdnRecordWithNewTagByContentValues(ContentValues values) {
+        if (values == null) {
+            return null;
+        }
+        final String newTag = values.getAsString(IccProvider.STR_NEW_TAG);
+        final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER);
+        final String newEmail = values.getAsString(IccProvider.STR_NEW_EMAILS);
+        final String newAnr = values.getAsString(IccProvider.STR_NEW_ANRS);
+        String[] newEmailArray = TextUtils.isEmpty(newEmail)
+                ? null : getEmailStringArray(newEmail);
+        String[] newAnrArray = TextUtils.isEmpty(newAnr) ? null : getAnrStringArray(newAnr);
+        return new AdnRecord(newTag, newPhoneNumber, newEmailArray, newAnrArray);
+    }
+
     /**
      * Replace oldAdn with newAdn in ADN-like record in EF
      *
      * getAdnRecordsInEf must be called at least once before this function,
-     * otherwise an error will be returned. Currently the email field
-     * if set in the ADN record is ignored.
+     * otherwise an error will be returned.
      * throws SecurityException if no WRITE_CONTACTS permission
      *
      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
-     * @param oldTag adn tag to be replaced
-     * @param oldPhoneNumber adn number to be replaced
-     *        Set both oldTag and oldPhoneNubmer to "" means to replace an
-     *        empty record, aka, insert new record
-     * @param newTag adn tag to be stored
-     * @param newPhoneNumber adn number ot be stored
-     *        Set both newTag and newPhoneNubmer to "" means to replace the old
-     *        record with empty one, aka, delete old record
+     * @param values old adn tag,  phone number, email and anr to be replaced
+     *        new adn tag,  phone number, email and anr to be stored
      * @param pin2 required to update EF_FDN, otherwise must be null
      * @return true for success
      */
-    public boolean
-    updateAdnRecordsInEfBySearch (int efid,
-            String oldTag, String oldPhoneNumber,
-            String newTag, String newPhoneNumber, String pin2) {
-
+    public boolean updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values,
+            String pin2) {
 
         if (mPhone.getContext().checkCallingOrSelfPermission(
-                android.Manifest.permission.WRITE_CONTACTS)
-            != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    "Requires android.permission.WRITE_CONTACTS permission");
+                android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires android.permission.WRITE_CONTACTS permission");
         }
 
-
-        if (DBG) logd("updateAdnRecordsInEfBySearch: efid=0x" +
-                Integer.toHexString(efid).toUpperCase() + " ("+ Rlog.pii(LOG_TAG, oldTag) + "," +
-                Rlog.pii(LOG_TAG, oldPhoneNumber) + ")" + "==>" + " ("+ Rlog.pii(LOG_TAG, newTag) +
-                "," + Rlog.pii(LOG_TAG, newPhoneNumber) + ")"+ " pin2=" + Rlog.pii(LOG_TAG, pin2));
-
         efid = updateEfForIccType(efid);
 
+        if (DBG) {
+            logd("updateAdnRecordsWithContentValuesInEfBySearch: efid=" + efid + ", values = " +
+                values + ", pin2=" + pin2);
+        }
+
         checkThread();
         Request updateRequest = new Request();
         synchronized (updateRequest) {
             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
-            AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber);
-            AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
-            if (mAdnCache != null) {
-                mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
+            AdnRecord oldAdn = generateAdnRecordWithOldTagByContentValues(values);
+            AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(values);
+            if (usesPbCache(efid)) {
+                mSimPbRecordCache.updateSimPbAdnBySearch(oldAdn, newAdn, response);
                 waitForResult(updateRequest);
                 return (boolean) updateRequest.mResult;
             } else {
-                loge("Failure while trying to update by search due to uninitialised adncache");
-                return false;
+                if (mAdnCache != null) {
+                    mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
+                    waitForResult(updateRequest);
+                    return (boolean) updateRequest.mResult;
+                } else {
+                    loge("Failure while trying to update by search due to uninitialised adncache");
+                    return false;
+                }
             }
         }
     }
@@ -210,15 +242,14 @@
      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
      * @param newTag adn tag to be stored
      * @param newPhoneNumber adn number to be stored
-     *        Set both newTag and newPhoneNubmer to "" means to replace the old
+     *        Set both newTag and newPhoneNumber to "" means to replace the old
      *        record with empty one, aka, delete old record
      * @param index is 1-based adn record index to be updated
      * @param pin2 required to update EF_FDN, otherwise must be null
      * @return true for success
      */
     public boolean
-    updateAdnRecordsInEfByIndex(int efid, String newTag,
-            String newPhoneNumber, int index, String pin2) {
+    updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2) {
 
         if (mPhone.getContext().checkCallingOrSelfPermission(
                 android.Manifest.permission.WRITE_CONTACTS)
@@ -226,25 +257,30 @@
             throw new SecurityException(
                     "Requires android.permission.WRITE_CONTACTS permission");
         }
-
-        if (DBG) logd("updateAdnRecordsInEfByIndex: efid=0x" +
-                Integer.toHexString(efid).toUpperCase() + " Index=" + index + " ==> " + "(" +
-                Rlog.pii(LOG_TAG, newTag) + "," + Rlog.pii(LOG_TAG, newPhoneNumber) + ")" +
-                " pin2=" + Rlog.pii(LOG_TAG, pin2));
-
+        efid = updateEfForIccType(efid);
+        if (DBG) {
+            logd("updateAdnRecordsInEfByIndex: efid=" + efid + ", values = " +
+                values + " index=" + index + ", pin2=" + pin2);
+        }
 
         checkThread();
         Request updateRequest = new Request();
         synchronized (updateRequest) {
             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
-            AdnRecord newAdn = new AdnRecord(efid, index, newTag, newPhoneNumber);
-            if (mAdnCache != null) {
-                mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
+            AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(values);
+            if (usesPbCache(efid)) {
+                mSimPbRecordCache.updateSimPbAdnByRecordId(index, newAdn, response);
                 waitForResult(updateRequest);
                 return (boolean) updateRequest.mResult;
             } else {
-                loge("Failure while trying to update by index due to uninitialised adncache");
-                return false;
+                if (mAdnCache != null) {
+                    mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
+                    waitForResult(updateRequest);
+                    return (boolean) updateRequest.mResult;
+                } else {
+                    loge("Failure while trying to update by index due to uninitialised adncache");
+                    return false;
+                }
             }
         }
     }
@@ -301,13 +337,20 @@
         Request loadRequest = new Request();
         synchronized (loadRequest) {
             Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, loadRequest);
-            if (mAdnCache != null) {
-                mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response);
+            if (usesPbCache(efid)) {
+                mSimPbRecordCache.requestLoadAllPbRecords(response);
                 waitForResult(loadRequest);
                 return (List<AdnRecord>) loadRequest.mResult;
             } else {
-                loge("Failure while trying to load from SIM due to uninitialised adncache");
-                return null;
+                if (mAdnCache != null) {
+                    mAdnCache.requestLoadAllAdnLike(efid,
+                            mAdnCache.extensionEfForEf(efid), response);
+                    waitForResult(loadRequest);
+                    return (List<AdnRecord>) loadRequest.mResult;
+                } else {
+                    loge("Failure while trying to load from SIM due to uninitialised adncache");
+                    return null;
+                }
             }
         }
     }
@@ -344,5 +387,67 @@
         }
         return efid;
     }
-}
 
+    private String[] getEmailStringArray(String str) {
+        return str != null ? str.split(",") : null;
+    }
+
+    private String[] getAnrStringArray(String str) {
+        return str != null ? str.split(":") : null;
+    }
+
+    /**
+     * Get the capacity of ADN records
+     *
+     * @return AdnCapacity
+     */
+    public AdnCapacity getAdnRecordsCapacity() {
+        if (DBG) logd("getAdnRecordsCapacity" );
+        if (mPhone.getContext().checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_CONTACTS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "Requires android.permission.READ_CONTACTS permission");
+        }
+        int phoneId = mPhone.getPhoneId();
+
+        UiccProfile profile = UiccController.getInstance().getUiccProfileForPhone(phoneId);
+
+        if (profile != null) {
+            IccCardConstants.State cardstate = profile.getState();
+            if (cardstate == IccCardConstants.State.READY
+                    || cardstate == IccCardConstants.State.LOADED) {
+                checkThread();
+                AdnCapacity capacity = mSimPbRecordCache.isEnabled()
+                        ? mSimPbRecordCache.getAdnCapacity() : null;
+                if (capacity == null) {
+                    loge("Adn capacity is null");
+                    return null;
+                }
+
+                if (DBG) logd("getAdnRecordsCapacity on slot " + phoneId
+                        + ": max adn=" + capacity.getMaxAdnCount()
+                        + ", used adn=" + capacity.getUsedAdnCount()
+                        + ", max email=" + capacity.getMaxEmailCount()
+                        + ", used email=" + capacity.getUsedEmailCount()
+                        + ", max anr=" + capacity.getMaxAnrCount()
+                        + ", used anr=" + capacity.getUsedAnrCount()
+                        + ", max name length="+ capacity.getMaxNameLength()
+                        + ", max number length =" + capacity.getMaxNumberLength()
+                        + ", max email length =" + capacity.getMaxEmailLength()
+                        + ", max anr length =" + capacity.getMaxAnrLength());
+                return capacity;
+            } else {
+                logd("No UICC when getAdnRecordsCapacity.");
+            }
+        } else {
+            logd("sim state is not ready when getAdnRecordsCapacity.");
+        }
+        return null;
+    }
+
+    private boolean usesPbCache(int efid) {
+        return mSimPbRecordCache.isEnabled() &&
+                    (efid == IccConstants.EF_PBR || efid == IccConstants.EF_ADN);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java
index b2ac07f..7a128c0 100644
--- a/src/java/com/android/internal/telephony/IccProvider.java
+++ b/src/java/com/android/internal/telephony/IccProvider.java
@@ -31,6 +31,7 @@
 import android.telephony.TelephonyFrameworkInitializer;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.IccConstants;
 import com.android.telephony.Rlog;
@@ -51,6 +52,7 @@
         "name",
         "number",
         "emails",
+        "anrs",
         "_id"
     };
 
@@ -62,10 +64,24 @@
     protected static final int SDN_SUB = 6;
     protected static final int ADN_ALL = 7;
 
-    protected static final String STR_TAG = "tag";
-    protected static final String STR_NUMBER = "number";
-    protected static final String STR_EMAILS = "emails";
-    protected static final String STR_PIN2 = "pin2";
+    @VisibleForTesting
+    public static final String STR_TAG = "tag";
+    @VisibleForTesting
+    public static final String STR_NUMBER = "number";
+    @VisibleForTesting
+    public static final String STR_EMAILS = "emails";
+    @VisibleForTesting
+    public static final String STR_ANRS = "anrs";
+    @VisibleForTesting
+    public static final String STR_NEW_TAG = "newTag";
+    @VisibleForTesting
+    public static final String STR_NEW_NUMBER = "newNumber";
+    @VisibleForTesting
+    public static final String STR_NEW_EMAILS = "newEmails";
+    @VisibleForTesting
+    public static final String STR_NEW_ANRS = "newAnrs";
+    @VisibleForTesting
+    public static final String STR_PIN2 = "pin2";
 
     private static final UriMatcher URL_MATCHER =
                             new UriMatcher(UriMatcher.NO_MATCH);
@@ -203,10 +219,19 @@
                         "Cannot insert into URL: " + url);
         }
 
-        String tag = initialValues.getAsString("tag");
-        String number = initialValues.getAsString("number");
-        // TODO(): Read email instead of sending null.
-        boolean success = addIccRecordToEf(efType, tag, number, null, pin2, subId);
+        // We're not using the incoming initialValues
+        // so we can check/gate the arguments.
+        String tag = initialValues.getAsString(STR_TAG);
+        String number = initialValues.getAsString(STR_NUMBER);
+        String emails = initialValues.getAsString(STR_EMAILS);
+        String anrs = initialValues.getAsString(STR_ANRS);
+
+        ContentValues values = new ContentValues();
+        values.put(STR_NEW_TAG, tag);
+        values.put(STR_NEW_NUMBER, number);
+        values.put(STR_NEW_EMAILS, emails);
+        values.put(STR_NEW_ANRS, anrs);
+        boolean success = updateIccRecordInEf(efType, values, pin2, subId);
 
         if (!success) {
             return null;
@@ -299,7 +324,8 @@
         // parse where clause
         String tag = null;
         String number = null;
-        String[] emails = null;
+        String emails = null;
+        String anrs = null;
         String pin2 = null;
 
         String[] tokens = where.split(" AND ");
@@ -323,18 +349,24 @@
             } else if (STR_NUMBER.equals(key)) {
                 number = normalizeValue(val);
             } else if (STR_EMAILS.equals(key)) {
-                //TODO(): Email is null.
-                emails = null;
+                emails = normalizeValue(val);
+            } else if (STR_ANRS.equals(key)) {
+                anrs = normalizeValue(val);
             } else if (STR_PIN2.equals(key)) {
                 pin2 = normalizeValue(val);
             }
         }
 
-        if (efType == FDN && TextUtils.isEmpty(pin2)) {
+        ContentValues values = new ContentValues();
+        values.put(STR_TAG, tag);
+        values.put(STR_NUMBER, number);
+        values.put(STR_EMAILS, emails);
+        values.put(STR_ANRS, anrs);
+        if ((efType == FDN) && TextUtils.isEmpty(pin2)) {
             return 0;
         }
-
-        boolean success = deleteIccRecordFromEf(efType, tag, number, emails, pin2, subId);
+        if (DBG) log("delete mvalues= " + values);
+        boolean success = updateIccRecordInEf(efType, values, pin2, subId);
         if (!success) {
             return 0;
         }
@@ -380,15 +412,7 @@
                         "Cannot insert into URL: " + url);
         }
 
-        String tag = values.getAsString("tag");
-        String number = values.getAsString("number");
-        String[] emails = null;
-        String newTag = values.getAsString("newTag");
-        String newNumber = values.getAsString("newNumber");
-        String[] newEmails = null;
-        // TODO(): Update for email.
-        boolean success = updateIccRecordInEf(efType, tag, number,
-                newTag, newNumber, pin2, subId);
+        boolean success = updateIccRecordInEf(efType, values, pin2, subId);
 
         if (!success) {
             return 0;
@@ -435,19 +459,10 @@
     }
 
     private boolean
-    addIccRecordToEf(int efType, String name, String number, String[] emails,
-            String pin2, int subId) {
-        if (DBG) log("addIccRecordToEf: efType=0x" + Integer.toHexString(efType).toUpperCase() +
-                ", name=" + Rlog.pii(TAG, name) + ", number=" + Rlog.pii(TAG, number) +
-                ", emails=" + Rlog.pii(TAG, emails) + ", subscription=" + subId);
-
+    updateIccRecordInEf(int efType, ContentValues values, String pin2, int subId) {
         boolean success = false;
-
-        // TODO: do we need to call getAdnRecordsInEf() before calling
-        // updateAdnRecordsInEfBySearch()? In any case, we will leave
-        // the UI level logic to fill that prereq if necessary. But
-        // hopefully, we can remove this requirement.
-
+        if (DBG) log("updateIccRecordInEf: efType=" + efType +
+                ", values: [ "+ values + "  ], subId:" + subId);
         try {
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
                     TelephonyFrameworkInitializer
@@ -455,37 +470,9 @@
                             .getIccPhoneBookServiceRegisterer()
                             .get());
             if (iccIpb != null) {
-                success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType,
-                        "", "", name, number, pin2);
-            }
-        } catch (RemoteException ex) {
-            // ignore it
-        } catch (SecurityException ex) {
-            if (DBG) log(ex.toString());
-        }
-        if (DBG) log("addIccRecordToEf: " + success);
-        return success;
-    }
-
-    private boolean
-    updateIccRecordInEf(int efType, String oldName, String oldNumber,
-            String newName, String newNumber, String pin2, int subId) {
-        if (DBG) log("updateIccRecordInEf: efType=0x" + Integer.toHexString(efType).toUpperCase() +
-                ", oldname=" + Rlog.pii(TAG, oldName) + ", oldnumber=" + Rlog.pii(TAG, oldNumber) +
-                ", newname=" + Rlog.pii(TAG, newName) + ", newnumber=" + Rlog.pii(TAG, newName) +
-                ", subscription=" + subId);
-
-        boolean success = false;
-
-        try {
-            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
-                    TelephonyFrameworkInitializer
-                            .getTelephonyServiceManager()
-                            .getIccPhoneBookServiceRegisterer()
-                            .get());
-            if (iccIpb != null) {
-                success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType, oldName,
-                        oldNumber, newName, newNumber, pin2);
+                success = iccIpb
+                        .updateAdnRecordsInEfBySearchForSubscriber(
+                            subId, efType, values, pin2);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -496,35 +483,6 @@
         return success;
     }
 
-
-    private boolean deleteIccRecordFromEf(int efType, String name, String number, String[] emails,
-            String pin2, int subId) {
-        if (DBG) log("deleteIccRecordFromEf: efType=0x" +
-                Integer.toHexString(efType).toUpperCase() + ", name=" + Rlog.pii(TAG, name) +
-                ", number=" + Rlog.pii(TAG, number) + ", emails=" + Rlog.pii(TAG, emails) +
-                ", pin2=" + Rlog.pii(TAG, pin2) + ", subscription=" + subId);
-
-        boolean success = false;
-
-        try {
-            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
-                    TelephonyFrameworkInitializer
-                            .getTelephonyServiceManager()
-                            .getIccPhoneBookServiceRegisterer()
-                            .get());
-            if (iccIpb != null) {
-                success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType,
-                          name, number, "", "", pin2);
-            }
-        } catch (RemoteException ex) {
-            // ignore it
-        } catch (SecurityException ex) {
-            if (DBG) log(ex.toString());
-        }
-        if (DBG) log("deleteIccRecordFromEf: " + success);
-        return success;
-    }
-
     /**
      * Loads an AdnRecord into a MatrixCursor. Must be called with mLock held.
      *
@@ -534,7 +492,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void loadRecord(AdnRecord record, MatrixCursor cursor, int id) {
         if (!record.isEmpty()) {
-            Object[] contact = new Object[4];
+            Object[] contact = new Object[5];
             String alphaTag = record.getAlphaTag();
             String number = record.getNumber();
 
@@ -552,7 +510,19 @@
                 }
                 contact[2] = emailString.toString();
             }
-            contact[3] = id;
+
+            String[] anrs = record.getAdditionalNumbers();
+            if (anrs != null) {
+                StringBuilder anrString = new StringBuilder();
+                for (String anr : anrs) {
+                    if (DBG) log("Adding anr:" + anr);
+                    anrString.append(anr);
+                    anrString.append(":");
+                }
+                contact[3] = anrString.toString();
+            }
+
+            contact[4] = id;
             cursor.addRow(contact);
         }
     }
diff --git a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
index 54f6aa1..3535678 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -202,7 +202,6 @@
                         new NetworkRegStateCallback());
             } catch (RemoteException exception) {
                 // Remote exception means that the binder already died.
-                mDeathRecipient.binderDied();
                 logd("RemoteException " + exception);
             }
         }
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index cdea374..04c1c46 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -129,6 +129,7 @@
 import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
@@ -6004,6 +6005,100 @@
         }
     }
 
+    @Override
+    public void getSimPhonebookRecords(Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        if (radioProxy != null) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            }
+
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+                android.hardware.radio.V1_6.IRadio radioProxy16 =
+                            android.hardware.radio.V1_6.IRadio.castFrom(radioProxy);
+                try {
+                    radioProxy16.getSimPhonebookRecords(rr.mSerial);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "getPhonebookRecords", e);
+                }
+            } else {
+                riljLog("Unsupported API in lower than version 1.6 radio HAL" );
+                if (result != null) {
+                    AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                    result.sendToTarget();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void getSimPhonebookCapacity(Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        if (radioProxy != null) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            }
+
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+                android.hardware.radio.V1_6.IRadio radioProxy16 =
+                            android.hardware.radio.V1_6.IRadio.castFrom(radioProxy);
+                try {
+                    radioProxy16.getSimPhonebookCapacity(rr.mSerial);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "getPhonebookRecords", e);
+                }
+            } else {
+                riljLog("Unsupported API in lower than version 1.6 radio HAL" );
+                if (result != null) {
+                    AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                    result.sendToTarget();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        if (radioProxy != null) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        + " with " + phonebookRecord.toString());
+            }
+
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+                android.hardware.radio.V1_6.IRadio radioProxy16 =
+                        android.hardware.radio.V1_6.IRadio.castFrom(radioProxy);
+
+                android.hardware.radio.V1_6.PhonebookRecordInfo pbRecordInfo =
+                        phonebookRecord.toPhonebookRecordInfo();
+                try {
+                     radioProxy16.updateSimPhonebookRecords(rr.mSerial, pbRecordInfo);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "updatePhonebookRecord", e);
+                }
+            } else {
+                riljLog("Unsupported API in lower than version 1.6 radio HAL" );
+                if (result != null) {
+                    AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                    result.sendToTarget();
+                }
+            }
+        }
+    }
+
     //***** Private Methods
     /** Helper that gets V1.6 of the radio interface OR sends back REQUEST_NOT_SUPPORTED */
     @Nullable private android.hardware.radio.V1_6.IRadio getRadioV16(Message msg) {
@@ -6996,6 +7091,12 @@
                 return "GET_ALLOWED_NETWORK_TYPES_BITMAP";
             case RIL_REQUEST_GET_SLICING_CONFIG:
                 return "GET_SLICING_CONFIG";
+            case RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS:
+                return "GET_SIM_PHONEBOOK_RECORDS";
+            case RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD:
+                return "UPDATE_SIM_PHONEBOOK_RECORD";
+            case RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY:
+                return "GET_SIM_PHONEBOOK_CAPACITY";
             default: return "<unknown request>";
         }
     }
@@ -7119,6 +7220,10 @@
                 return "UNSOL_REGISTRATION_FAILED";
             case RIL_UNSOL_BARRING_INFO_CHANGED:
                 return "UNSOL_BARRING_INFO_CHANGED";
+            case RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED:
+                return "UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED";
+            case RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED:
+                return "UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED";
             default:
                 return "<unknown response>";
         }
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponse.java b/src/java/com/android/internal/telephony/RadioConfigResponse.java
index 97cdc77..8a60fe5 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponse.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponse.java
@@ -16,13 +16,14 @@
 
 package com.android.internal.telephony;
 
-import static android.telephony.TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED;
 import static android.telephony.TelephonyManager
         .CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE;
 import static android.telephony.TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED;
 import static android.telephony.TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE;
+import static android.telephony.TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM;
 import static android.telephony.TelephonyManager.CAPABILITY_SLICING_CONFIG_SUPPORTED;
 import static android.telephony.TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING;
+import static android.telephony.TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK;
 import static android.telephony.TelephonyManager.RadioInterfaceCapability;
 
 import android.hardware.radio.V1_0.RadioError;
@@ -299,8 +300,8 @@
 
         Rlog.d(TAG, "Radio Hal Version = " + radioHalVersion.toString());
         if (radioHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) {
-            caps.add(CAPABILITY_ALLOWED_NETWORK_TYPES_USED);
-            Rlog.d(TAG, "CAPABILITY_ALLOWED_NETWORK_TYPES_USED");
+            caps.add(CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK);
+            Rlog.d(TAG, "CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK");
 
             if (!modemReducedFeatureSet1) {
                 caps.add(CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE);
@@ -313,6 +314,8 @@
                 Rlog.d(TAG, "CAPABILITY_SLICING_CONFIG_SUPPORTED");
                 caps.add(CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
                 Rlog.d(TAG, "CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED");
+                caps.add(CAPABILITY_SIM_PHONEBOOK_IN_MODEM);
+                Rlog.d(TAG, "CAPABILITY_SIM_PHONEBOOK_IN_MODEM");
             }
         }
         return caps;
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index 00d0830..d1ed81b 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -68,6 +68,8 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UNTHROTTLE_APN;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_VOICE_RADIO_TECH_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOl_CDMA_PRL_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED;
 
 import android.hardware.radio.V1_0.CdmaCallWaiting;
 import android.hardware.radio.V1_0.CdmaInformationRecord;
@@ -114,8 +116,10 @@
 import com.android.internal.telephony.dataconnection.KeepaliveStatus;
 import com.android.internal.telephony.gsm.SsData;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
+import com.android.internal.telephony.uicc.ReceivedPhonebookRecords;
 import com.android.internal.telephony.uicc.IccRefreshResponse;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -1078,18 +1082,39 @@
      * @param indicationType RadioIndicationType
      */
     public void simPhonebookChanged(int indicationType) {
+        mRil.processIndication(indicationType);
 
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED);
+        }
+
+        mRil.mSimPhonebookChangedRegistrants.notifyRegistrants();
     }
 
     /**
-     * Indicates the content of all the used records in the SIM phonebook.
-     *
+     * Indicates the content of all the used records in the SIM phonebook..
      * @param indicationType RadioIndicationType
      * @param records Content of the SIM phonebook records
      */
     public void simPhonebookRecordsReceived(int indicationType, byte status,
             ArrayList<PhonebookRecordInfo> records) {
+        mRil.processIndication(indicationType);
 
+        List<SimPhonebookRecord> simPhonebookRecords = new ArrayList<SimPhonebookRecord>();
+
+        for (PhonebookRecordInfo record : records) {
+            simPhonebookRecords.add(new SimPhonebookRecord(record));
+        }
+
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogRet(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED,
+                    "status = " + status +
+                    " received " + records.size() + " records");
+        }
+
+        mRil.mSimPhonebookRecordsReceivedRegistrants.notifyRegistrants(
+                new AsyncResult(null,
+                new ReceivedPhonebookRecords(status, simPhonebookRecords), null));
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index b1332a1..9e08b9c 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -63,6 +63,7 @@
 
 import com.android.internal.telephony.dataconnection.KeepaliveStatus;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+import com.android.internal.telephony.uicc.AdnCapacity;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccIoResult;
@@ -2014,7 +2015,8 @@
     public void getSimPhonebookCapacityResponse(
             android.hardware.radio.V1_6.RadioResponseInfo responseInfo,
             android.hardware.radio.V1_6.PhonebookCapacity pbCapacity) {
-        responseVoid_1_6(responseInfo);
+        AdnCapacity capacity = new AdnCapacity(pbCapacity);
+        responseAdnCapacity(responseInfo, capacity);
     }
 
     /**
@@ -2024,7 +2026,19 @@
     public void updateSimPhonebookRecordsResponse(
             android.hardware.radio.V1_6.RadioResponseInfo responseInfo,
             int updatedRecordIndex) {
-        responseVoid_1_6(responseInfo);
+        responseInts_1_6(responseInfo, updatedRecordIndex);
+    }
+
+    private void responseAdnCapacity(
+            android.hardware.radio.V1_6.RadioResponseInfo responseInfo,
+            AdnCapacity capacity) {
+        RILRequest rr = mRil.processResponse_1_6(responseInfo);
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, capacity);
+            }
+            mRil.processResponseDone_1_6(rr, responseInfo, capacity);
+        }
     }
 
     private void responseIccCardStatus(RadioResponseInfo responseInfo, CardStatus cardStatus) {
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 48e3871..6633f80 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -2516,9 +2516,9 @@
         if (carrierPackages != null && carrierPackages.size() == 1) {
             return carrierPackages.get(0);
         }
-        // If there is no carrier package which implements CarrierMessagingService, then lookup if
-        // for a carrierImsPackage that implements CarrierMessagingService.
-        return CarrierSmsUtils.getCarrierImsPackageForIntent(mContext, mPhone,
+        // If there is no carrier package which implements CarrierMessagingService, then lookup
+        // an ImsService implementing RCS that also implements CarrierMessagingService.
+        return CarrierSmsUtils.getImsRcsPackageForIntent(mContext, mPhone,
                 new Intent(CarrierMessagingService.SERVICE_INTERFACE));
     }
 
diff --git a/src/java/com/android/internal/telephony/SmsPermissions.java b/src/java/com/android/internal/telephony/SmsPermissions.java
index 3837881..44751ac 100644
--- a/src/java/com/android/internal/telephony/SmsPermissions.java
+++ b/src/java/com/android/internal/telephony/SmsPermissions.java
@@ -78,14 +78,14 @@
     /**
      * Enforces that the caller is one of the following apps:
      * <ul>
-     *     <li> IMS App
+     *     <li> IMS App determined by telephony to implement RCS features
      *     <li> Carrier App
      * </ul>
      */
     public void enforceCallerIsImsAppOrCarrierApp(String message) {
-        String carrierImsPackage = CarrierSmsUtils.getCarrierImsPackageForIntent(mContext,
+        String imsRcsPackage = CarrierSmsUtils.getImsRcsPackageForIntent(mContext,
                 mPhone, new Intent(CarrierMessagingService.SERVICE_INTERFACE));
-        if (carrierImsPackage != null && packageNameMatchesCallingUid(carrierImsPackage)) {
+        if (imsRcsPackage != null && packageNameMatchesCallingUid(imsRcsPackage)) {
             return;
         }
         TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(
diff --git a/src/java/com/android/internal/telephony/UiccPhoneBookController.java b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
index 34be94c..5b55c35 100644
--- a/src/java/com/android/internal/telephony/UiccPhoneBookController.java
+++ b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2008 The Android Open Source Project
- * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2011-2013, 2021 The Linux Foundation. All rights reserved.
  * Not a Contribution.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,7 +22,9 @@
 import android.os.Build;
 import android.os.TelephonyServiceManager.ServiceRegisterer;
 import android.telephony.TelephonyFrameworkInitializer;
+import android.content.ContentValues;
 
+import com.android.internal.telephony.uicc.AdnCapacity;
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.telephony.Rlog;
 
@@ -46,44 +48,24 @@
     public boolean
     updateAdnRecordsInEfBySearch (int efid, String oldTag, String oldPhoneNumber,
             String newTag, String newPhoneNumber, String pin2) throws android.os.RemoteException {
-        return updateAdnRecordsInEfBySearchForSubscriber(getDefaultSubscription(), efid, oldTag,
-                oldPhoneNumber, newTag, newPhoneNumber, pin2);
+        ContentValues values = new ContentValues();
+        values.put(IccProvider.STR_TAG, oldTag);
+        values.put(IccProvider.STR_NUMBER, oldPhoneNumber);
+        values.put(IccProvider.STR_NEW_TAG, newTag);
+        values.put(IccProvider.STR_NEW_NUMBER, newPhoneNumber);
+        return updateAdnRecordsInEfBySearchForSubscriber(getDefaultSubscription(),
+                efid, values, pin2);
     }
 
     @Override
     public boolean
-    updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid, String oldTag,
-            String oldPhoneNumber, String newTag, String newPhoneNumber,
-            String pin2) throws android.os.RemoteException {
+    updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid, ContentValues values,
+            int index, String pin2) throws android.os.RemoteException {
         IccPhoneBookInterfaceManager iccPbkIntMgr =
                              getIccPhoneBookInterfaceManager(subId);
         if (iccPbkIntMgr != null) {
-            return iccPbkIntMgr.updateAdnRecordsInEfBySearch(efid, oldTag,
-                    oldPhoneNumber, newTag, newPhoneNumber, pin2);
-        } else {
-            Rlog.e(TAG,"updateAdnRecordsInEfBySearch iccPbkIntMgr is" +
-                      " null for Subscription:"+subId);
-            return false;
-        }
-    }
-
-    @Override
-    public boolean
-    updateAdnRecordsInEfByIndex(int efid, String newTag,
-            String newPhoneNumber, int index, String pin2) throws android.os.RemoteException {
-        return updateAdnRecordsInEfByIndexForSubscriber(getDefaultSubscription(), efid, newTag,
-                newPhoneNumber, index, pin2);
-    }
-
-    @Override
-    public boolean
-    updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid, String newTag,
-            String newPhoneNumber, int index, String pin2) throws android.os.RemoteException {
-        IccPhoneBookInterfaceManager iccPbkIntMgr =
-                             getIccPhoneBookInterfaceManager(subId);
-        if (iccPbkIntMgr != null) {
-            return iccPbkIntMgr.updateAdnRecordsInEfByIndex(efid, newTag,
-                    newPhoneNumber, index, pin2);
+            return iccPbkIntMgr.updateAdnRecordsInEfByIndex(efid, values,
+                    index, pin2);
         } else {
             Rlog.e(TAG,"updateAdnRecordsInEfByIndex iccPbkIntMgr is" +
                       " null for Subscription:"+subId);
@@ -129,6 +111,34 @@
         }
     }
 
+    @Override
+    public AdnCapacity getAdnRecordsCapacityForSubscriber(int subId)
+           throws android.os.RemoteException {
+        IccPhoneBookInterfaceManager iccPbkIntMgr = getIccPhoneBookInterfaceManager(subId);
+        if (iccPbkIntMgr != null) {
+            return iccPbkIntMgr.getAdnRecordsCapacity();
+        } else {
+            Rlog.e(TAG, "getAdnRecordsCapacity iccPbkIntMgr is null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    @Override
+    public boolean
+    updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid,
+            ContentValues values, String pin2)
+            throws android.os.RemoteException {
+        IccPhoneBookInterfaceManager iccPbkIntMgr = getIccPhoneBookInterfaceManager(subId);
+        if (iccPbkIntMgr != null) {
+            return iccPbkIntMgr.updateAdnRecordsInEfBySearchForSubscriber(
+                efid, values, pin2);
+        } else {
+            Rlog.e(TAG,"updateAdnRecordsInEfBySearchForSubscriber " +
+                "iccPbkIntMgr is null for Subscription:"+subId);
+            return false;
+        }
+    }
+
     /**
      * get phone book interface manager object based on subscription.
      **/
diff --git a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
index 3b5e0e7..53b7515 100644
--- a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
@@ -194,7 +194,6 @@
                 registerDataThrottlersFirstTime();
 
             } catch (RemoteException e) {
-                mDeathRecipient.binderDied();
                 loge("Remote exception. " + e);
             }
         }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
index 91a6d96..3ba577a 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
@@ -222,6 +222,7 @@
             mDeathRecipient = new DataServiceManagerDeathRecipient();
             mBound = true;
             mLastBoundPackageName = getDataServicePackageName();
+            removeMessages(EVENT_WATCHDOG_TIMEOUT);
 
             try {
                 service.linkToDeath(mDeathRecipient, 0);
@@ -231,11 +232,9 @@
                 mIDataService.registerForUnthrottleApn(mPhone.getPhoneId(),
                         new CellularDataServiceCallback("unthrottleApn"));
             } catch (RemoteException e) {
-                mDeathRecipient.binderDied();
                 loge("Remote exception. " + e);
                 return;
             }
-            removeMessages(EVENT_WATCHDOG_TIMEOUT);
             mServiceBindingChangedRegistrants.notifyResult(true);
         }
         @Override
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index b717582..399baa5 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -69,6 +69,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -128,6 +129,27 @@
     // Delay between dynamic ImsService queries.
     private static final int DELAY_DYNAMIC_QUERY_MS = 5000;
 
+    private static ImsResolver sInstance;
+
+    /**
+     * Create the ImsResolver Service singleton instance.
+     */
+    public static void make(Context context, String defaultMmTelPackageName,
+            String defaultRcsPackageName, int numSlots, ImsFeatureBinderRepository repo) {
+        if (sInstance == null) {
+            sInstance = new ImsResolver(context, defaultMmTelPackageName, defaultRcsPackageName,
+                    numSlots, repo);
+        }
+    }
+
+    /**
+     * @return The ImsResolver Service instance. May be {@code null} if no ImsResolver was created
+     * due to IMS not being supported.
+     */
+    public static @Nullable ImsResolver getInstance() {
+        return sInstance;
+    }
+
     private static class OverrideConfig {
         public final int slotId;
         public final boolean isCarrierService;
@@ -830,7 +852,18 @@
             return false;
         }
         // Config exists, but the carrier ImsService also needs to support this feature
-        ImsServiceInfo info = getImsServiceInfoFromCache(carrierPackage);
+        return doesCachedImsServiceExist(carrierPackage, slotId, featureType);
+    }
+
+    /**
+     * Check the cached ImsServices that exist on this device to determine if there is a ImsService
+     * with the same package name that matches the provided configuration.
+     */
+    // not synchronized, access in handler ONLY.
+    private boolean doesCachedImsServiceExist(String packageName, int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        // Config exists, but the carrier ImsService also needs to support this feature
+        ImsServiceInfo info = getImsServiceInfoFromCache(packageName);
         return info != null && info.getSupportedFeatures().stream().anyMatch(
                 feature -> feature.slotId == slotId && feature.featureType == featureType);
     }
@@ -858,6 +891,94 @@
             return null;
         }
     }
+    /**
+     * Resolves the PackageName of the ImsService that is configured to be bound for the slotId and
+     * FeatureType specified and returns it.
+     * <p>
+     * If there is a PackageName that is configured, but there is no application on the device that
+     * fulfills that configuration, this method will also return {@code null} as the ImsService will
+     * not be bound.
+     *
+     * @param slotId The slot ID that the request is for.
+     * @param featureType The ImsService feature type that the request is for.
+     * @return The package name of the ImsService that will be bound from telephony for the provided
+     * slot id and featureType.
+     */
+    public String getConfiguredImsServicePackageName(int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        if (slotId < 0 || slotId >= mNumSlots || featureType <= ImsFeature.FEATURE_INVALID
+                || featureType >= ImsFeature.FEATURE_MAX) {
+            Log.w(TAG, "getResolvedImsServicePackageName received invalid parameters - slot: "
+                    + slotId + ", feature: " + featureType);
+            return null;
+        }
+        CompletableFuture<String> packageNameFuture = new CompletableFuture<>();
+        if (mHandler.getLooper().isCurrentThread()) {
+            // If we are on the same thread as the Handler's looper, run the internal method
+            // directly.
+            packageNameFuture.complete(getConfiguredImsServicePackageNameInternal(slotId,
+                    featureType));
+        } else {
+            mEventLog.log("getResolvedImsServicePackageName - [" + slotId + ", "
+                    + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "], starting query...");
+            Log.d(TAG, "getResolvedImsServicePackageName: [" + slotId + ", "
+                    + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "], starting query...");
+            mHandler.post(() -> {
+                try {
+                    packageNameFuture.complete(getConfiguredImsServicePackageNameInternal(slotId,
+                            featureType));
+                } catch (Exception e) {
+                    // Catch all Exceptions to ensure we do not block indefinitely in the case of an
+                    // unexpected error.
+                    packageNameFuture.completeExceptionally(e);
+                }
+            });
+        }
+        try {
+            String packageName = packageNameFuture.get();
+            mEventLog.log("getResolvedImsServicePackageName - [" + slotId + ", "
+                    + ImsFeature.FEATURE_LOG_MAP.get(featureType)
+                    + "], async query complete with package name: " + packageName);
+            Log.d(TAG, "getResolvedImsServicePackageName: [" + slotId + ", "
+                    + ImsFeature.FEATURE_LOG_MAP.get(featureType)
+                    + "], async query complete with package name: " + packageName);
+            return packageName;
+        } catch (Exception e) {
+            mEventLog.log("getResolvedImsServicePackageName - [" + slotId + ", "
+                    + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "] -> Exception: " + e);
+            Log.w(TAG, "getResolvedImsServicePackageName: [" + slotId + ", "
+                    + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "] returned Exception: " + e);
+            return null;
+        }
+    }
+
+    /**
+     * @return the package name for the configured carrier ImsService if it exists on the device and
+     * supports the supplied slotId and featureType. If no such configuration exists, fall back to
+     * the device ImsService. If neither exist, then return {@code null};
+     */
+    // Not synchronized, access on Handler ONLY!
+    private String getConfiguredImsServicePackageNameInternal(int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        // If a carrier ImsService is configured to be used for the provided slotId and
+        // featureType, then return that one.
+        String carrierPackage = getCarrierConfiguredPackageName(slotId, featureType);
+        if (!TextUtils.isEmpty(carrierPackage)
+                && doesCachedImsServiceExist(carrierPackage, slotId, featureType)) {
+            return carrierPackage;
+        }
+        // If there is no carrier ImsService configured for that configuration, then
+        // return the device's default ImsService for the provided slotId and
+        // featureType.
+        String devicePackage = getDeviceConfiguration(featureType);
+        if (!TextUtils.isEmpty(devicePackage)
+                && doesCachedImsServiceExist(devicePackage, slotId, featureType)) {
+            return devicePackage;
+        }
+        // There is no ImsService configuration that exists for the slotId and
+        // featureType.
+        return null;
+    }
 
     private void putImsController(int slotId, int feature, ImsServiceController controller) {
         if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.FEATURE_INVALID
diff --git a/src/java/com/android/internal/telephony/uicc/AdnCapacity.aidl b/src/java/com/android/internal/telephony/uicc/AdnCapacity.aidl
new file mode 100644
index 0000000..61ae736
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/AdnCapacity.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 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.internal.telephony.uicc;
+
+parcelable AdnCapacity;
diff --git a/src/java/com/android/internal/telephony/uicc/AdnCapacity.java b/src/java/com/android/internal/telephony/uicc/AdnCapacity.java
new file mode 100644
index 0000000..300759a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/AdnCapacity.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2021 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.internal.telephony.uicc;
+
+import android.hardware.radio.V1_6.PhonebookCapacity;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Used to present ADN capacity
+ *
+ * {@hide}
+ */
+public class AdnCapacity implements Parcelable {
+
+    private int mMaxAdnCount;
+    private int mUsedAdnCount;
+    private int mMaxEmailCount;
+    private int mUsedEmailCount;
+    private int mMaxAnrCount;
+    private int mUsedAnrCount;
+    private int mMaxNameLength;
+    private int mMaxNumberLength;
+    private int mMaxEmailLength;
+    private int mMaxAnrLength;
+
+    private int mHashCode = 0;
+
+    public AdnCapacity(int maxAdnCount, int usedAdnCount, int maxEmailCount,
+            int usedEmailCount, int maxAnrCount, int usedAnrCount, int maxNameLength,
+            int maxNumberLength, int maxEmailLength, int maxAnrLength) {
+        mMaxAdnCount = maxAdnCount;
+        mUsedAdnCount = usedAdnCount;
+        mMaxEmailCount = maxEmailCount;
+        mUsedEmailCount = usedEmailCount;
+        mMaxAnrCount = maxAnrCount;
+        mUsedAnrCount = usedAnrCount;
+        mMaxNameLength = maxNameLength;
+        mMaxNumberLength = maxNumberLength;
+        mMaxEmailLength = maxEmailLength;
+        mMaxAnrLength = maxAnrLength;
+    }
+
+    public AdnCapacity(PhonebookCapacity pbCap) {
+        if (pbCap != null) {
+            mMaxAdnCount = pbCap.maxAdnRecords;
+            mUsedAdnCount = pbCap.usedAdnRecords;
+            mMaxEmailCount = pbCap.maxEmailRecords;
+            mUsedEmailCount = pbCap.usedEmailRecords;
+            mMaxAnrCount = pbCap.maxAdditionalNumberRecords;
+            mUsedAnrCount = pbCap.usedAdditionalNumberRecords;
+            mMaxNameLength = pbCap.maxNameLen;
+            mMaxNumberLength = pbCap.maxNumberLen;
+            mMaxEmailLength = pbCap.maxEmailLen;
+            mMaxAnrLength = pbCap.maxAdditionalNumberLen;
+        }
+    }
+
+    public int getMaxAdnCount() {
+        return mMaxAdnCount;
+    }
+
+    public int getUsedAdnCount() {
+        return mUsedAdnCount;
+    }
+
+    public int getMaxEmailCount() {
+        return mMaxEmailCount;
+    }
+
+    public int getUsedEmailCount() {
+        return mUsedEmailCount;
+    }
+
+    public int getMaxAnrCount() {
+        return mMaxAnrCount;
+    }
+
+    public int getUsedAnrCount() {
+        return mUsedAnrCount;
+    }
+
+    public int getMaxNameLength() {
+        return mMaxNameLength;
+    }
+
+    public int getMaxNumberLength() {
+        return mMaxNumberLength;
+    }
+
+    public int getMaxEmailLength() {
+        return mMaxEmailLength;
+    }
+
+    public int getMaxAnrLength() {
+        return mMaxAnrLength;
+    }
+
+    public boolean isSimFull() {
+        return mMaxAdnCount == mUsedAdnCount;
+    }
+
+    public static final Parcelable.Creator<AdnCapacity> CREATOR
+            = new Parcelable.Creator<AdnCapacity>() {
+        @Override
+        public AdnCapacity createFromParcel(Parcel source) {
+            final int maxAdnCount = source.readInt();
+            final int usedAdnCount = source.readInt();
+            final int maxEmailCount = source.readInt();
+            final int usedEmailCount = source.readInt();
+            final int maxAnrCount = source.readInt();
+            final int usedAnrCount = source.readInt();
+            final int maxNameLength = source.readInt();
+            final int maxNumberLength = source.readInt();
+            final int maxEmailLength = source.readInt();
+            final int maxAnrLength = source.readInt();
+            return new AdnCapacity(maxAdnCount, usedAdnCount, maxEmailCount,
+                    usedEmailCount, maxAnrCount, usedAnrCount, maxNameLength,
+                    maxNumberLength, maxEmailLength, maxAnrLength);
+        }
+
+        @Override
+        public AdnCapacity[] newArray(int size) {
+            return new AdnCapacity[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMaxAdnCount);
+        dest.writeInt(mUsedAdnCount);
+        dest.writeInt(mMaxEmailCount);
+        dest.writeInt(mUsedEmailCount);
+        dest.writeInt(mMaxAnrCount);
+        dest.writeInt(mUsedAnrCount);
+        dest.writeInt(mMaxNameLength);
+        dest.writeInt(mMaxNumberLength);
+        dest.writeInt(mMaxEmailLength);
+        dest.writeInt(mMaxAnrLength);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof AdnCapacity) {
+            AdnCapacity capacity = (AdnCapacity)obj;
+            return capacity.getMaxAdnCount() == mMaxAdnCount
+                    && capacity.getUsedAdnCount() == mUsedAdnCount
+                    && capacity.getMaxEmailCount() == mMaxEmailCount
+                    && capacity.getUsedEmailCount() == mUsedEmailCount
+                    && capacity.getMaxAnrCount() == mMaxAnrCount
+                    && capacity.getUsedAnrCount() == mUsedAnrCount
+                    && capacity.getMaxNameLength() == mMaxNameLength
+                    && capacity.getMaxNumberLength() == mMaxNumberLength
+                    && capacity.getMaxEmailLength() == mMaxEmailLength
+                    && capacity.getMaxAnrLength() == mMaxAnrLength;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        if (mHashCode == 0) {
+            mHashCode = mMaxAdnCount;
+            mHashCode = 31 * mHashCode + mUsedAdnCount;
+            mHashCode = 31 * mHashCode + mMaxEmailCount;
+            mHashCode = 31 * mHashCode + mUsedEmailCount;
+            mHashCode = 31 * mHashCode + mMaxAnrCount;
+            mHashCode = 31 * mHashCode + mUsedAnrCount;
+            mHashCode = 31 * mHashCode + mMaxNameLength;
+            mHashCode = 31 * mHashCode + mMaxNumberLength;
+            mHashCode = 31 * mHashCode + mMaxEmailLength;
+            mHashCode = 31 * mHashCode + mMaxAnrLength;
+        }
+        return mHashCode;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecord.java b/src/java/com/android/internal/telephony/uicc/AdnRecord.java
index ce0fc79..0d23ce7 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnRecord.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnRecord.java
@@ -24,9 +24,11 @@
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.telephony.Rlog;
 
 import java.util.Arrays;
+import java.util.List;
 
 /**
  *
@@ -46,6 +48,7 @@
     String mNumber = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     String[] mEmails;
+    String[] mAdditionalNumbers = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     int mExtRecord = 0xff;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -88,14 +91,16 @@
             String alphaTag;
             String number;
             String[] emails;
+            String[] additionalNumbers;
 
             efid = source.readInt();
             recordNumber = source.readInt();
             alphaTag = source.readString();
             number = source.readString();
             emails = source.createStringArray();
+            additionalNumbers = source.createStringArray();
 
-            return new AdnRecord(efid, recordNumber, alphaTag, number, emails);
+            return new AdnRecord(efid, recordNumber, alphaTag, number, emails, additionalNumbers);
         }
 
         @Override
@@ -170,6 +175,10 @@
         this(0, 0, alphaTag, number, emails);
     }
 
+    public AdnRecord(String alphaTag, String number, String[] emails, String[] additionalNumbers) {
+        this(0, 0, alphaTag, number, emails, additionalNumbers);
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public AdnRecord (int efid, int recordNumber, String alphaTag, String number, String[] emails) {
         this.mEfid = efid;
@@ -177,6 +186,17 @@
         this.mAlphaTag = alphaTag;
         this.mNumber = number;
         this.mEmails = emails;
+        this.mAdditionalNumbers = null;
+    }
+
+    public AdnRecord(int efid, int recordNumber, String alphaTag, String number, String[] emails,
+            String[] additionalNumbers) {
+        this.mEfid = efid;
+        this.mRecordNumber = recordNumber;
+        this.mAlphaTag = alphaTag;
+        this.mNumber = number;
+        this.mEmails = emails;
+        this.mAdditionalNumbers = additionalNumbers;
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -186,6 +206,7 @@
         this.mAlphaTag = alphaTag;
         this.mNumber = number;
         this.mEmails = null;
+        this.mAdditionalNumbers = null;
     }
 
     //***** Instance Methods
@@ -202,6 +223,10 @@
         return mRecordNumber;
     }
 
+    public void setRecId(int recordId) {
+        mRecordNumber = recordId;
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public String getNumber() {
         return mNumber;
@@ -221,15 +246,25 @@
         this.mEmails = emails;
     }
 
+    public String[] getAdditionalNumbers() {
+        return mAdditionalNumbers;
+    }
+
+    public void setAdditionalNumbers(String[] additionalNumbers) {
+        mAdditionalNumbers = additionalNumbers;
+    }
+
     @Override
     public String toString() {
         return "ADN Record '" + mAlphaTag + "' '" + Rlog.pii(LOG_TAG, mNumber) + " "
-                + Rlog.pii(LOG_TAG, mEmails) + "'";
+                + Rlog.pii(LOG_TAG, mEmails) + " "
+                + Rlog.pii(LOG_TAG, mAdditionalNumbers) + "'";
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean isEmpty() {
-        return TextUtils.isEmpty(mAlphaTag) && TextUtils.isEmpty(mNumber) && mEmails == null;
+        return TextUtils.isEmpty(mAlphaTag) && TextUtils.isEmpty(mNumber)
+                && mEmails == null && mAdditionalNumbers == null;
     }
 
     public boolean hasExtendedRecord() {
@@ -250,10 +285,30 @@
         return (s1.equals(s2));
     }
 
+    /** Help function for ANR/EMAIL array compare. */
+    private static boolean arrayCompareNullEqualsEmpty(String s1[], String s2[]) {
+        if (s1 == s2) {
+            return true;
+        }
+
+        s1 = ArrayUtils.emptyIfNull(s1, String.class);
+        s2 = ArrayUtils.emptyIfNull(s2, String.class);
+
+        List<String> src = Arrays.asList(s1);
+        List<String> dest = Arrays.asList(s2);
+
+        if (src.size() != dest.size()) {
+            return false;
+        }
+
+        return src.containsAll(dest);
+    }
+
     public boolean isEqual(AdnRecord adn) {
         return ( stringCompareNullEqualsEmpty(mAlphaTag, adn.mAlphaTag) &&
                 stringCompareNullEqualsEmpty(mNumber, adn.mNumber) &&
-                Arrays.equals(mEmails, adn.mEmails));
+                arrayCompareNullEqualsEmpty(mEmails, adn.mEmails) &&
+                arrayCompareNullEqualsEmpty(mAdditionalNumbers, adn.mAdditionalNumbers));
     }
     //***** Parcelable Implementation
 
@@ -269,6 +324,7 @@
         dest.writeString(mAlphaTag);
         dest.writeString(mNumber);
         dest.writeStringArray(mEmails);
+        dest.writeStringArray(mAdditionalNumbers);
     }
 
     /**
@@ -292,10 +348,10 @@
             adnString[i] = (byte) 0xFF;
         }
 
-        if (TextUtils.isEmpty(mNumber)) {
+        if (TextUtils.isEmpty(mNumber) && TextUtils.isEmpty(mAlphaTag)) {
             Rlog.w(LOG_TAG, "[buildAdnString] Empty dialing number");
             return adnString;   // return the empty record (for delete)
-        } else if (mNumber.length()
+        } else if (mNumber != null && mNumber.length()
                 > (ADN_DIALING_NUMBER_END - ADN_DIALING_NUMBER_START + 1) * 2) {
             Rlog.w(LOG_TAG,
                     "[buildAdnString] Max length of dialing number is 20");
@@ -308,14 +364,16 @@
             Rlog.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset);
             return null;
         } else {
-            bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
-                    mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
+            if (!TextUtils.isEmpty(mNumber)) {
+                bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+                        mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
-            System.arraycopy(bcdNumber, 0, adnString,
-                    footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
+                System.arraycopy(bcdNumber, 0, adnString,
+                        footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
 
-            adnString[footerOffset + ADN_BCD_NUMBER_LENGTH]
-                    = (byte) (bcdNumber.length);
+                adnString[footerOffset + ADN_BCD_NUMBER_LENGTH]
+                        = (byte) (bcdNumber.length);
+            }
             adnString[footerOffset + ADN_CAPABILITY_ID]
                     = (byte) 0xFF; // Capability Id
             adnString[footerOffset + ADN_EXTENSION_ID]
@@ -400,12 +458,13 @@
             mExtRecord = 0xff & record[record.length - 1];
 
             mEmails = null;
-
+            mAdditionalNumbers = null;
         } catch (RuntimeException ex) {
             Rlog.w(LOG_TAG, "Error parsing AdnRecord", ex);
             mNumber = "";
             mAlphaTag = "";
             mEmails = null;
+            mAdditionalNumbers = null;
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/ReceivedPhonebookRecords.java b/src/java/com/android/internal/telephony/uicc/ReceivedPhonebookRecords.java
new file mode 100644
index 0000000..16fb55b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/ReceivedPhonebookRecords.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 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.internal.telephony.uicc;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Represents the received ADN entries from the SIM.
+ *
+ * {@hide}
+ */
+public class ReceivedPhonebookRecords {
+    @PhonebookReceivedState
+    private int mPhonebookReceivedState;
+    private List<SimPhonebookRecord> mEntries;
+
+    @IntDef(value = {
+        RS_OK,
+        RS_ERROR,
+        RS_ABORT,
+        RS_FINAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PhonebookReceivedState {}
+
+    public static final int RS_OK = 1;
+    public static final int RS_ERROR = 2;
+    public static final int RS_ABORT = 3;
+    public static final int RS_FINAL = 4;
+
+    public ReceivedPhonebookRecords(@PhonebookReceivedState int state,
+            List<SimPhonebookRecord> entries) {
+        mPhonebookReceivedState = state;
+        mEntries = entries;
+    }
+
+    public boolean isCompleted() {
+        return mPhonebookReceivedState == RS_FINAL;
+    }
+
+    public boolean isRetryNeeded() {
+        return mPhonebookReceivedState == RS_ABORT;
+    }
+
+    public boolean isOk() {
+        return mPhonebookReceivedState == RS_OK;
+    }
+    public List<SimPhonebookRecord> getPhonebookRecords() {
+        return mEntries;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/uicc/SimPhonebookRecord.java b/src/java/com/android/internal/telephony/uicc/SimPhonebookRecord.java
new file mode 100644
index 0000000..c6c7d6d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/SimPhonebookRecord.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2021 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.internal.telephony.uicc;
+
+import android.hardware.radio.V1_6.PhonebookRecordInfo;
+import android.text.TextUtils;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a Phonebook entry from the SIM.
+ *
+ * {@hide}
+ */
+public class SimPhonebookRecord {
+    // Instance variables
+    private int mRecordIndex = 0;
+    private String mAlphaTag;
+    private String mNumber;
+    private String[] mEmails;
+    private String[] mAdditionalNumbers;
+
+    // Instance methods
+    public SimPhonebookRecord (int recordIndex, String alphaTag, String number,
+               String[] emails, String[] adNumbers) {
+        mRecordIndex = recordIndex;
+        mAlphaTag = alphaTag;
+        mNumber = convertRecordFormatToNumber(number);
+        mEmails = emails;
+        if (adNumbers != null) {
+            mAdditionalNumbers = new String[adNumbers.length];
+            for (int i = 0 ; i < adNumbers.length; i++) {
+                mAdditionalNumbers[i] =
+                        convertRecordFormatToNumber(adNumbers[i]);
+            }
+        }
+    }
+
+    public SimPhonebookRecord(PhonebookRecordInfo recInfo) {
+        mRecordIndex = recInfo.recordId;
+        mAlphaTag = recInfo.name;
+        mNumber = recInfo.number;
+        mEmails = recInfo.emails == null ? null
+                : recInfo.emails.toArray(new String[recInfo.emails.size()]);
+        mAdditionalNumbers = recInfo.additionalNumbers == null ? null
+                : recInfo.additionalNumbers.toArray(
+                        new String[recInfo.additionalNumbers.size()]);
+    }
+
+    public SimPhonebookRecord() {}
+
+    public PhonebookRecordInfo toPhonebookRecordInfo() {
+        PhonebookRecordInfo pbRecordInfo = new PhonebookRecordInfo();
+        pbRecordInfo.recordId = mRecordIndex;
+        pbRecordInfo.name = convertNullToEmptyString(mAlphaTag);
+        pbRecordInfo.number = convertNullToEmptyString(convertNumberToRecordFormat(mNumber));
+        if (mEmails != null) {
+            for (String email : mEmails) {
+                pbRecordInfo.emails.add(email);
+            }
+        }
+        if (mAdditionalNumbers != null) {
+            for (String addNum : mAdditionalNumbers) {
+                pbRecordInfo.additionalNumbers.add(convertNumberToRecordFormat(addNum));
+            }
+        }
+        return pbRecordInfo;
+    }
+    public int getRecordIndex() {
+        return mRecordIndex;
+    }
+
+    public String getAlphaTag() {
+        return mAlphaTag;
+    }
+
+    public String getNumber() {
+        return mNumber;
+    }
+
+    public String[] getEmails() {
+        return mEmails;
+    }
+
+    public String[] getAdditionalNumbers() {
+        return mAdditionalNumbers;
+    }
+
+    /**
+     * convert phone number in the SIM phonebook record format to GSM pause/wild/wait character
+     */
+    private static String convertRecordFormatToNumber(String input) {
+        return input == null ? null : input.replace( 'e', PhoneNumberUtils.WAIT )
+                .replace( 'T', PhoneNumberUtils.PAUSE )
+                .replace( '?', PhoneNumberUtils.WILD );
+    }
+
+    /**
+     * convert the GSM pause/wild/wait character to the phone number in the SIM pb record format
+     */
+    private static String convertNumberToRecordFormat(String input) {
+        return input == null ? null : input.replace(PhoneNumberUtils.WAIT, 'e')
+                .replace(PhoneNumberUtils.PAUSE, 'T')
+                .replace(PhoneNumberUtils.WILD, '?');
+    }
+
+    private static String convertNullToEmptyString(String string) {
+        return string != null ? string : "";
+    }
+
+    public boolean isEmpty() {
+        return TextUtils.isEmpty(mAlphaTag)
+                && TextUtils.isEmpty(mNumber)
+                && ArrayUtils.isEmpty(mEmails)
+                && ArrayUtils.isEmpty(mAdditionalNumbers);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("SimPhoneBookRecord{").append("index =")
+                .append(mRecordIndex).append(", name = ")
+                .append(mAlphaTag == null ? "null" : mAlphaTag)
+                .append(", number = ").append(mNumber == null ? "null" : mNumber)
+                .append(", email count = ").append(mEmails == null ? 0 : mEmails.length)
+                .append(", email = ").append(Arrays.toString(mEmails))
+                .append(", ad number count = ")
+                .append(mAdditionalNumbers == null ? 0 : mAdditionalNumbers.length)
+                .append(", ad number = ").append(Arrays.toString(mAdditionalNumbers))
+                .append("}");
+        return sb.toString();
+    }
+
+    public final static class Builder {
+        private int mRecordIndex = 0;
+        private String mAlphaTag = null;
+        private String mNumber = null;
+        private String[] mEmails;
+        private String[] mAdditionalNumbers;
+
+        public SimPhonebookRecord build() {
+            SimPhonebookRecord record = new SimPhonebookRecord();
+            record.mAlphaTag = mAlphaTag;
+            record.mRecordIndex = mRecordIndex;
+            record.mNumber = mNumber;
+            if (mEmails != null) {
+                record.mEmails = mEmails;
+            }
+            if (mAdditionalNumbers != null) {
+                record.mAdditionalNumbers = mAdditionalNumbers;
+            }
+            return record;
+        }
+
+        public Builder setRecordIndex(int index) {
+            mRecordIndex = index;
+            return this;
+        }
+
+        public Builder setAlphaTag(String tag) {
+            mAlphaTag = tag;
+            return this;
+        }
+
+        public Builder setNumber(String number) {
+            mNumber = number;
+            return this;
+        }
+
+        public Builder setEmails(String[] emails) {
+            mEmails = emails;
+            return this;
+        }
+
+        public Builder setAdditionalNumbers(String[] anrs) {
+            mAdditionalNumbers = anrs;
+            return this;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/uicc/SimPhonebookRecordCache.java b/src/java/com/android/internal/telephony/uicc/SimPhonebookRecordCache.java
new file mode 100644
index 0000000..ca00112
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/SimPhonebookRecordCache.java
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2021 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.internal.telephony.uicc;
+
+import android.annotation.RequiresFeature;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.RadioInterfaceCapabilityController;
+import com.android.internal.telephony.uicc.AdnCapacity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+/**
+ * Used to store SIM phonebook records.
+ * <p/>
+ * This will be {@link #INVALID} if either is the case:
+ * <ol>
+ *   <li>The device does not support
+ * {@link android.telephony.TelephonyManager#CAPABILITY_SIM_PHONEBOOK_IN_MODEM}.</li>
+ * </ol>
+ * {@hide}
+ */
+@RequiresFeature(
+        enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+        value = "TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM")
+public class SimPhonebookRecordCache extends Handler {
+    // Instance Variables
+    private static final String LOG_TAG = "SimPhonebookRecordCache";
+    private static final boolean DBG = true;
+    // Event Constants
+    private static final int EVENT_PHONEBOOK_CHANGED = 1;
+    private static final int EVENT_PHONEBOOK_RECORDS_RECEIVED = 2;
+    private static final int EVENT_GET_PHONEBOOK_RECORDS_DONE = 3;
+    private static final int EVENT_GET_PHONEBOOK_CAPACITY_DONE = 4;
+    private static final int EVENT_UPDATE_PHONEBOOK_RECORD_DONE = 5;
+    private static final int EVENT_SIM_REFRESH = 6;
+    private static final int EVENT_GET_PHONEBOOK_RECORDS_RETRY = 7;
+
+    private static final int MAX_RETRY_COUNT = 3;
+    private static final int RETRY_INTERVAL = 3000; // 3S
+
+    // member variables
+    private final CommandsInterface mCi;
+    private int mPhoneId;
+    private Context mContext;
+
+    // Presenting ADN capacity, including ADN, EMAIL ANR, and so on.
+    private AtomicReference<AdnCapacity> mAdnCapacity = new AtomicReference<AdnCapacity>(null);
+    private Object mReadLock = new Object();
+    private List<AdnRecord> mSimPbRecords =
+            Collections.synchronizedList(new ArrayList<AdnRecord>());
+    private List<UpdateRequest> mUpdateRequests =
+            Collections.synchronizedList(new ArrayList<UpdateRequest>());
+    // If true, clear the records in the cache and re-query from modem
+    private AtomicBoolean mIsCacheInvalidated = new AtomicBoolean(false);
+    private AtomicBoolean mIsRecordLoading = new AtomicBoolean(false);
+    private AtomicBoolean mIsInRetry = new AtomicBoolean(false);
+    private AtomicBoolean mIsInitialized = new AtomicBoolean(false);
+
+    // People waiting for SIM phonebook records to be loaded
+    ArrayList<Message> mAdnLoadingWaiters = new ArrayList<Message>();
+    /**
+     * The manual update from upper layer will result in notifying SIM phonebook changed,
+     * leading to fetch the Adn capacity, then whether to need to reload phonebook records
+     * is a problem. the SIM phoneback changed shall follow by updating record done, so that
+     * uses this flag to avoid unnecessary loading.
+     */
+    boolean mIsUpdateDone = false;
+
+    public SimPhonebookRecordCache(Context context, int phoneId, CommandsInterface ci) {
+        mCi = ci;
+        mPhoneId = phoneId;
+        mContext = context;
+        mCi.registerForSimPhonebookChanged(this, EVENT_PHONEBOOK_CHANGED, null);
+        mCi.registerForIccRefresh(this, EVENT_SIM_REFRESH, null);
+        mCi.registerForSimPhonebookRecordsReceived(this, EVENT_PHONEBOOK_RECORDS_RECEIVED, null);
+    }
+
+    /**
+     * This is recommended to use in work thread like IccPhoneBookInterfaceManager
+     * because it can't block main thread.
+     * @return true if this feature is supported
+     */
+    public boolean isEnabled() {
+        boolean isEnabled = RadioInterfaceCapabilityController
+                .getInstance()
+                .getCapabilities()
+                .contains(TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM);
+        return mIsInitialized.get() || isEnabled;
+    }
+
+    public void dispose() {
+        reset();
+        mCi.unregisterForSimPhonebookChanged(this);
+        mCi.unregisterForIccRefresh(this);
+        mCi.unregisterForSimPhonebookRecordsReceived(this);
+    }
+
+    private void reset() {
+        mAdnCapacity.set(null);
+        mSimPbRecords.clear();
+        mIsCacheInvalidated.set(false);
+        mIsRecordLoading.set(false);
+        mIsInRetry.set(false);
+        mIsInitialized.set(false);
+        mIsUpdateDone = false;
+    }
+
+    private void sendErrorResponse(Message response, String errString) {
+        if (response != null) {
+            Exception e = new RuntimeException(errString);
+            AsyncResult.forMessage(response).exception = e;
+            response.sendToTarget();
+        }
+    }
+
+    private void notifyAndClearWaiters() {
+        synchronized (mReadLock) {
+            for (Message response : mAdnLoadingWaiters){
+                if (response != null) {
+                    AsyncResult.forMessage(response).result = mSimPbRecords;
+                    response.sendToTarget();
+                }
+            }
+            mAdnLoadingWaiters.clear();
+        }
+    }
+
+    private void sendResponsesToWaitersWithError() {
+        synchronized (mReadLock) {
+            mReadLock.notify();
+
+            for (Message response : mAdnLoadingWaiters) {
+                sendErrorResponse(response, "Query adn record failed");
+            }
+            mAdnLoadingWaiters.clear();
+        }
+    }
+
+    private void getSimPhonebookCapacity() {
+        logd("Start to getSimPhonebookCapacity");
+        mCi.getSimPhonebookCapacity(obtainMessage(EVENT_GET_PHONEBOOK_CAPACITY_DONE));
+    }
+
+    public AdnCapacity getAdnCapacity() {
+        return mAdnCapacity.get();
+    }
+
+    private void fillCache() {
+        synchronized (mReadLock) {
+            fillCacheWithoutWaiting();
+            try {
+                mReadLock.wait();
+            } catch (InterruptedException e) {
+                loge("Interrupted Exception in queryAdnRecord");
+            }
+        }
+    }
+
+    private void fillCacheWithoutWaiting() {
+        logd("Start to queryAdnRecord");
+        if (mIsRecordLoading.compareAndSet(false, true)) {
+            mCi.getSimPhonebookRecords(obtainMessage(EVENT_GET_PHONEBOOK_RECORDS_DONE));
+        } else {
+            logd("The loading is ongoing");
+        }
+    }
+
+    public void requestLoadAllPbRecords(Message response) {
+        if (response == null && !mIsInitialized.get()) {
+            logd("Try to enforce flushing cache");
+            fillCacheWithoutWaiting();
+            return;
+        }
+
+        synchronized (mReadLock) {
+            mAdnLoadingWaiters.add(response);
+            final int pendingSize = mAdnLoadingWaiters.size();
+            boolean isCapacityInvalid = getAdnCapacity() == null;
+            if (isCapacityInvalid) {
+                getSimPhonebookCapacity();
+            }
+            if (pendingSize > 1 || mIsInRetry.get()
+                    || !mIsInitialized.get() || isCapacityInvalid) {
+                logd("Add to the pending list as pending size = "
+                        + pendingSize + " is retrying = " + mIsInRetry.get()
+                        + " IsInitialized = " + mIsInitialized.get());
+                return;
+            }
+        }
+        if (!mIsRecordLoading.get() && !mIsInRetry.get()) {
+            logd("ADN cache has already filled in");
+            if (!mIsCacheInvalidated.get()) {
+                notifyAndClearWaiters();
+                return;
+            }
+        }
+        fillCache();
+    }
+
+    @VisibleForTesting
+    public boolean isLoading() {
+        return mIsRecordLoading.get();
+    }
+
+    @VisibleForTesting
+    public List<AdnRecord> getAdnRecords() {
+        return mSimPbRecords;
+    }
+
+    private void notifyAdnLoadingWaiters() {
+        synchronized (mReadLock) {
+            mReadLock.notify();
+        }
+        notifyAndClearWaiters();
+    }
+
+    public void updateSimPbAdnByRecordId(int recordId, AdnRecord newAdn, Message response) {
+        if (newAdn == null) {
+            sendErrorResponse(response, "There is an invalid new Adn for update");
+            return;
+        }
+        boolean found = false;
+        int index = 0;
+        for (Iterator<AdnRecord> it = mSimPbRecords.iterator(); it.hasNext();) {
+            AdnRecord oldAdn = it.next();
+            ++index;
+            if (oldAdn.getRecId() == recordId) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            sendErrorResponse(response, "There is an invalid old Adn for update");
+            return;
+        }
+        updateSimPhonebookByNewAdn(index, newAdn, response);
+    }
+
+    public void updateSimPbAdnBySearch(AdnRecord oldAdn, AdnRecord newAdn, Message response) {
+        int index = -1;
+        if ((oldAdn == null || oldAdn.isEmpty()) && !newAdn.isEmpty()) {
+            // Add contact
+            index = 0;
+        } else {
+            int count = 1;
+            // Delete or update contact
+            for (Iterator<AdnRecord> it = mSimPbRecords.iterator(); it.hasNext();) {
+                if (oldAdn.isEqual(it.next())) {
+                    index = count;
+                    break;
+                }
+                count++;
+            }
+        }
+        if (index == -1) {
+            sendErrorResponse(response, "SIM Phonebook record don't exist for " + oldAdn);
+            return;
+        }
+
+        if (newAdn == null) {
+            sendErrorResponse(response, "There is an invalid new Adn for update");
+            return;
+        }
+
+        if (index == 0 && mAdnCapacity.get() != null && mAdnCapacity.get().isSimFull()) {
+            sendErrorResponse(response, "SIM Phonebook record is full");
+            return;
+        }
+
+        updateSimPhonebookByNewAdn(index, newAdn, response);
+    }
+
+    private void updateSimPhonebookByNewAdn(int index, AdnRecord newAdn, Message response) {
+        int recordIndex = (index == 0) ? newAdn.getRecId()
+                : mSimPbRecords.get(index - 1).getRecId();
+        SimPhonebookRecord updateAdn = new SimPhonebookRecord.Builder()
+                .setRecordIndex(recordIndex)
+                .setAlphaTag(newAdn.getAlphaTag())
+                .setNumber(newAdn.getNumber())
+                .setEmails(newAdn.getEmails())
+                .setAdditionalNumbers(newAdn.getAdditionalNumbers())
+                .build();
+        UpdateRequest updateRequest = new UpdateRequest(index, newAdn, updateAdn, response);
+        mUpdateRequests.add(updateRequest);
+        boolean isCapacityInvalid = getAdnCapacity() == null;
+        if (isCapacityInvalid) {
+            getSimPhonebookCapacity();
+        }
+        if (mIsRecordLoading.get() || mIsInRetry.get() || mUpdateRequests.size() > 1
+                || !mIsInitialized.get() || isCapacityInvalid) {
+            logd("It is pending on update as " + " mIsRecordLoading = " + mIsRecordLoading.get()
+                    + " mIsInRetry = " + mIsInRetry.get() + " pending size = "
+                    + mUpdateRequests.size() + " mIsInitialized = " + mIsInitialized.get());
+            return;
+        }
+
+        updateSimPhonebook(updateRequest);
+    }
+
+    private void updateSimPhonebook(UpdateRequest request) {
+        logd("update Sim phonebook");
+        mCi.updateSimPhonebookRecord(request.phonebookRecord,
+                obtainMessage(EVENT_UPDATE_PHONEBOOK_RECORD_DONE, request));
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        AsyncResult ar;
+        switch(msg.what) {
+            case EVENT_PHONEBOOK_CHANGED:
+                logd("EVENT_PHONEBOOK_CHANGED");
+                handlePhonebookChanged();
+                break;
+            case EVENT_GET_PHONEBOOK_RECORDS_DONE:
+                logd("EVENT_GET_PHONEBOOK_RECORDS_DONE");
+                ar = (AsyncResult)msg.obj;
+                if (ar != null && ar.exception != null) {
+                    loge("Failed to gain phonebook records");
+                    invalidateSimPbCache();
+                    if (!mIsInRetry.get()) {
+                        sendGettingPhonebookRecordsRetry(0);
+                    }
+                }
+                break;
+            case EVENT_GET_PHONEBOOK_CAPACITY_DONE:
+                logd("EVENT_GET_PHONEBOOK_CAPACITY_DONE");
+                ar = (AsyncResult)msg.obj;
+                if (ar != null && ar.exception == null) {
+                    AdnCapacity capacity = (AdnCapacity)ar.result;
+                    handlePhonebookCapacityChanged(capacity);
+                }
+                break;
+            case EVENT_PHONEBOOK_RECORDS_RECEIVED:
+                logd("EVENT_PHONEBOOK_RECORDS_RECEIVED");
+                ar = (AsyncResult)msg.obj;
+                if (ar.exception != null) {
+                    loge("Unexpected exception happened");
+                    ar.result = null;
+                }
+
+                handlePhonebookRecordReceived((ReceivedPhonebookRecords)(ar.result));
+                break;
+            case EVENT_UPDATE_PHONEBOOK_RECORD_DONE:
+                logd("EVENT_UPDATE_PHONEBOOK_RECORD_DONE");
+                ar = (AsyncResult)msg.obj;
+                handleUpdatePhonebookRecordDone(ar);
+                break;
+            case EVENT_SIM_REFRESH:
+                logd("EVENT_SIM_REFRESH");
+                ar = (AsyncResult)msg.obj;
+                if (ar.exception == null) {
+                    handleSimRefresh((IccRefreshResponse)ar.result);
+                } else {
+                    logd("SIM refresh Exception: " + ar.exception);
+                }
+                break;
+            case EVENT_GET_PHONEBOOK_RECORDS_RETRY:
+                int retryCount = msg.arg1;
+                logd("EVENT_GET_PHONEBOOK_RECORDS_RETRY cnt = " + retryCount);
+                if (retryCount < MAX_RETRY_COUNT) {
+                    mIsRecordLoading.set(false);
+                    fillCacheWithoutWaiting();
+                    sendGettingPhonebookRecordsRetry(++retryCount);
+                } else {
+                    responseToWaitersWithErrorOrSuccess(false);
+                }
+                break;
+            default:
+                loge("Unexpected event: " + msg.what);
+        }
+
+    }
+
+    private void responseToWaitersWithErrorOrSuccess(boolean success) {
+        logd("responseToWaitersWithErrorOrSuccess success = " + success);
+        mIsRecordLoading.set(false);
+        mIsInRetry.set(false);
+        if (success) {
+            notifyAdnLoadingWaiters();
+        } else {
+            sendResponsesToWaitersWithError();
+
+        }
+        tryFireUpdatePendingList();
+    }
+
+    private void handlePhonebookChanged() {
+        if (mUpdateRequests.isEmpty()) {
+            // If this event is received, means this feature is supported.
+            getSimPhonebookCapacity();
+        } else {
+            logd("Do nothing in the midst of multiple update");
+        }
+    }
+
+    private void handlePhonebookCapacityChanged(AdnCapacity newCapacity) {
+        AdnCapacity oldCapacity = mAdnCapacity.get();
+        mAdnCapacity.set(newCapacity);
+        if (oldCapacity == null && newCapacity != null) {
+            if (newCapacity.getMaxAdnCount() > 0){
+                mSimPbRecords.clear();
+                fillCacheWithoutWaiting();
+            } else {
+                notifyAdnLoadingWaiters();
+            }
+            mIsInitialized.set(true); // Let's say the whole process is ready
+        } else {
+            // There is nothing from PB, so notify waiters directly if any
+            if (newCapacity != null && newCapacity.getMaxAdnCount() == 0) {
+                notifyAdnLoadingWaiters();
+            } else if (!mIsUpdateDone) {
+                invalidateSimPbCache();
+                fillCacheWithoutWaiting();
+            }
+            mIsUpdateDone = false;
+        }
+    }
+
+    private void handlePhonebookRecordReceived(ReceivedPhonebookRecords records) {
+        if (records != null) {
+            if (records.isOk()) {
+                logd("Partial data is received");
+                populateAdnRecords(records.getPhonebookRecords());
+            } else if (records.isCompleted()) {
+                logd("The whole loading process is finished");
+                populateAdnRecords(records.getPhonebookRecords());
+                mIsRecordLoading.set(false);
+                mIsInRetry.set(false);
+                notifyAdnLoadingWaiters();
+                tryFireUpdatePendingList();
+            } else if (records.isRetryNeeded() && !mIsInRetry.get()) {
+                logd("Start to retry as aborted");
+                sendGettingPhonebookRecordsRetry(0);
+            } else {
+                loge("Error happened");
+                // Let's keep the stale data, in example of SIM getting removed during loading,
+                // expects to finish the whole process.
+                responseToWaitersWithErrorOrSuccess(true);
+            }
+        } else {
+            loge("No records there");
+            responseToWaitersWithErrorOrSuccess(true);
+        }
+    }
+
+    private void handleUpdatePhonebookRecordDone(AsyncResult ar) {
+        Exception e = null;
+        UpdateRequest updateRequest = (UpdateRequest)ar.userObj;
+        mIsUpdateDone = true;
+        if (ar.exception == null) {
+            int index = updateRequest.index;
+            AdnRecord adn = updateRequest.adnRecord;
+            int recordIndex = ((int[]) (ar.result))[0];
+
+            if (index == 0) {
+                // add contact
+                addSimPbRecord(adn, recordIndex);
+            } else if (adn.isEmpty()){
+                // delete contact
+                AdnRecord deletedRecord = mSimPbRecords.get(index - 1);
+                int adnRecordIndex = deletedRecord.getRecId();
+                logd("Record number for deleted ADN is " + adnRecordIndex);
+                if(recordIndex == adnRecordIndex) {
+                    deleteSimPbRecord(index);
+                } else {
+                    e = new RuntimeException(
+                            "The index for deleted ADN record did not match");
+                }
+            } else {
+                // Change contact
+                if (mSimPbRecords.size() > index - 1) {
+                    AdnRecord oldRecord = mSimPbRecords.get(index - 1);
+                    int adnRecordIndex = oldRecord.getRecId();
+                    logd("Record number for changed ADN is " + adnRecordIndex);
+                    if(recordIndex == adnRecordIndex) {
+                        updateSimPbRecord(adn, recordIndex, index);
+                    } else {
+                        e = new RuntimeException(
+                                "The index for changed ADN record did not match");
+                    }
+                } else {
+                    e = new RuntimeException(
+                            "The index for changed ADN record is out of the border");
+                }
+            }
+        } else {
+            e = new RuntimeException("Update adn record failed", ar.exception);
+        }
+
+        if (mUpdateRequests.contains(updateRequest)) {
+            mUpdateRequests.remove(updateRequest);
+            updateRequest.responseResult(e);
+        } else {
+            loge("this update request isn't found");
+        }
+        tryFireUpdatePendingList();
+    }
+
+    private void tryFireUpdatePendingList() {
+        if (!mUpdateRequests.isEmpty()) {
+            updateSimPhonebook(mUpdateRequests.get(0));
+        }
+    }
+
+    private void handleSimRefresh(IccRefreshResponse iccRefreshResponse) {
+        if (iccRefreshResponse != null) {
+            if (iccRefreshResponse.refreshResult == IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE
+                    && (iccRefreshResponse.efId == IccConstants.EF_PBR ||
+                    iccRefreshResponse.efId == IccConstants.EF_ADN) ||
+                    iccRefreshResponse.refreshResult == IccRefreshResponse.REFRESH_RESULT_INIT) {
+                invalidateSimPbCache();
+                getSimPhonebookCapacity();
+            }
+        } else {
+            logd("IccRefreshResponse received is null");
+        }
+    }
+
+    private void populateAdnRecords(List<SimPhonebookRecord> records) {
+        if (records != null) {
+            List<AdnRecord> newRecords = records.stream().map(record -> {return
+                    new AdnRecord(0, // PBR or ADN
+                    record.getRecordIndex(),
+                    record.getAlphaTag(),
+                    record.getNumber(),
+                    record.getEmails(),
+                    record.getAdditionalNumbers());}).collect(Collectors.toList());
+            mSimPbRecords.addAll(newRecords);
+        }
+    }
+
+    private void sendGettingPhonebookRecordsRetry (int times) {
+        if (hasMessages(EVENT_GET_PHONEBOOK_RECORDS_RETRY)) {
+            removeMessages(EVENT_GET_PHONEBOOK_RECORDS_RETRY);
+        }
+        mIsInRetry.set(true);
+        Message message = obtainMessage(EVENT_GET_PHONEBOOK_RECORDS_RETRY, 1, 0);
+        sendMessageDelayed(message, RETRY_INTERVAL);
+    }
+
+    private void addSimPbRecord(AdnRecord addedRecord, int recordIndex) {
+        logd("Record number for the added ADN is " + recordIndex);
+        addedRecord.setRecId(recordIndex);
+        mSimPbRecords.add(addedRecord);
+    }
+
+
+    private void deleteSimPbRecord(int index) {
+        logd("Record number for the deleted ADN is " + index);
+        mSimPbRecords.remove(index - 1);
+    }
+
+    private void updateSimPbRecord(AdnRecord newRecord,
+            int recordIndex, int index) {
+        logd("Record number for the updated ADN is " + recordIndex);
+        newRecord.setRecId(recordIndex);
+        mSimPbRecords.set(index - 1, newRecord);
+    }
+
+    private void invalidateSimPbCache() {
+        logd("invalidateSimPbCache");
+        mIsCacheInvalidated.set(true);
+        mSimPbRecords.clear();
+    }
+
+    private void logd(String msg) {
+        if (DBG) {
+            Rlog.d(LOG_TAG, msg);
+        }
+    }
+
+    private void loge(String msg) {
+        if (DBG) {
+            Rlog.e(LOG_TAG, msg);
+        }
+    }
+
+    private final static class UpdateRequest {
+        private int index;
+        private Message response;
+        private AdnRecord adnRecord;
+        private SimPhonebookRecord phonebookRecord;
+
+        UpdateRequest(int index, AdnRecord record, SimPhonebookRecord phonebookRecord,
+                Message response) {
+            this.index = index;
+            this.adnRecord = record;
+            this.phonebookRecord = phonebookRecord;
+            this.response = response;
+        }
+
+        void responseResult(Exception e) {
+            if (response != null) {
+                AsyncResult.forMessage(response, null, e);
+                response.sendToTarget();
+            }
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RadioConfigResponseTest.java b/tests/telephonytests/src/com/android/internal/telephony/RadioConfigResponseTest.java
index a346ece..f8a3330 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RadioConfigResponseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RadioConfigResponseTest.java
@@ -48,7 +48,7 @@
         assertFalse(
                 caps.contains(TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE));
         assertFalse(
-                caps.contains(TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED));
+                caps.contains(TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK));
         assertFalse(
                 caps.contains(
                         TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE));
@@ -64,7 +64,7 @@
         assertFalse(
                 caps.contains(TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE));
         assertTrue(
-                caps.contains(TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED));
+                caps.contains(TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK));
         assertFalse(
                 caps.contains(
                         TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE));
@@ -80,7 +80,7 @@
         assertTrue(
                 caps.contains(TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE));
         assertTrue(
-                caps.contains(TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED));
+                caps.contains(TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK));
         assertTrue(
                 caps.contains(
                         TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
index 15e16fb..ea6f19d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
@@ -16,9 +16,12 @@
 
 package com.android.internal.telephony;
 
+import android.content.ContentValues;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.test.suitebuilder.annotation.Suppress;
 
+import com.android.internal.telephony.IccProvider;
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.IccConstants;
 
@@ -38,15 +41,17 @@
                                 .get());
         assertNotNull(simPhoneBook);
 
-        int size[] = simPhoneBook.getAdnRecordsSize(IccConstants.EF_ADN);
+        final int subId = SubscriptionManager.getDefaultSubscriptionId();
+        int size[] = simPhoneBook.getAdnRecordsSizeForSubscriber(subId, IccConstants.EF_ADN);
         assertNotNull(size);
         assertEquals(3, size.length);
         assertEquals(size[0] * size[2], size[1]);
         assertTrue(size[2] >= 100);
 
-        List<AdnRecord> adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+        List<AdnRecord> adnRecordList =
+                simPhoneBook.getAdnRecordsInEfForSubscriber(subId, IccConstants.EF_ADN);
         // do it twice cause the second time shall read from cache only
-        adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+        adnRecordList = simPhoneBook.getAdnRecordsInEfForSubscriber(subId, IccConstants.EF_ADN);
         assertNotNull(adnRecordList);
 
         // Test for phone book update
@@ -75,37 +80,66 @@
         String pin2 = null;
 
         // udpate by index
-        boolean success = simPhoneBook.updateAdnRecordsInEfByIndex(IccConstants.EF_ADN,
-                firstAdn.getAlphaTag(), firstAdn.getNumber(), adnIndex, pin2);
-        adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
-         AdnRecord tmpAdn = adnRecordList.get(listIndex);
+        ContentValues values = new ContentValues();
+        values.put(IccProvider.STR_NEW_TAG, firstAdn.getAlphaTag());
+        values.put(IccProvider.STR_NEW_NUMBER, firstAdn.getNumber());
+        values.put(IccProvider.STR_NEW_EMAILS, "");
+        values.put(IccProvider.STR_NEW_ANRS, "");
+        boolean success = simPhoneBook.updateAdnRecordsInEfByIndexForSubscriber(subId,
+                IccConstants.EF_ADN, values, adnIndex, pin2);
+        adnRecordList =
+                simPhoneBook.getAdnRecordsInEfForSubscriber(subId, IccConstants.EF_ADN);
+        AdnRecord tmpAdn = adnRecordList.get(listIndex);
         assertTrue(success);
         assertTrue(firstAdn.isEqual(tmpAdn));
 
         // replace by search
-        success = simPhoneBook.updateAdnRecordsInEfBySearch(IccConstants.EF_ADN,
-                firstAdn.getAlphaTag(), firstAdn.getNumber(),
-                secondAdn.getAlphaTag(), secondAdn.getNumber(), pin2);
-        adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+        ContentValues values2 = new ContentValues();
+        values.put(IccProvider.STR_TAG, firstAdn.getAlphaTag());
+        values.put(IccProvider.STR_NUMBER, firstAdn.getNumber());
+        values.put(IccProvider.STR_EMAILS, "");
+        values.put(IccProvider.STR_ANRS, "");
+        values.put(IccProvider.STR_NEW_TAG, secondAdn.getAlphaTag());
+        values.put(IccProvider.STR_NEW_NUMBER, secondAdn.getNumber());
+        values.put(IccProvider.STR_NEW_EMAILS, "");
+        values.put(IccProvider.STR_NEW_ANRS, "");
+        success = simPhoneBook.updateAdnRecordsInEfBySearchForSubscriber(subId,
+                IccConstants.EF_ADN, values2, pin2);
+        adnRecordList =
+                simPhoneBook.getAdnRecordsInEfForSubscriber(subId, IccConstants.EF_ADN);
         tmpAdn = adnRecordList.get(listIndex);
         assertTrue(success);
         assertFalse(firstAdn.isEqual(tmpAdn));
         assertTrue(secondAdn.isEqual(tmpAdn));
 
         // erase be search
-        success = simPhoneBook.updateAdnRecordsInEfBySearch(IccConstants.EF_ADN,
-                secondAdn.getAlphaTag(), secondAdn.getNumber(),
-                emptyAdn.getAlphaTag(), emptyAdn.getNumber(), pin2);
-        adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+        ContentValues values3 = new ContentValues();
+        values.put(IccProvider.STR_TAG, secondAdn.getAlphaTag());
+        values.put(IccProvider.STR_NUMBER, secondAdn.getNumber());
+        values.put(IccProvider.STR_EMAILS, "");
+        values.put(IccProvider.STR_ANRS, "");
+        values.put(IccProvider.STR_NEW_TAG, emptyAdn.getAlphaTag());
+        values.put(IccProvider.STR_NEW_NUMBER, emptyAdn.getNumber());
+        values.put(IccProvider.STR_NEW_EMAILS, "");
+        values.put(IccProvider.STR_NEW_ANRS, "");
+        success = simPhoneBook.updateAdnRecordsInEfBySearchForSubscriber(subId,
+                IccConstants.EF_ADN, values3, pin2);
+        adnRecordList =
+                simPhoneBook.getAdnRecordsInEfForSubscriber(subId, IccConstants.EF_ADN);
         tmpAdn = adnRecordList.get(listIndex);
         assertTrue(success);
         assertTrue(tmpAdn.isEmpty());
 
         // restore the orginial adn
-        success = simPhoneBook.updateAdnRecordsInEfByIndex(IccConstants.EF_ADN,
-                originalAdn.getAlphaTag(), originalAdn.getNumber(), adnIndex,
-                pin2);
-        adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+        ContentValues values4 = new ContentValues();
+        values.put(IccProvider.STR_NEW_TAG, originalAdn.getAlphaTag());
+        values.put(IccProvider.STR_NEW_NUMBER, originalAdn.getNumber());
+        values.put(IccProvider.STR_NEW_EMAILS, "");
+        values.put(IccProvider.STR_NEW_ANRS, "");
+        success = simPhoneBook.updateAdnRecordsInEfByIndexForSubscriber(subId,
+                IccConstants.EF_ADN, values4, adnIndex, pin2);
+        adnRecordList =
+                simPhoneBook.getAdnRecordsInEfForSubscriber(subId, IccConstants.EF_ADN);
         tmpAdn = adnRecordList.get(listIndex);
         assertTrue(success);
         assertTrue(originalAdn.isEqual(tmpAdn));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
index 949fca7..c545cdd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
@@ -68,6 +68,9 @@
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
+import com.android.internal.telephony.uicc.AdnCapacity;
+import com.android.internal.telephony.uicc.ReceivedPhonebookRecords;
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccIoResult;
@@ -2453,4 +2456,30 @@
         mVoiceRegStateResult = regStateResult;
     }
 
+    @Override
+    public void getSimPhonebookRecords(Message result) {
+        resultSuccess(result, null);
+
+        // send a fake result
+        List<SimPhonebookRecord> phonebookRecordInfoGroup = new ArrayList<SimPhonebookRecord>();
+        mSimPhonebookRecordsReceivedRegistrants.notifyRegistrants(
+                new AsyncResult(null,
+                new ReceivedPhonebookRecords(4, phonebookRecordInfoGroup), null));
+    }
+
+    @Override
+    public void getSimPhonebookCapacity(Message result) {
+        resultSuccess(result, new AdnCapacity(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
+    }
+
+    @Override
+    public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result) {
+        resultSuccess(result, new int[]{phonebookRecord.getRecordIndex()});
+        notifySimPhonebookChanged();
+    }
+
+    @VisibleForTesting
+    public void notifySimPhonebookChanged() {
+        mSimPhonebookChangedRegistrants.notifyRegistrants();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
index c4d54ff..e9c74ae 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
@@ -36,6 +36,7 @@
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
 
 public class SimulatedCommandsVerifier implements CommandsInterface {
     private static SimulatedCommandsVerifier sInstance;
@@ -1496,4 +1497,32 @@
     @Override
     public void getSlicingConfig(Message result) {
     }
+
+    @Override
+    public void getSimPhonebookRecords(Message result){
+    }
+
+    @Override
+    public void getSimPhonebookCapacity(Message result){
+    }
+
+    @Override
+    public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result){
+    }
+
+    @Override
+    public void registerForSimPhonebookChanged(Handler h, int what, Object obj){
+    }
+
+    @Override
+    public void unregisterForSimPhonebookChanged(Handler h){
+    }
+
+    @Override
+    public void registerForSimPhonebookRecordsReceived(Handler h, int what, Object obj){
+    }
+
+    @Override
+    public void unregisterForSimPhonebookRecordsReceived(Handler h){
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
index 2f355bb..4ba26d1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -194,6 +194,182 @@
     }
 
     /**
+     * Add a device ImsService and ensure that querying the configured ImsService for all features
+     * reports the device ImsService.
+     */
+    @Test
+    @SmallTest
+    public void testGetConfiguredImsServiceDevice() throws Exception {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        HashSet<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        setupPackageQuery(TEST_DEVICE_DEFAULT_NAME, features, true);
+        setupController();
+
+        // Complete package manager lookup and cache.
+        startBindCarrierConfigAlreadySet();
+
+        // device package name should be returned for both features.
+        final String[] packageName = new String[1];
+        // Calling this method will block us until the looper processes the command, so use
+        // runWithLooper to allow the message to be processed.
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_MMTEL));
+        assertEquals(TEST_DEVICE_DEFAULT_NAME.getPackageName(), packageName[0]);
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_RCS));
+        assertEquals(TEST_DEVICE_DEFAULT_NAME.getPackageName(), packageName[0]);
+    }
+
+    /**
+     * Add in the case that there is no device or carrier ImsService found, we return null for
+     * configuration queries.
+     */
+    @Test
+    @SmallTest
+    public void testGetConfiguredImsServiceNoDeviceOrCarrier() throws Exception {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        // package query returns null
+        setupController();
+        // Complete package manager lookup and cache.
+        startBindCarrierConfigAlreadySet();
+
+        // device package name should be returned for both features.
+        final String[] packageName = new String[1];
+        // Calling this method will block us until the looper processes the command, so use
+        // runWithLooper to allow the message to be processed.
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_MMTEL));
+        assertNull(packageName[0]);
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_RCS));
+        assertNull(packageName[0]);
+    }
+
+    /**
+     * Add in the case that there is no device or carrier ImsService configured, we return null for
+     * configuration queries.
+     */
+    @Test
+    @SmallTest
+    public void testGetConfiguredImsServiceNoDeviceConfig() throws Exception {
+        // device configuration for MMTEL and RCS is null
+        setupResolver(1 /*numSlots*/, null, null);
+        HashSet<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        // ImsService query does report a device ImsService
+        setupPackageQuery(TEST_DEVICE_DEFAULT_NAME, features, true);
+        setupController();
+        // Complete package manager lookup and cache.
+        startBindCarrierConfigAlreadySet();
+
+        // device package name should be returned for both features.
+        final String[] packageName = new String[1];
+        // Calling this method will block us until the looper processes the command, so use
+        // runWithLooper to allow the message to be processed.
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_MMTEL));
+        assertNull(packageName[0]);
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_RCS));
+        assertNull(packageName[0]);
+    }
+
+    /**
+     * Add a device and carrier ImsService and ensure that querying the configured ImsService for
+     * all features reports the carrier ImsService and not device.
+     */
+    @Test
+    @SmallTest
+    public void testGetConfiguredImsServiceCarrier() throws Exception {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        HashSet<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        setupController();
+
+        // Complete package manager lookup and cache.
+        startBindCarrierConfigAlreadySet();
+        // Setup the carrier features and response.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_MMTEL));
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_RCS));
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+        // carrier package name should be returned for both features.
+        final String[] packageName = new String[1];
+        // Calling this method will block us until the looper processes the command, so use
+        // runWithLooper to allow the message to be processed.
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_MMTEL));
+        assertEquals(TEST_CARRIER_DEFAULT_NAME.getPackageName(), packageName[0]);
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_RCS));
+        assertEquals(TEST_CARRIER_DEFAULT_NAME.getPackageName(), packageName[0]);
+    }
+
+    /**
+     * Add a device ImsService and ensure that querying the configured ImsService for all features
+     * reports the device ImsService though there is a configuration for carrier (but no cached
+     * ImsService).
+     */
+    @Test
+    @SmallTest
+    public void testGetConfiguredImsServiceCarrierDevice() throws Exception {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        HashSet<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Carrier service is configured
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        // Carrier ImsService is not found during package query.
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
+        setupPackageQuery(info);
+        setupController();
+
+        // Complete package manager lookup and cache.
+        startBindCarrierConfigAlreadySet();
+
+        final String[] packageName = new String[1];
+        // Calling this method will block us until the looper processes the command, so use
+        // runWithLooper to allow the message to be processed.
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_MMTEL));
+        assertEquals(TEST_DEVICE_DEFAULT_NAME.getPackageName(), packageName[0]);
+        mLooper.runWithLooper(() ->
+                packageName[0] = mTestImsResolver.getConfiguredImsServicePackageName(0,
+                        ImsFeature.FEATURE_RCS));
+        assertEquals(TEST_DEVICE_DEFAULT_NAME.getPackageName(), packageName[0]);
+    }
+
+    /**
      * Set the carrier config override value and ensure that ImsResolver calls .bind on that
      * package name with the correct ImsFeatures.
      */
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
old mode 100644
new mode 100755
index 72ff321..47ebc67
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
@@ -15,18 +15,25 @@
  */
 package com.android.internal.telephony.uicc;
 
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.AsyncResult;
-
-import com.android.internal.telephony.IccPhoneBookInterfaceManager;
-import com.android.internal.telephony.TelephonyTest;
-
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 
+import android.content.ContentValues;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.AsyncResult;
 import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+import com.android.internal.telephony.IccProvider;
+import com.android.internal.telephony.TelephonyTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -34,10 +41,6 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.anyInt;
-
 import java.util.Arrays;
 import java.util.List;
 
@@ -47,6 +50,8 @@
     private AdnRecordCache mAdnRecordCache;
     @Mock
     private AdnRecord mAdnRecord;
+    @Mock
+    private SimPhonebookRecordCache mSimPhonebookRecordCache;
     private IccPhoneBookInterfaceManager mIccPhoneBookInterfaceMgr;
     private IccPhoneBookInterfaceManagerHandler mIccPhoneBookInterfaceManagerHandler;
     private List<AdnRecord> mAdnList = Arrays.asList(mAdnRecord);
@@ -82,9 +87,19 @@
             }
         }).when(mAdnRecordCache).requestLoadAllAdnLike(anyInt(), anyInt(), (Message) anyObject());
 
+        doAnswer(invocation -> {
+            Message response = (Message) invocation.getArguments()[0];
+            //set result for load ADN EF
+            AsyncResult.forMessage(response).result = mAdnList;
+            response.sendToTarget();
+            return null;
+        }).when(mSimPhonebookRecordCache).requestLoadAllPbRecords((Message)anyObject());
         mIccPhoneBookInterfaceManagerHandler = new IccPhoneBookInterfaceManagerHandler(TAG);
         mIccPhoneBookInterfaceManagerHandler.start();
+
         waitUntilReady();
+        replaceInstance(IccPhoneBookInterfaceManager.class,
+                "mSimPbRecordCache", mIccPhoneBookInterfaceMgr, mSimPhonebookRecordCache);
     }
 
     @After
@@ -93,9 +108,11 @@
         mIccPhoneBookInterfaceManagerHandler.join();
         super.tearDown();
     }
+
     @Test
     @SmallTest
     public void testAdnEFLoadWithFailure() {
+        doReturn(false).when(mSimPhonebookRecordCache).isEnabled();
         List<AdnRecord> adnListResult = mIccPhoneBookInterfaceMgr.getAdnRecordsInEf(
                 IccConstants.EF_ADN);
         assertEquals(mAdnList, adnListResult);
@@ -116,4 +133,88 @@
         //verify the previous read is not got affected
         assertEquals(mAdnList, adnListResult);
     }
+
+    @Test
+    @SmallTest
+    public void testAdnEFLoadByPbCacheWithFailure() {
+        doReturn(true).when(mSimPhonebookRecordCache).isEnabled();
+        List<AdnRecord> adnListResult = mIccPhoneBookInterfaceMgr.getAdnRecordsInEf(
+                IccConstants.EF_ADN);
+        assertEquals(mAdnList, adnListResult);
+        //mock a ADN Ef load failure
+        doAnswer(invocation -> {
+            Message response = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(response).exception = new RuntimeException();
+            response.sendToTarget();
+            return null;
+        }).when(mSimPhonebookRecordCache).requestLoadAllPbRecords((Message) anyObject());
+        List<AdnRecord> adnListResultNew = mIccPhoneBookInterfaceMgr.getAdnRecordsInEf(
+                IccConstants.EF_ADN);
+        //the later read return null due to exception
+        assertNull(adnListResultNew);
+        //verify the previous read is not got affected
+        assertEquals(mAdnList, adnListResult);
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateAdnRecord() {
+        doReturn(false).when(mSimPhonebookRecordCache).isEnabled();
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message response = (Message) invocation.getArguments()[4];
+                //set result for update ADN EF
+                AsyncResult.forMessage(response).exception = null;
+                response.sendToTarget();
+                return null;
+            }
+        }).when(mAdnRecordCache).updateAdnBySearch(
+            anyInt(), any(), any(),
+            any(), (Message) anyObject());
+
+        ContentValues values = new ContentValues();
+        values.put(IccProvider.STR_TAG, "");
+        values.put(IccProvider.STR_NUMBER, "");
+        values.put(IccProvider.STR_EMAILS, "");
+        values.put(IccProvider.STR_ANRS, "");
+        values.put(IccProvider.STR_NEW_TAG, "test");
+        values.put(IccProvider.STR_NEW_NUMBER, "123456");
+        values.put(IccProvider.STR_NEW_EMAILS, "");
+        values.put(IccProvider.STR_NEW_ANRS, "");
+
+        boolean result = mIccPhoneBookInterfaceMgr.updateAdnRecordsInEfBySearchForSubscriber(
+                IccConstants.EF_ADN, values , null);
+
+        assertTrue(result);
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateAdnRecordByPbCache() {
+        doReturn(true).when(mSimPhonebookRecordCache).isEnabled();
+        doAnswer(invocation -> {
+            Message response = (Message) invocation.getArguments()[2];
+            //set result for update ADN EF
+            AsyncResult.forMessage(response).exception = null;
+            response.sendToTarget();
+            return null;
+        }).when(mSimPhonebookRecordCache).updateSimPbAdnBySearch(any(),
+            any(), (Message) anyObject());
+
+        ContentValues values = new ContentValues();
+        values.put(IccProvider.STR_TAG, "");
+        values.put(IccProvider.STR_NUMBER, "");
+        values.put(IccProvider.STR_EMAILS, "");
+        values.put(IccProvider.STR_ANRS, "");
+        values.put(IccProvider.STR_NEW_TAG, "test");
+        values.put(IccProvider.STR_NEW_NUMBER, "123456");
+        values.put(IccProvider.STR_NEW_EMAILS, "");
+        values.put(IccProvider.STR_NEW_ANRS, "");
+
+        boolean result = mIccPhoneBookInterfaceMgr.updateAdnRecordsInEfBySearchForSubscriber(
+                IccConstants.EF_ADN, values , "12");
+
+        assertTrue(result);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/SimPhonebookRecordCacheTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/SimPhonebookRecordCacheTest.java
new file mode 100644
index 0000000..24db77d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/SimPhonebookRecordCacheTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 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.internal.telephony.uicc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.AsyncResult;
+import android.os.HandlerThread;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SimPhonebookRecordCacheTest extends TelephonyTest {
+    private static final int EVENT_PHONEBOOK_RECORDS_RECEIVED = 2;
+
+    private SimPhonebookRecordCache mSimPhonebookRecordCacheUt;
+    private SimPhonebookRecordHandler mSimPhonebookRecordHandler;
+
+    private class SimPhonebookRecordHandler extends HandlerThread {
+        SimPhonebookRecordHandler(String name) {
+            super(name);
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            mSimPhonebookRecordCacheUt =
+                    new SimPhonebookRecordCache(mContext, 0, mSimulatedCommands);
+            setReady(true);
+        }
+
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(this.getClass().getSimpleName());
+        mSimPhonebookRecordHandler = new SimPhonebookRecordHandler(this.getClass().getSimpleName());
+        mSimPhonebookRecordHandler.start();
+        waitUntilReady();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSimPhonebookRecordHandler.quit();
+        mSimPhonebookRecordHandler.join();
+        super.tearDown();
+    }
+
+    @Test
+    public void testSimPhonebookChangedOnBootup() {
+        mSimulatedCommands.notifySimPhonebookChanged();
+        waitForLastHandlerAction(mSimPhonebookRecordCacheUt);
+        AdnCapacity capacity = mSimPhonebookRecordCacheUt.getAdnCapacity();
+        AdnCapacity capVerifer = new AdnCapacity(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+        assertNotNull(capacity);
+        assertTrue(capVerifer.equals(capacity));
+        mSimulatedCommands.notifySimPhonebookChanged();
+        waitForLastHandlerAction(mSimPhonebookRecordCacheUt);
+        assertTrue(capacity != mSimPhonebookRecordCacheUt.getAdnCapacity());
+        assertTrue(capVerifer.equals(capacity));
+    }
+
+    @Test
+    public void testGetPhonebookRecords() {
+        assertFalse(mSimPhonebookRecordCacheUt.isLoading());
+        mSimulatedCommands.notifySimPhonebookChanged();
+        waitForLastHandlerAction(mSimPhonebookRecordCacheUt);
+        assertFalse(mSimPhonebookRecordCacheUt.isLoading());
+        mSimPhonebookRecordCacheUt.requestLoadAllPbRecords(null);
+        waitForLastHandlerAction(mSimPhonebookRecordCacheUt);
+
+        List<SimPhonebookRecord> records = new ArrayList<SimPhonebookRecord>();
+        records.add(new SimPhonebookRecord(10, "ABC", "12345", null, null));
+        AsyncResult ar = new AsyncResult(null, new ReceivedPhonebookRecords(4, records), null);
+        Message msg = Message.obtain(mSimPhonebookRecordCacheUt,
+                EVENT_PHONEBOOK_RECORDS_RECEIVED, ar);
+        mSimPhonebookRecordCacheUt.handleMessage(msg);
+
+        assertFalse(mSimPhonebookRecordCacheUt.isLoading());
+        List<AdnRecord> adnRecords = mSimPhonebookRecordCacheUt.getAdnRecords();
+        assertEquals(adnRecords.size(), 1);
+        assertEquals(adnRecords.get(0).getRecId(), 10);
+    }
+
+    @Test
+    public void testGetPhonebookRecordsWithoutInitization() {
+        assertFalse(mSimPhonebookRecordCacheUt.isLoading());
+        mSimPhonebookRecordCacheUt.requestLoadAllPbRecords(null);
+        assertTrue(mSimPhonebookRecordCacheUt.isLoading());
+        waitForLastHandlerAction(mSimPhonebookRecordCacheUt);
+        assertFalse(mSimPhonebookRecordCacheUt.isLoading());
+    }
+
+    @Test
+    public void testUpdatePhonebookRecord() {
+        List<AdnRecord> adnRecords = mSimPhonebookRecordCacheUt.getAdnRecords();
+        assertEquals(adnRecords.size(), 0);
+
+        AdnRecord newAdn = new AdnRecord(0, 20, "AB", "123", null, null);
+        // add
+        mSimPhonebookRecordCacheUt.updateSimPbAdnBySearch(null, newAdn, null);
+        waitForLastHandlerAction(mSimPhonebookRecordCacheUt);
+        adnRecords = mSimPhonebookRecordCacheUt.getAdnRecords();
+        assertEquals(adnRecords.size(), 1);
+        AdnRecord oldAdn = adnRecords.get(0);
+        assertEquals(oldAdn.getRecId(), 20);
+        assertEquals(oldAdn.getAlphaTag(), "AB");
+        assertEquals(oldAdn.getNumber(), "123");
+        // update
+        newAdn = new AdnRecord(0, 20, "ABCD", "123456789", null, null);
+        mSimPhonebookRecordCacheUt.updateSimPbAdnBySearch(oldAdn, newAdn, null);
+        waitForLastHandlerAction(mSimPhonebookRecordCacheUt);
+        adnRecords = mSimPhonebookRecordCacheUt.getAdnRecords();
+        assertEquals(adnRecords.size(), 1);
+        oldAdn = adnRecords.get(0);
+        assertEquals(oldAdn.getRecId(), 20);
+        assertEquals(oldAdn.getAlphaTag(), "ABCD");
+        assertEquals(oldAdn.getNumber(), "123456789");
+        // Delete
+        newAdn = new AdnRecord(0, 20, null, null, null, null);
+        mSimPhonebookRecordCacheUt.updateSimPbAdnBySearch(oldAdn, newAdn, null);
+        waitForLastHandlerAction(mSimPhonebookRecordCacheUt);
+        adnRecords = mSimPhonebookRecordCacheUt.getAdnRecords();
+        assertEquals(adnRecords.size(), 0);
+    }
+}