Merge "Use jarjar rule generator for connectivity rules"
diff --git a/bpf_progs/dscp_policy.c b/bpf_progs/dscp_policy.c
index 92ea0e2..e45c1d4 100644
--- a/bpf_progs/dscp_policy.c
+++ b/bpf_progs/dscp_policy.c
@@ -64,10 +64,10 @@
 
     int zero = 0;
     int hdr_size = 0;
-    uint64_t* selectedMap = bpf_switch_comp_map_lookup_elem(&zero);
+    uint64_t* selected_map = bpf_switch_comp_map_lookup_elem(&zero);
 
     // use this with HASH map so map lookup only happens once policies have been added?
-    if (!selectedMap) {
+    if (!selected_map) {
         return;
     }
 
@@ -78,8 +78,8 @@
     uint16_t sport = 0;
     uint16_t dport = 0;
     uint8_t protocol = 0;  // TODO: Use are reserved value? Or int (-1) and cast to uint below?
-    struct in6_addr srcIp = {};
-    struct in6_addr dstIp = {};
+    struct in6_addr src_ip = {};
+    struct in6_addr dst_ip = {};
     uint8_t tos = 0;       // Only used for IPv4
     uint8_t priority = 0;  // Only used for IPv6
     uint8_t flow_lbl = 0;  // Only used for IPv6
@@ -96,12 +96,12 @@
         if (iph->ihl != 5) return;
 
         // V4 mapped address in in6_addr sets 10/11 position to 0xff.
-        srcIp.s6_addr32[2] = htonl(0x0000ffff);
-        dstIp.s6_addr32[2] = htonl(0x0000ffff);
+        src_ip.s6_addr32[2] = htonl(0x0000ffff);
+        dst_ip.s6_addr32[2] = htonl(0x0000ffff);
 
         // Copy IPv4 address into in6_addr for easy comparison below.
-        srcIp.s6_addr32[3] = iph->saddr;
-        dstIp.s6_addr32[3] = iph->daddr;
+        src_ip.s6_addr32[3] = iph->saddr;
+        dst_ip.s6_addr32[3] = iph->daddr;
         protocol = iph->protocol;
         tos = iph->tos;
     } else {
@@ -112,8 +112,8 @@
 
         if (ip6h->version != 6) return;
 
-        srcIp = ip6h->saddr;
-        dstIp = ip6h->daddr;
+        src_ip = ip6h->saddr;
+        dst_ip = ip6h->daddr;
         protocol = ip6h->nexthdr;
         priority = ip6h->priority;
         flow_lbl = ip6h->flow_lbl[0];
@@ -139,33 +139,33 @@
             return;
     }
 
-    RuleEntry* existingRule;
+    RuleEntry* existing_rule;
     if (ipv4) {
-        if (*selectedMap == MAP_A) {
-            existingRule = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
+        if (*selected_map == MAP_A) {
+            existing_rule = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
         } else {
-            existingRule = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
+            existing_rule = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
         }
     } else {
-        if (*selectedMap == MAP_A) {
-            existingRule = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
+        if (*selected_map == MAP_A) {
+            existing_rule = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
         } else {
-            existingRule = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
+            existing_rule = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
         }
     }
 
-    if (existingRule && v6_equal(srcIp, existingRule->srcIp) &&
-        v6_equal(dstIp, existingRule->dstIp) && skb->ifindex == existingRule->ifindex &&
-        ntohs(sport) == htons(existingRule->srcPort) &&
-        ntohs(dport) == htons(existingRule->dstPort) && protocol == existingRule->proto) {
+    if (existing_rule && v6_equal(src_ip, existing_rule->src_ip) &&
+            v6_equal(dst_ip, existing_rule->dst_ip) && skb->ifindex == existing_rule->ifindex &&
+        ntohs(sport) == htons(existing_rule->src_port) &&
+        ntohs(dport) == htons(existing_rule->dst_port) && protocol == existing_rule->proto) {
         if (ipv4) {
-            uint8_t newTos = UPDATE_TOS(existingRule->dscpVal, tos);
+            uint8_t newTos = UPDATE_TOS(existing_rule->dscp_val, tos);
             bpf_l3_csum_replace(skb, IP4_OFFSET(check, l2_header_size), htons(tos), htons(newTos),
                                 sizeof(uint16_t));
             bpf_skb_store_bytes(skb, IP4_OFFSET(tos, l2_header_size), &newTos, sizeof(newTos), 0);
         } else {
-            uint8_t new_priority = UPDATE_PRIORITY(existingRule->dscpVal);
-            uint8_t new_flow_label = UPDATE_FLOW_LABEL(existingRule->dscpVal, flow_lbl);
+            uint8_t new_priority = UPDATE_PRIORITY(existing_rule->dscp_val);
+            uint8_t new_flow_label = UPDATE_FLOW_LABEL(existing_rule->dscp_val, flow_lbl);
             bpf_skb_store_bytes(skb, 0 + l2_header_size, &new_priority, sizeof(uint8_t), 0);
             bpf_skb_store_bytes(skb, 1 + l2_header_size, &new_flow_label, sizeof(uint8_t), 0);
         }
@@ -173,12 +173,12 @@
     }
 
     // Linear scan ipv4_dscp_policies_map since no stored params match skb.
-    int bestScore = -1;
-    uint32_t bestMatch = 0;
+    int best_score = -1;
+    uint32_t best_match = 0;
 
     for (register uint64_t i = 0; i < MAX_POLICIES; i++) {
         int score = 0;
-        uint8_t tempMask = 0;
+        uint8_t temp_mask = 0;
         // Using a uint64 in for loop prevents infinite loop during BPF load,
         // but the key is uint32, so convert back.
         uint32_t key = i;
@@ -190,40 +190,40 @@
             policy = bpf_ipv6_dscp_policies_map_lookup_elem(&key);
         }
 
-        // If the policy lookup failed, presentFields is 0, or iface index does not match
+        // If the policy lookup failed, present_fields is 0, or iface index does not match
         // index on skb buff, then we can continue to next policy.
-        if (!policy || policy->presentFields == 0 || policy->ifindex != skb->ifindex) continue;
+        if (!policy || policy->present_fields == 0 || policy->ifindex != skb->ifindex) continue;
 
-        if ((policy->presentFields & SRC_IP_MASK_FLAG) == SRC_IP_MASK_FLAG &&
-            v6_equal(srcIp, policy->srcIp)) {
+        if ((policy->present_fields & SRC_IP_MASK_FLAG) == SRC_IP_MASK_FLAG &&
+            v6_equal(src_ip, policy->src_ip)) {
             score++;
-            tempMask |= SRC_IP_MASK_FLAG;
+            temp_mask |= SRC_IP_MASK_FLAG;
         }
-        if ((policy->presentFields & DST_IP_MASK_FLAG) == DST_IP_MASK_FLAG &&
-            v6_equal(dstIp, policy->dstIp)) {
+        if ((policy->present_fields & DST_IP_MASK_FLAG) == DST_IP_MASK_FLAG &&
+            v6_equal(dst_ip, policy->dst_ip)) {
             score++;
-            tempMask |= DST_IP_MASK_FLAG;
+            temp_mask |= DST_IP_MASK_FLAG;
         }
-        if ((policy->presentFields & SRC_PORT_MASK_FLAG) == SRC_PORT_MASK_FLAG &&
-            ntohs(sport) == htons(policy->srcPort)) {
+        if ((policy->present_fields & SRC_PORT_MASK_FLAG) == SRC_PORT_MASK_FLAG &&
+            ntohs(sport) == htons(policy->src_port)) {
             score++;
-            tempMask |= SRC_PORT_MASK_FLAG;
+            temp_mask |= SRC_PORT_MASK_FLAG;
         }
-        if ((policy->presentFields & DST_PORT_MASK_FLAG) == DST_PORT_MASK_FLAG &&
-            ntohs(dport) >= htons(policy->dstPortStart) &&
-            ntohs(dport) <= htons(policy->dstPortEnd)) {
+        if ((policy->present_fields & DST_PORT_MASK_FLAG) == DST_PORT_MASK_FLAG &&
+            ntohs(dport) >= htons(policy->dst_port_start) &&
+            ntohs(dport) <= htons(policy->dst_port_end)) {
             score++;
-            tempMask |= DST_PORT_MASK_FLAG;
+            temp_mask |= DST_PORT_MASK_FLAG;
         }
-        if ((policy->presentFields & PROTO_MASK_FLAG) == PROTO_MASK_FLAG &&
+        if ((policy->present_fields & PROTO_MASK_FLAG) == PROTO_MASK_FLAG &&
             protocol == policy->proto) {
             score++;
-            tempMask |= PROTO_MASK_FLAG;
+            temp_mask |= PROTO_MASK_FLAG;
         }
 
-        if (score > bestScore && tempMask == policy->presentFields) {
-            bestMatch = i;
-            bestScore = score;
+        if (score > best_score && temp_mask == policy->present_fields) {
+            best_match = i;
+            best_score = score;
         }
     }
 
@@ -231,16 +231,16 @@
     uint8_t new_dscp = 0;
     uint8_t new_priority = 0;
     uint8_t new_flow_lbl = 0;
-    if (bestScore > 0) {
+    if (best_score > 0) {
         DscpPolicy* policy;
         if (ipv4) {
-            policy = bpf_ipv4_dscp_policies_map_lookup_elem(&bestMatch);
+            policy = bpf_ipv4_dscp_policies_map_lookup_elem(&best_match);
         } else {
-            policy = bpf_ipv6_dscp_policies_map_lookup_elem(&bestMatch);
+            policy = bpf_ipv6_dscp_policies_map_lookup_elem(&best_match);
         }
 
         if (policy) {
-            new_dscp = policy->dscpVal;
+            new_dscp = policy->dscp_val;
             if (ipv4) {
                 new_tos = UPDATE_TOS(new_dscp, tos);
             } else {
@@ -252,24 +252,24 @@
         return;
 
     RuleEntry value = {
-        .srcIp = srcIp,
-        .dstIp = dstIp,
+        .src_ip = src_ip,
+        .dst_ip = dst_ip,
         .ifindex = skb->ifindex,
-        .srcPort = sport,
-        .dstPort = dport,
+        .src_port = sport,
+        .dst_port = dport,
         .proto = protocol,
-        .dscpVal = new_dscp,
+        .dscp_val = new_dscp,
     };
 
     // Update map with new policy.
     if (ipv4) {
-        if (*selectedMap == MAP_A) {
+        if (*selected_map == MAP_A) {
             bpf_ipv4_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
         } else {
             bpf_ipv4_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
         }
     } else {
-        if (*selectedMap == MAP_A) {
+        if (*selected_map == MAP_A) {
             bpf_ipv6_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
         } else {
             bpf_ipv6_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
diff --git a/bpf_progs/dscp_policy.h b/bpf_progs/dscp_policy.h
index 1637f7a..455a121 100644
--- a/bpf_progs/dscp_policy.h
+++ b/bpf_progs/dscp_policy.h
@@ -44,27 +44,27 @@
         (void*)BPF_FUNC_skb_ecn_set_ce;
 
 typedef struct {
-    struct in6_addr srcIp;
-    struct in6_addr dstIp;
+    struct in6_addr src_ip;
+    struct in6_addr dst_ip;
     uint32_t ifindex;
-    __be16 srcPort;
-    __be16 dstPortStart;
-    __be16 dstPortEnd;
+    __be16 src_port;
+    __be16 dst_port_start;
+    __be16 dst_port_end;
     uint8_t proto;
-    uint8_t dscpVal;
-    uint8_t presentFields;
+    uint8_t dscp_val;
+    uint8_t present_fields;
     uint8_t pad[3];
 } DscpPolicy;
 STRUCT_SIZE(DscpPolicy, 2 * 16 + 4 + 3 * 2 + 3 * 1 + 3);  // 48
 
 typedef struct {
-    struct in6_addr srcIp;
-    struct in6_addr dstIp;
+    struct in6_addr src_ip;
+    struct in6_addr dst_ip;
     __u32 ifindex;
-    __be16 srcPort;
-    __be16 dstPort;
+    __be16 src_port;
+    __be16 dst_port;
     __u8 proto;
-    __u8 dscpVal;
+    __u8 dscp_val;
     __u8 pad[2];
 } RuleEntry;
 STRUCT_SIZE(RuleEntry, 2 * 16 + 1 * 4 + 2 * 2 + 2 * 1 + 2);  // 44
\ No newline at end of file
diff --git a/framework-t/src/android/app/usage/NetworkStats.java b/framework-t/src/android/app/usage/NetworkStats.java
index 74fe4bd..26841de 100644
--- a/framework-t/src/android/app/usage/NetworkStats.java
+++ b/framework-t/src/android/app/usage/NetworkStats.java
@@ -1,17 +1,17 @@
 /**
  * Copyright (C) 2015 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
+ * 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
+ *      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.
+ * 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 android.app.usage;
@@ -36,11 +36,11 @@
 import java.util.ArrayList;
 
 /**
- * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects
- * are returned as results to various queries in {@link NetworkStatsManager}.
+ * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats}
+ * objects are returned as results to various queries in {@link NetworkStatsManager}.
  */
 public final class NetworkStats implements AutoCloseable {
-    private final static String TAG = "NetworkStats";
+    private static final String TAG = "NetworkStats";
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
@@ -616,7 +616,7 @@
     /**
      * Steps to next uid in enumeration and collects history for that.
      */
-    private void stepHistory(){
+    private void stepHistory() {
         if (hasNextUid()) {
             stepUid();
             mHistory = null;
@@ -692,8 +692,8 @@
                 bucketOut.mMetered = Bucket.METERED_ALL;
                 bucketOut.mRoaming = Bucket.ROAMING_ALL;
                 bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
-                bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart +
-                        mRecycledHistoryEntry.bucketDuration;
+                bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart
+                        + mRecycledHistoryEntry.bucketDuration;
                 bucketOut.mRxBytes = mRecycledHistoryEntry.rxBytes;
                 bucketOut.mRxPackets = mRecycledHistoryEntry.rxPackets;
                 bucketOut.mTxBytes = mRecycledHistoryEntry.txBytes;
diff --git a/framework-t/src/android/app/usage/NetworkStatsManager.java b/framework-t/src/android/app/usage/NetworkStatsManager.java
index f41475b..d139544 100644
--- a/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -19,6 +19,9 @@
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_WIFI;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -55,6 +58,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -1020,14 +1024,17 @@
         switch (networkType) {
             case ConnectivityManager.TYPE_MOBILE:
                 template = subscriberId == null
-                        ? NetworkTemplate.buildTemplateMobileWildcard()
-                        : NetworkTemplate.buildTemplateMobileAll(subscriberId);
+                        ? new NetworkTemplate.Builder(MATCH_MOBILE)
+                                .setMeteredness(METERED_YES).build()
+                        : new NetworkTemplate.Builder(MATCH_MOBILE)
+                                .setMeteredness(METERED_YES)
+                                .setSubscriberIds(Set.of(subscriberId)).build();
                 break;
             case ConnectivityManager.TYPE_WIFI:
                 template = TextUtils.isEmpty(subscriberId)
-                        ? NetworkTemplate.buildTemplateWifiWildcard()
-                        : NetworkTemplate.buildTemplateWifi(NetworkTemplate.WIFI_NETWORKID_ALL,
-                                subscriberId);
+                        ? new NetworkTemplate.Builder(MATCH_WIFI).build()
+                        : new  NetworkTemplate.Builder(MATCH_WIFI)
+                                .setSubscriberIds(Set.of(subscriberId)).build();
                 break;
             default:
                 throw new IllegalArgumentException("Cannot create template for network type "
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 0bb98f8..a655a9b 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -1041,7 +1041,7 @@
      */
     public long getTotalPackets() {
         long total = 0;
-        for (int i = size-1; i >= 0; i--) {
+        for (int i = size - 1; i >= 0; i--) {
             total += rxPackets[i] + txPackets[i];
         }
         return total;
diff --git a/nearby/OWNERS b/nearby/OWNERS
index 980c221..844ef06 100644
--- a/nearby/OWNERS
+++ b/nearby/OWNERS
@@ -1,4 +1,6 @@
+chenw@google.com
 chunzhang@google.com
 weiwa@google.com
 weiwu@google.com
+xinhe@google.com
 xlythe@google.com
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 3376c15..2613363 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -65,6 +65,7 @@
     TETHERING "map_offload_tether_stats_map",
     TETHERING "map_offload_tether_upstream4_map",
     TETHERING "map_offload_tether_upstream6_map",
+    TETHERING "map_test_bitmap",
     TETHERING "map_test_tether_downstream6_map",
     TETHERING "prog_offload_schedcls_tether_downstream4_ether",
     TETHERING "prog_offload_schedcls_tether_downstream4_rawip",
@@ -76,6 +77,11 @@
     TETHERING "prog_offload_schedcls_tether_upstream6_rawip",
 };
 
+// Provided by *current* mainline module for S+ devices with 5.10+ kernels
+static const set<string> MAINLINE_FOR_S_5_10_PLUS = {
+    TETHERING "prog_test_xdp_drop_ipv4_udp_ether",
+};
+
 // Provided by *current* mainline module for T+ devices
 static const set<string> MAINLINE_FOR_T_PLUS = {
     SHARED "map_block_blocked_ports_map",
@@ -146,6 +152,7 @@
     DO_EXPECT(IsAtLeastR() && !IsAtLeastS(), PLATFORM_ONLY_IN_R);
 
     DO_EXPECT(IsAtLeastS(), MAINLINE_FOR_S_PLUS);
+    DO_EXPECT(IsAtLeastS() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_S_5_10_PLUS);
 
     // Nothing added or removed in SCv2.
 
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index b1b76ec..71c03ff 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -16,6 +16,10 @@
 
 package android.app.usage;
 
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_WIFI;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -52,6 +56,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 
+import java.util.Set;
+
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -204,20 +210,20 @@
     @Test
     public void testNetworkTemplateWhenRunningQueryDetails_NoSubscriberId() throws RemoteException {
         runQueryDetailsAndCheckTemplate(ConnectivityManager.TYPE_MOBILE,
-                null /* subscriberId */, NetworkTemplate.buildTemplateMobileWildcard());
+                null /* subscriberId */, new NetworkTemplate.Builder(MATCH_MOBILE)
+                        .setMeteredness(METERED_YES).build());
         runQueryDetailsAndCheckTemplate(ConnectivityManager.TYPE_WIFI,
-                "" /* subscriberId */, NetworkTemplate.buildTemplateWifiWildcard());
+                "" /* subscriberId */, new NetworkTemplate.Builder(MATCH_WIFI).build());
         runQueryDetailsAndCheckTemplate(ConnectivityManager.TYPE_WIFI,
-                null /* subscriberId */, NetworkTemplate.buildTemplateWifiWildcard());
+                null /* subscriberId */, new NetworkTemplate.Builder(MATCH_WIFI).build());
     }
 
     @Test
     public void testNetworkTemplateWhenRunningQueryDetails_MergedCarrierWifi()
             throws RemoteException {
         runQueryDetailsAndCheckTemplate(ConnectivityManager.TYPE_WIFI,
-                TEST_SUBSCRIBER_ID,
-                NetworkTemplate.buildTemplateWifi(NetworkTemplate.WIFI_NETWORKID_ALL,
-                        TEST_SUBSCRIBER_ID));
+                TEST_SUBSCRIBER_ID, new NetworkTemplate.Builder(MATCH_WIFI)
+                        .setSubscriberIds(Set.of(TEST_SUBSCRIBER_ID)).build());
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 5c1992d..03f2589 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -969,6 +969,31 @@
                 AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, AppOpsManager.OPSTR_ACTIVATE_VPN);
     }
 
+    private void setAppOpsPermission() {
+        doAnswer(invocation -> {
+            when(mAppOps.noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN,
+                    Process.myUid(), TEST_VPN_PKG,
+                    null /* attributionTag */, null /* message */))
+                    .thenReturn(AppOpsManager.MODE_ALLOWED);
+            return null;
+        }).when(mAppOps).setMode(
+                eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
+                eq(Process.myUid()),
+                eq(TEST_VPN_PKG),
+                eq(AppOpsManager.MODE_ALLOWED));
+    }
+
+    @Test
+    public void testProvisionVpnProfileNotPreconsented_withControlVpnPermission() throws Exception {
+        setAppOpsPermission();
+        doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+        final Vpn vpn = createVpnAndSetupUidChecks();
+
+        // ACTIVATE_PLATFORM_VPN will be granted if VPN app has CONTROL_VPN permission.
+        checkProvisionVpnProfile(vpn, true /* expectedResult */,
+                AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+    }
+
     @Test
     public void testProvisionVpnProfileVpnServicePreconsented() throws Exception {
         final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);