CTS test ConnectivityDiagnostics via CarrierPrivilegesTracker.

This change adds a CTS test for ConnectivityDiagnostics using
CarrierPrivilegesTracker to receive permissions for
ConnectivityDiagnostics callbacks.

Specifically, the test provides CarrierPrivileges to the test package
(android.net.cts) for the active subscription via Carrier Configs
overrides. CarrierPrivilegesTracker listens to the Carrier Configs
change and matches the loaded certificate with the test package, then
includes the test package UID as an administrator for the Network in
ConnectivityService. Then, a ConnectivityDiagnosticsCallback is
registered and receives a ConnectivityReport on register for the active
subscription.

Bug: 148032944
Bug: 151332031
Test: atest ConnectivityDiagnosticsManagerTest
Change-Id: Iee8a428ada0b6e2127410267a13c3b688df83e6d
Merged-In: Iee8a428ada0b6e2127410267a13c3b688df83e6d
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index d17d8e5..1e64d83 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -16,6 +16,7 @@
 
 package android.net.cts;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
@@ -31,6 +32,7 @@
 import static android.net.ConnectivityDiagnosticsManager.persistableBundleEquals;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
 
@@ -41,9 +43,15 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityDiagnosticsManager;
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
@@ -58,10 +66,15 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.platform.test.annotations.AppModeFull;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.ArrayUtils;
 import com.android.testutils.ArrayTrackRecord;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
@@ -71,7 +84,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.security.MessageDigest;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @IgnoreUpTo(Build.VERSION_CODES.Q) // ConnectivityDiagnosticsManager did not exist in Q
@@ -85,6 +101,7 @@
     private static final int FAIL_RATE_PERCENTAGE = 100;
     private static final int UNKNOWN_DETECTION_METHOD = 4;
     private static final int FILTERED_UNKNOWN_DETECTION_METHOD = 0;
+    private static final int CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT = 5000;
 
     private static final Executor INLINE_EXECUTOR = x -> x.run();
 
@@ -95,15 +112,23 @@
                     .removeCapability(NET_CAPABILITY_NOT_VPN)
                     .build();
 
+    private static final String SHA_256 = "SHA-256";
+
     // Callback used to keep TestNetworks up when there are no other outstanding NetworkRequests
     // for it.
     private static final TestNetworkCallback TEST_NETWORK_CALLBACK = new TestNetworkCallback();
 
+    private static final NetworkRequest CELLULAR_NETWORK_REQUEST =
+            new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+
     private static final IBinder BINDER = new Binder();
 
     private Context mContext;
     private ConnectivityManager mConnectivityManager;
     private ConnectivityDiagnosticsManager mCdm;
+    private CarrierConfigManager mCarrierConfigManager;
+    private PackageManager mPackageManager;
+    private TelephonyManager mTelephonyManager;
     private Network mTestNetwork;
 
     @Before
@@ -111,6 +136,9 @@
         mContext = InstrumentationRegistry.getContext();
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class);
+        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+        mPackageManager = mContext.getPackageManager();
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
 
         mConnectivityManager.requestNetwork(TEST_NETWORK_REQUEST, TEST_NETWORK_CALLBACK);
     }
@@ -142,6 +170,98 @@
     }
 
     @Test
+    public void testRegisterCallbackWithCarrierPrivileges() throws Exception {
+        assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
+
+        final int subId = SubscriptionManager.getDefaultSubscriptionId();
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            fail("Need an active subscription. Please ensure that the device has working mobile"
+                    + " data.");
+        }
+
+        final CarrierConfigReceiver carrierConfigReceiver = new CarrierConfigReceiver(subId);
+        mContext.registerReceiver(
+                carrierConfigReceiver,
+                new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+
+        final TestNetworkCallback testNetworkCallback = new TestNetworkCallback();
+        final TestConnectivityDiagnosticsCallback connDiagsCallback =
+                new TestConnectivityDiagnosticsCallback();
+        try {
+            doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
+                    subId, carrierConfigReceiver, testNetworkCallback, connDiagsCallback);
+        } finally {
+            runWithShellPermissionIdentity(
+                    () -> mCarrierConfigManager.overrideConfig(subId, null),
+                    android.Manifest.permission.MODIFY_PHONE_STATE);
+            mConnectivityManager.unregisterNetworkCallback(testNetworkCallback);
+            mCdm.unregisterConnectivityDiagnosticsCallback(connDiagsCallback);
+            mContext.unregisterReceiver(carrierConfigReceiver);
+        }
+    }
+
+    private String getCertHashForThisPackage() throws Exception {
+        final PackageInfo pkgInfo =
+                mPackageManager.getPackageInfo(
+                        mContext.getOpPackageName(), PackageManager.GET_SIGNATURES);
+        final MessageDigest md = MessageDigest.getInstance(SHA_256);
+        final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray());
+        return IccUtils.bytesToHexString(certHash);
+    }
+
+    private void doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
+            int subId,
+            @NonNull CarrierConfigReceiver carrierConfigReceiver,
+            @NonNull TestNetworkCallback testNetworkCallback,
+            @NonNull TestConnectivityDiagnosticsCallback connDiagsCallback)
+            throws Exception {
+        final PersistableBundle carrierConfigs = new PersistableBundle();
+        carrierConfigs.putStringArray(
+                CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
+                new String[] {getCertHashForThisPackage()});
+
+        runWithShellPermissionIdentity(
+                () -> {
+                    mCarrierConfigManager.overrideConfig(subId, carrierConfigs);
+                    mCarrierConfigManager.notifyConfigChangedForSubId(subId);
+                },
+                android.Manifest.permission.MODIFY_PHONE_STATE);
+
+        // TODO(b/157779832): This should use android.permission.CHANGE_NETWORK_STATE. However, the
+        // shell does not have CHANGE_NETWORK_STATE, so use CONNECTIVITY_INTERNAL until the shell
+        // permissions are updated.
+        runWithShellPermissionIdentity(
+                () -> mConnectivityManager.requestNetwork(
+                        CELLULAR_NETWORK_REQUEST, testNetworkCallback),
+                android.Manifest.permission.CONNECTIVITY_INTERNAL);
+
+        final Network network = testNetworkCallback.waitForAvailable();
+        assertNotNull(network);
+
+        assertTrue("Didn't receive broadcast for ACTION_CARRIER_CONFIG_CHANGED for subId=" + subId,
+                carrierConfigReceiver.waitForCarrierConfigChanged());
+        assertTrue("Don't have Carrier Privileges after adding cert for this package",
+                mTelephonyManager.createForSubscriptionId(subId).hasCarrierPrivileges());
+
+        // Wait for CarrierPrivilegesTracker to receive the ACTION_CARRIER_CONFIG_CHANGED
+        // broadcast. CPT then needs to update the corresponding DataConnection, which then
+        // updates ConnectivityService. Unfortunately, this update to the NetworkCapabilities in
+        // CS does not trigger NetworkCallback#onCapabilitiesChanged as changing the
+        // administratorUids is not a publicly visible change. In lieu of a better signal to
+        // detministically wait for, use Thread#sleep here.
+        Thread.sleep(500);
+
+        mCdm.registerConnectivityDiagnosticsCallback(
+                CELLULAR_NETWORK_REQUEST, INLINE_EXECUTOR, connDiagsCallback);
+
+        final String interfaceName =
+                mConnectivityManager.getLinkProperties(network).getInterfaceName();
+        connDiagsCallback.expectOnConnectivityReportAvailable(
+                network, interfaceName, TRANSPORT_CELLULAR);
+        connDiagsCallback.assertNoCallback();
+    }
+
+    @Test
     public void testRegisterDuplicateConnectivityDiagnosticsCallback() {
         final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
         mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
@@ -326,13 +446,18 @@
 
         public void expectOnConnectivityReportAvailable(
                 @NonNull Network network, @NonNull String interfaceName) {
+            expectOnConnectivityReportAvailable(network, interfaceName, TRANSPORT_TEST);
+        }
+
+        public void expectOnConnectivityReportAvailable(
+                @NonNull Network network, @NonNull String interfaceName, int transportType) {
             final ConnectivityReport result =
                     (ConnectivityReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
             assertEquals(network, result.getNetwork());
 
             final NetworkCapabilities nc = result.getNetworkCapabilities();
             assertNotNull(nc);
-            assertTrue(nc.hasTransport(TRANSPORT_TEST));
+            assertTrue(nc.hasTransport(transportType));
             assertNotNull(result.getLinkProperties());
             assertEquals(interfaceName, result.getLinkProperties().getInterfaceName());
 
@@ -386,4 +511,43 @@
                     mHistory.poll(NO_CALLBACK_INVOKED_TIMEOUT, x -> true));
         }
     }
+
+    private class CarrierConfigReceiver extends BroadcastReceiver {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final int mSubId;
+
+        CarrierConfigReceiver(int subId) {
+            mSubId = subId;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+
+            final int subId =
+                    intent.getIntExtra(
+                            CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            if (mSubId != subId) return;
+
+            final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId);
+            if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) return;
+
+            final String[] certs =
+                    carrierConfigs.getStringArray(
+                            CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
+            try {
+                if (ArrayUtils.contains(certs, getCertHashForThisPackage())) {
+                    mLatch.countDown();
+                }
+            } catch (Exception e) {
+            }
+        }
+
+        boolean waitForCarrierConfigChanged() throws Exception {
+            return mLatch.await(CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+    }
 }