Enforce map the telephony features with APIs in CarrierConfigLoader.

If the required telephony feature is not defined, throw UnsupportedOperationException.

Bug: 297989574
Test: atest CarrierConfigLoaderTest
Change-Id: Iaba1a26e03f77b31af0aebea1b6df056ec8dbf33
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 3eafb24..ef71016 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -16,12 +16,15 @@
 
 package com.android.phone;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION;
 import static android.service.carrier.CarrierService.ICarrierServiceWrapper.KEY_CONFIG_BUNDLE;
 import static android.service.carrier.CarrierService.ICarrierServiceWrapper.RESULT_ERROR;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -66,6 +69,7 @@
 import com.android.internal.telephony.PhoneConfigurationManager;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.telephony.util.TelephonyUtils;
@@ -685,12 +689,17 @@
 
     @NonNull private final Handler mHandler;
 
+    @NonNull private final FeatureFlags  mFeatureFlags;
+
+    @NonNull private final PackageManager mPackageManager;
+
     /**
      * Constructs a CarrierConfigLoader, registers it as a service, and registers a broadcast
      * receiver for relevant events.
      */
     @VisibleForTesting
-    /* package */ CarrierConfigLoader(@NonNull Context context, @NonNull Looper looper) {
+    /* package */ CarrierConfigLoader(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags) {
         super(PermissionEnforcer.fromContext(context));
         mContext = context;
         mPlatformCarrierConfigPackage =
@@ -719,6 +728,8 @@
             TelephonyManager.from(context).registerCarrierPrivilegesCallback(phoneId,
                     new HandlerExecutor(mHandler), mCarrierServiceChangeCallbacks[phoneId]);
         }
+        mFeatureFlags = featureFlags;
+        mPackageManager = context.getPackageManager();
         logd("CarrierConfigLoader has started");
 
         PhoneConfigurationManager.registerForMultiSimConfigChange(
@@ -733,10 +744,11 @@
      * This is only done once, at startup, from {@link com.android.phone.PhoneApp#onCreate}.
      */
     @NonNull
-    /* package */ static CarrierConfigLoader init(@NonNull Context context) {
+    /* package */ static CarrierConfigLoader init(@NonNull Context context,
+            @NonNull FeatureFlags featureFlags) {
         synchronized (CarrierConfigLoader.class) {
             if (sInstance == null) {
-                sInstance = new CarrierConfigLoader(context, Looper.myLooper());
+                sInstance = new CarrierConfigLoader(context, Looper.myLooper(), featureFlags);
                 // Make this service available through ServiceManager.
                 TelephonyFrameworkInitializer.getTelephonyServiceManager()
                         .getCarrierConfigServiceRegisterer().register(sInstance);
@@ -1323,6 +1335,8 @@
             return new PersistableBundle();
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getConfigForSubIdWithFeature");
+
         int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
         PersistableBundle retConfig = CarrierConfigManager.getDefaultConfig();
         if (SubscriptionManager.isValidPhoneId(phoneId)) {
@@ -1367,6 +1381,9 @@
         Objects.requireNonNull(keys, "Config keys must be non-null");
         enforceCallerIsSystemOrRequestingPackage(callingPackage);
 
+        enforceTelephonyFeatureWithException(callingPackage,
+                "getConfigSubsetForSubIdWithFeature");
+
         // Permission check is performed inside and an empty bundle will return on failure.
         // No SecurityException thrown here since most clients expect to retrieve the overridden
         // value if present or use default one if not
@@ -1416,6 +1433,9 @@
             throw new IllegalArgumentException(
                     "Invalid phoneId " + phoneId + " for subId " + subscriptionId);
         }
+
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "overrideConfig");
+
         // Post to run on handler thread on which all states should be confined.
         mHandler.post(() -> {
             overrideConfig(mOverrideConfigs, phoneId, overrides);
@@ -1468,6 +1488,9 @@
                     "Invalid phoneId " + phoneId + " for subId " + subscriptionId);
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "notifyConfigChangedForSubId");
+
         logdWithLocalLog("Notified carrier config changed. phoneId=" + phoneId
                 + ", subId=" + subscriptionId);
 
@@ -1488,6 +1511,9 @@
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             throw new IllegalArgumentException("Invalid phoneId: " + phoneId);
         }
+
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "updateConfigForPhoneId");
+
         // requires Java 7 for switch on string.
         switch (simState) {
             case IccCardConstants.INTENT_VALUE_ICC_ABSENT:
@@ -1509,6 +1535,10 @@
     @NonNull
     public String getDefaultCarrierServicePackageName() {
         getDefaultCarrierServicePackageName_enforcePermission();
+
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "getDefaultCarrierServicePackageName");
+
         return mPlatformCarrierConfigPackage;
     }
 
@@ -1819,6 +1849,40 @@
                 == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
     }
 
+    /**
+     * Get the current calling package name.
+     * @return the current calling package name
+     */
+    @Nullable
+    private String getCurrentPackageName() {
+        if (mPackageManager == null) return null;
+        String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+        return (callingUids == null) ? null : callingUids[0];
+    }
+
+    /**
+     * Make sure the device has required telephony feature
+     *
+     * @throws UnsupportedOperationException if the device does not have required telephony feature
+     */
+    private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+            @NonNull String methodName) {
+        if (callingPackage == null || mPackageManager == null) {
+            return;
+        }
+
+        if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+                || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+                Binder.getCallingUserHandle())) {
+            return;
+        }
+
+        if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY_SUBSCRIPTION)) {
+            throw new UnsupportedOperationException(
+                    methodName + " is unsupported without " + FEATURE_TELEPHONY_SUBSCRIPTION);
+        }
+    }
+
     private class CarrierServiceConnection implements ServiceConnection {
         final int phoneId;
         @NonNull final String pkgName;
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index f93b1e5..b19e276 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -578,7 +578,7 @@
 
             imsRcsController = ImsRcsController.init(this);
 
-            configLoader = CarrierConfigLoader.init(this);
+            configLoader = CarrierConfigLoader.init(this, mFeatureFlags);
 
             if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
                 mImsStateCallbackController =
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
index bd2e4f7..f4197d9 100644
--- a/tests/src/com/android/phone/CarrierConfigLoaderTest.java
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -30,6 +31,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
@@ -56,12 +58,17 @@
 
 import com.android.TelephonyTestBase;
 import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -76,10 +83,14 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class CarrierConfigLoaderTest extends TelephonyTestBase {
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
+    private static final String TAG = CarrierConfigLoaderTest.class.getSimpleName();
     private static final int DEFAULT_PHONE_ID = 0;
     private static final int DEFAULT_SUB_ID = SubscriptionManager.getDefaultSubscriptionId();
     private static final String PLATFORM_CARRIER_CONFIG_PACKAGE = "com.android.carrierconfig";
+    private static final String PLATFORM_CARRIER_CONFIG_FEATURE = "com.android.carrierconfig";
     private static final long PLATFORM_CARRIER_CONFIG_PACKAGE_VERSION_CODE = 1;
     private static final String CARRIER_CONFIG_EXAMPLE_KEY =
             CarrierConfigManager.KEY_CARRIER_USSD_METHOD_INT;
@@ -92,6 +103,7 @@
     @Mock SubscriptionManagerService mSubscriptionManagerService;
     @Mock SharedPreferences mSharedPreferences;
     @Mock TelephonyRegistryManager mTelephonyRegistryManager;
+    @Mock FeatureFlags mFeatureFlags;
 
     private TelephonyManager mTelephonyManager;
     private CarrierConfigLoader mCarrierConfigLoader;
@@ -118,6 +130,8 @@
         doReturn(Build.FINGERPRINT).when(mSharedPreferences).getString(eq("build_fingerprint"),
                 any());
         doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(new String[]{TAG}).when(mPackageManager).getPackagesForUid(anyInt());
+
         doReturn(mResources).when(mContext).getResources();
         doReturn(InstrumentationRegistry.getTargetContext().getFilesDir()).when(
                 mContext).getFilesDir();
@@ -141,7 +155,8 @@
         mHandlerThread.start();
 
         mTestableLooper = new TestableLooper(mHandlerThread.getLooper());
-        mCarrierConfigLoader = new CarrierConfigLoader(mContext, mTestableLooper.getLooper());
+        mCarrierConfigLoader = new CarrierConfigLoader(mContext, mTestableLooper.getLooper(),
+                mFeatureFlags);
         mHandler = mCarrierConfigLoader.getHandler();
 
         // Clear all configs to have the same starting point.
@@ -412,6 +427,34 @@
         assertThat(dumpContent).doesNotContain("Permission Denial:");
     }
 
+    @Test
+    @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
+    public void testGetConfigForSubIdWithFeature_withTelephonyFeatureMapping() {
+        doNothing().when(mContext).enforcePermission(
+                eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+                anyInt(), anyInt(), anyString());
+
+        doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(false).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+        // Not defined required feature, expect UnsupportedOperationException
+        assertThrows(UnsupportedOperationException.class,
+                () -> mCarrierConfigLoader.getConfigForSubIdWithFeature(DEFAULT_SUB_ID,
+                        PLATFORM_CARRIER_CONFIG_PACKAGE, PLATFORM_CARRIER_CONFIG_FEATURE));
+
+        doReturn(true).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+        // Defined required feature, not expect UnsupportedOperationException
+        try {
+            mCarrierConfigLoader.getConfigForSubIdWithFeature(DEFAULT_SUB_ID,
+                    PLATFORM_CARRIER_CONFIG_PACKAGE, PLATFORM_CARRIER_CONFIG_FEATURE);
+        } catch (UnsupportedOperationException e) {
+            fail("not expected UnsupportedOperationException");
+        }
+    }
+
     private static PersistableBundle getTestConfig() {
         PersistableBundle config = new PersistableBundle();
         config.putInt(CARRIER_CONFIG_EXAMPLE_KEY, CARRIER_CONFIG_EXAMPLE_VALUE);