Merge "Keep the screen on while the test activity is on top." into tm-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 95f854b..c4c79c6 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -22,6 +22,18 @@
}
]
},
+ // Also run CtsNetTestCasesLatestSdk to ensure tests using older shims pass.
+ {
+ "name": "CtsNetTestCasesLatestSdk",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
{
"name": "bpf_existence_test"
},
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index a9c1005..9076dca 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -106,12 +106,6 @@
certificate: "com.android.tethering",
}
-filegroup {
- name: "connectivity-hiddenapi-files",
- srcs: ["hiddenapi/*.txt"],
- visibility: ["//packages/modules/Connectivity:__subpackages__"],
-}
-
// Encapsulate the contributions made by the com.android.tethering to the bootclasspath.
bootclasspath_fragment {
name: "com.android.tethering-bootclasspath-fragment",
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 44935fc..35a394d 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -1288,7 +1288,7 @@
// Finally bring up serving on the new interface
mWifiP2pTetherInterface = group.getInterface();
- enableWifiIpServing(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
+ enableWifiP2pIpServing(mWifiP2pTetherInterface);
}
private void handleUserRestrictionAction() {
@@ -1379,20 +1379,22 @@
changeInterfaceState(ifname, ipServingMode);
}
- private void disableWifiIpServingCommon(int tetheringType, String ifname, int apState) {
- mLog.log("Canceling WiFi tethering request -"
- + " type=" + tetheringType
- + " interface=" + ifname
- + " state=" + apState);
-
- if (!TextUtils.isEmpty(ifname)) {
- final TetherState ts = mTetherStates.get(ifname);
- if (ts != null) {
- ts.ipServer.unwanted();
- return;
- }
+ private void disableWifiIpServingCommon(int tetheringType, String ifname) {
+ if (!TextUtils.isEmpty(ifname) && mTetherStates.containsKey(ifname)) {
+ mTetherStates.get(ifname).ipServer.unwanted();
+ return;
}
+ if (SdkLevel.isAtLeastT()) {
+ mLog.e("Tethering no longer handle untracked interface after T: " + ifname);
+ return;
+ }
+
+ // Attempt to guess the interface name before T. Pure AOSP code should never enter here
+ // because WIFI_AP_STATE_CHANGED intent always include ifname and it should be tracked
+ // by mTetherStates. In case OEMs have some modification in wifi side which pass null
+ // or empty ifname. Before T, tethering allow to disable the first wifi ipServer if
+ // given ifname don't match any tracking ipServer.
for (int i = 0; i < mTetherStates.size(); i++) {
final IpServer ipServer = mTetherStates.valueAt(i).ipServer;
if (ipServer.interfaceType() == tetheringType) {
@@ -1400,7 +1402,6 @@
return;
}
}
-
mLog.log("Error disabling Wi-Fi IP serving; "
+ (TextUtils.isEmpty(ifname) ? "no interface name specified"
: "specified interface: " + ifname));
@@ -1409,20 +1410,39 @@
private void disableWifiIpServing(String ifname, int apState) {
// Regardless of whether we requested this transition, the AP has gone
// down. Don't try to tether again unless we're requested to do so.
- // TODO: Remove this altogether, once Wi-Fi reliably gives us an
- // interface name with every broadcast.
mWifiTetherRequested = false;
- disableWifiIpServingCommon(TETHERING_WIFI, ifname, apState);
+ mLog.log("Canceling WiFi tethering request - interface=" + ifname + " state=" + apState);
+
+ disableWifiIpServingCommon(TETHERING_WIFI, ifname);
+ }
+
+ private void enableWifiP2pIpServing(String ifname) {
+ if (TextUtils.isEmpty(ifname)) {
+ mLog.e("Cannot enable P2P IP serving with invalid interface");
+ return;
+ }
+
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable p2p regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI_P2P : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+ enableIpServing(type, ifname, IpServer.STATE_LOCAL_ONLY);
}
private void disableWifiP2pIpServingIfNeeded(String ifname) {
if (TextUtils.isEmpty(ifname)) return;
- disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname, /* fake */ 0);
+ mLog.log("Canceling P2P tethering request - interface=" + ifname);
+ disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname);
}
private void enableWifiIpServing(String ifname, int wifiIpMode) {
+ mLog.log("request WiFi tethering - interface=" + ifname + " state=" + wifiIpMode);
+
// Map wifiIpMode values to IpServer.Callback serving states, inferring
// from mWifiTetherRequested as a final "best guess".
final int ipServingMode;
@@ -1438,13 +1458,18 @@
return;
}
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable wifi regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+
if (!TextUtils.isEmpty(ifname)) {
- ensureIpServerStarted(ifname);
- changeInterfaceState(ifname, ipServingMode);
+ enableIpServing(type, ifname, ipServingMode);
} else {
- mLog.e(String.format(
- "Cannot enable IP serving in mode %s on missing interface name",
- ipServingMode));
+ mLog.e("Cannot enable IP serving on missing interface name");
}
}
@@ -2715,23 +2740,28 @@
mTetherMainSM.sendMessage(which, state, 0, newLp);
}
+ private boolean hasSystemFeature(final String feature) {
+ return mContext.getPackageManager().hasSystemFeature(feature);
+ }
+
+ private boolean checkTetherableType(int type) {
+ if ((type == TETHERING_WIFI || type == TETHERING_WIGIG)
+ && !hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ return false;
+ }
+
+ if (type == TETHERING_WIFI_P2P && !hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
+ return false;
+ }
+
+ return type != TETHERING_INVALID;
+ }
+
private void ensureIpServerStarted(final String iface) {
// If we don't care about this type of interface, ignore.
final int interfaceType = ifaceNameToType(iface);
- if (interfaceType == TETHERING_INVALID) {
- mLog.log(iface + " is not a tetherable iface, ignoring");
- return;
- }
-
- final PackageManager pm = mContext.getPackageManager();
- if ((interfaceType == TETHERING_WIFI || interfaceType == TETHERING_WIGIG)
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- mLog.log(iface + " is not tetherable, because WiFi feature is disabled");
- return;
- }
- if (interfaceType == TETHERING_WIFI_P2P
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
- mLog.log(iface + " is not tetherable, because WiFi Direct feature is disabled");
+ if (!checkTetherableType(interfaceType)) {
+ mLog.log(iface + " is used for " + interfaceType + " which is not tetherable");
return;
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 2fd7f48..6ef0e24 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -57,6 +57,7 @@
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -936,7 +937,7 @@
// Emulate externally-visible WifiManager effects, when hotspot mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1509,7 +1510,7 @@
// Emulate externally-visible WifiManager effects, when tethering mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1903,7 +1904,13 @@
mTethering.unregisterTetheringEventCallback(callback);
mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED);
+ if (isAtLeastT()) {
+ // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
+ callback2.assertNoStateChangeCallback();
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
+ IFACE_IP_MODE_TETHERED);
+ }
tetherState = callback2.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
mLooper.dispatchAll();
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 0e7b22d..4fc678f 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -119,5 +119,5 @@
include_dirs: [
"frameworks/libs/net/common/netd/libnetdutils/include",
],
- sub_dir: "net_shared",
+ sub_dir: "netd_shared",
}
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index ddd9a1c..601b932 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -19,6 +19,9 @@
#include <netinet/in.h>
#include <stdint.h>
+// The resulting .o needs to load on the Android T bpfloader v0.12+
+#define BPFLOADER_MIN_VER 12u
+
#include "bpf_helpers.h"
#define ALLOW 1
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 9a246a6..14fcdd6 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -98,29 +98,29 @@
static const int CONFIGURATION_MAP_SIZE = 2;
static const int UID_OWNER_MAP_SIZE = 2000;
-#define BPF_PATH "/sys/fs/bpf/net_shared/"
+#define BPF_NETD_PATH "/sys/fs/bpf/netd_shared/"
-#define BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_egress_stats"
-#define BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_ingress_stats"
-#define XT_BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_ingress_xtbpf"
-#define XT_BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_egress_xtbpf"
-#define XT_BPF_ALLOWLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_allowlist_xtbpf"
-#define XT_BPF_DENYLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_denylist_xtbpf"
-#define CGROUP_SOCKET_PROG_PATH BPF_PATH "prog_netd_cgroupsock_inet_create"
+#define BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_egress_stats"
+#define BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_ingress_stats"
+#define XT_BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_ingress_xtbpf"
+#define XT_BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_egress_xtbpf"
+#define XT_BPF_ALLOWLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf"
+#define XT_BPF_DENYLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_denylist_xtbpf"
+#define CGROUP_SOCKET_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
-#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
+#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_NETD_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
-#define COOKIE_TAG_MAP_PATH BPF_PATH "map_netd_cookie_tag_map"
-#define UID_COUNTERSET_MAP_PATH BPF_PATH "map_netd_uid_counterset_map"
-#define APP_UID_STATS_MAP_PATH BPF_PATH "map_netd_app_uid_stats_map"
-#define STATS_MAP_A_PATH BPF_PATH "map_netd_stats_map_A"
-#define STATS_MAP_B_PATH BPF_PATH "map_netd_stats_map_B"
-#define IFACE_INDEX_NAME_MAP_PATH BPF_PATH "map_netd_iface_index_name_map"
-#define IFACE_STATS_MAP_PATH BPF_PATH "map_netd_iface_stats_map"
-#define CONFIGURATION_MAP_PATH BPF_PATH "map_netd_configuration_map"
-#define UID_OWNER_MAP_PATH BPF_PATH "map_netd_uid_owner_map"
-#define UID_PERMISSION_MAP_PATH BPF_PATH "map_netd_uid_permission_map"
+#define COOKIE_TAG_MAP_PATH BPF_NETD_PATH "map_netd_cookie_tag_map"
+#define UID_COUNTERSET_MAP_PATH BPF_NETD_PATH "map_netd_uid_counterset_map"
+#define APP_UID_STATS_MAP_PATH BPF_NETD_PATH "map_netd_app_uid_stats_map"
+#define STATS_MAP_A_PATH BPF_NETD_PATH "map_netd_stats_map_A"
+#define STATS_MAP_B_PATH BPF_NETD_PATH "map_netd_stats_map_B"
+#define IFACE_INDEX_NAME_MAP_PATH BPF_NETD_PATH "map_netd_iface_index_name_map"
+#define IFACE_STATS_MAP_PATH BPF_NETD_PATH "map_netd_iface_stats_map"
+#define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
+#define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
+#define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
enum UidOwnerMatchType {
NO_MATCH = 0,
@@ -163,13 +163,15 @@
#define UID_RULES_CONFIGURATION_KEY 1
#define CURRENT_STATS_MAP_CONFIGURATION_KEY 2
+#define BPF_CLATD_PATH "/sys/fs/bpf/net_shared/"
+
#define CLAT_INGRESS6_PROG_RAWIP_NAME "prog_clatd_schedcls_ingress6_clat_rawip"
#define CLAT_INGRESS6_PROG_ETHER_NAME "prog_clatd_schedcls_ingress6_clat_ether"
-#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
-#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_PATH CLAT_INGRESS6_PROG_ETHER_NAME
+#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
+#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_ETHER_NAME
-#define CLAT_INGRESS6_MAP_PATH BPF_PATH "map_clatd_clat_ingress6_map"
+#define CLAT_INGRESS6_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_ingress6_map"
typedef struct {
uint32_t iif; // The input interface index
@@ -187,10 +189,10 @@
#define CLAT_EGRESS4_PROG_RAWIP_NAME "prog_clatd_schedcls_egress4_clat_rawip"
#define CLAT_EGRESS4_PROG_ETHER_NAME "prog_clatd_schedcls_egress4_clat_ether"
-#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
-#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_PATH CLAT_EGRESS4_PROG_ETHER_NAME
+#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
+#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_ETHER_NAME
-#define CLAT_EGRESS4_MAP_PATH BPF_PATH "map_clatd_clat_egress4_map"
+#define CLAT_EGRESS4_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_egress4_map"
typedef struct {
uint32_t iif; // The input interface index
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 9a9d337..87795f5 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -30,6 +30,9 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
+// The resulting .o needs to load on the Android T bpfloader v0.12+
+#define BPFLOADER_MIN_VER 12u
+
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
#include "bpf_shared.h"
diff --git a/bpf_progs/dscp_policy.c b/bpf_progs/dscp_policy.c
index d5df7ef..7211f2b 100644
--- a/bpf_progs/dscp_policy.c
+++ b/bpf_progs/dscp_policy.c
@@ -27,6 +27,9 @@
#include <netinet/udp.h>
#include <string.h>
+// The resulting .o needs to load on the Android T bpfloader v0.12+
+#define BPFLOADER_MIN_VER 12u
+
#include "bpf_helpers.h"
#include "dscp_policy.h"
diff --git a/bpf_progs/dscp_policy.h b/bpf_progs/dscp_policy.h
index 777c4ff..1637f7a 100644
--- a/bpf_progs/dscp_policy.h
+++ b/bpf_progs/dscp_policy.h
@@ -26,12 +26,11 @@
#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
-#ifndef v6_equal
-#define v6_equal(a, b) (a.s6_addr32[0] == b.s6_addr32[0] && \
- a.s6_addr32[1] == b.s6_addr32[1] && \
- a.s6_addr32[2] == b.s6_addr32[2] && \
- a.s6_addr32[3] == b.s6_addr32[3])
-#endif
+#define v6_equal(a, b) \
+ (((a.s6_addr32[0] ^ b.s6_addr32[0]) | \
+ (a.s6_addr32[1] ^ b.s6_addr32[1]) | \
+ (a.s6_addr32[2] ^ b.s6_addr32[2]) | \
+ (a.s6_addr32[3] ^ b.s6_addr32[3])) == 0)
// TODO: these are already defined in packages/modules/Connectivity/bpf_progs/bpf_net_helpers.h.
// smove to common location in future.
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 76911f4..33381d7 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+// The resulting .o needs to load on the Android T Beta 3 bpfloader v0.13+
+#define BPFLOADER_MIN_VER 13u
+
#include <bpf_helpers.h>
#include <linux/bpf.h>
#include <linux/if.h>
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 88ca2af..9c8b359 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -103,7 +103,7 @@
// Do not add static_libs to this library: put them in framework-connectivity instead.
// The jarjar rules are only so that references to jarjared utils in
// framework-connectivity-pre-jarjar match at runtime.
- jarjar_rules: ":framework-connectivity-jarjar-rules",
+ jarjar_rules: ":connectivity-jarjar-rules",
permitted_packages: [
"android.app.usage",
"android.net",
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
index 29ea772..6a1d2dd 100644
--- a/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -865,6 +865,9 @@
* Add association of the history with the specified key in this map.
*
* @param key The object used to identify a network, see {@link Key}.
+ * If history already exists for this key, then the passed-in history is appended
+ * to the previously-passed in history. The caller must ensure that the history
+ * passed-in timestamps are greater than all previously-passed-in timestamps.
* @param history {@link NetworkStatsHistory} instance associated to the given {@link Key}.
* @return The builder object.
*/
@@ -874,9 +877,21 @@
Objects.requireNonNull(key);
Objects.requireNonNull(history);
final List<Entry> historyEntries = history.getEntries();
+ final NetworkStatsHistory existing = mEntries.get(key);
+ final int size = historyEntries.size() + ((existing != null) ? existing.size() : 0);
final NetworkStatsHistory.Builder historyBuilder =
- new NetworkStatsHistory.Builder(mBucketDurationMillis, historyEntries.size());
+ new NetworkStatsHistory.Builder(mBucketDurationMillis, size);
+
+ // TODO: this simply appends the entries to any entries that were already present in
+ // the builder, which requires the caller to pass in entries in order. We might be
+ // able to do better with something like recordHistory.
+ if (existing != null) {
+ for (Entry entry : existing.getEntries()) {
+ historyBuilder.addEntry(entry);
+ }
+ }
+
for (Entry entry : historyEntries) {
historyBuilder.addEntry(entry);
}
diff --git a/framework-t/src/android/net/NetworkStatsHistory.java b/framework-t/src/android/net/NetworkStatsHistory.java
index b45d44d..0ff9d96 100644
--- a/framework-t/src/android/net/NetworkStatsHistory.java
+++ b/framework-t/src/android/net/NetworkStatsHistory.java
@@ -32,6 +32,7 @@
import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -949,6 +950,25 @@
return writer.toString();
}
+ /**
+ * Same as "equals", but not actually called equals as this would affect public API behavior.
+ * @hide
+ */
+ @Nullable
+ public boolean isSameAs(NetworkStatsHistory other) {
+ return bucketCount == other.bucketCount
+ && Arrays.equals(bucketStart, other.bucketStart)
+ // Don't check activeTime since it can change on import due to the importer using
+ // recordHistory. It's also not exposed by the APIs or present in dumpsys or
+ // toString().
+ && Arrays.equals(rxBytes, other.rxBytes)
+ && Arrays.equals(rxPackets, other.rxPackets)
+ && Arrays.equals(txBytes, other.txBytes)
+ && Arrays.equals(txPackets, other.txPackets)
+ && Arrays.equals(operations, other.operations)
+ && totalBytes == other.totalBytes;
+ }
+
@UnsupportedAppUsage
public static final @android.annotation.NonNull Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
@Override
@@ -1116,14 +1136,44 @@
mOperations = new ArrayList<>(initialCapacity);
}
+ private void addToElement(List<Long> list, int pos, long value) {
+ list.set(pos, list.get(pos) + value);
+ }
+
/**
* Add an {@link Entry} into the {@link NetworkStatsHistory} instance.
*
- * @param entry The target {@link Entry} object.
+ * @param entry The target {@link Entry} object. The entry timestamp must be greater than
+ * that of any previously-added entry.
* @return The builder object.
*/
@NonNull
public Builder addEntry(@NonNull Entry entry) {
+ final int lastBucket = mBucketStart.size() - 1;
+ final long lastBucketStart = (lastBucket != -1) ? mBucketStart.get(lastBucket) : 0;
+
+ // If last bucket has the same timestamp, modify it instead of adding another bucket.
+ // This allows callers to pass in the same bucket twice (e.g., to accumulate
+ // data over time), but still requires that entries must be sorted.
+ // The importer will do this in case a rotated file has the same timestamp as
+ // the previous file.
+ if (lastBucket != -1 && entry.bucketStart == lastBucketStart) {
+ addToElement(mActiveTime, lastBucket, entry.activeTime);
+ addToElement(mRxBytes, lastBucket, entry.rxBytes);
+ addToElement(mRxPackets, lastBucket, entry.rxPackets);
+ addToElement(mTxBytes, lastBucket, entry.txBytes);
+ addToElement(mTxPackets, lastBucket, entry.txPackets);
+ addToElement(mOperations, lastBucket, entry.operations);
+ return this;
+ }
+
+ // Inserting in the middle is prohibited for performance reasons.
+ if (entry.bucketStart <= lastBucketStart) {
+ throw new IllegalArgumentException("new bucket start " + entry.bucketStart
+ + " must be greater than last bucket start " + lastBucketStart);
+ }
+
+ // Common case: add entries at the end of the list.
mBucketStart.add(entry.bucketStart);
mActiveTime.add(entry.activeTime);
mRxBytes.add(entry.rxBytes);
diff --git a/framework/Android.bp b/framework/Android.bp
index c8b64c7..d7de439 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -111,7 +111,6 @@
// because the tethering stubs depend on the connectivity stubs (e.g.,
// TetheringRequest depends on LinkAddress).
"framework-tethering.stubs.module_lib",
- "framework-wifi.stubs.module_lib",
],
visibility: ["//packages/modules/Connectivity:__subpackages__"]
}
@@ -120,7 +119,7 @@
name: "framework-connectivity",
defaults: ["framework-connectivity-defaults"],
installable: true,
- jarjar_rules: ":framework-connectivity-jarjar-rules",
+ jarjar_rules: ":connectivity-jarjar-rules",
permitted_packages: ["android.net"],
impl_library_visibility: [
"//packages/modules/Connectivity/Tethering/apex",
@@ -223,35 +222,3 @@
],
output_extension: "srcjar",
}
-
-java_genrule {
- name: "framework-connectivity-jarjar-rules",
- tool_files: [
- ":connectivity-hiddenapi-files",
- ":framework-connectivity-pre-jarjar",
- ":framework-connectivity-t-pre-jarjar",
- ":framework-connectivity.stubs.module_lib",
- ":framework-connectivity-t.stubs.module_lib",
- "jarjar-excludes.txt",
- ],
- tools: [
- "jarjar-rules-generator",
- "dexdump",
- ],
- out: ["framework_connectivity_jarjar_rules.txt"],
- cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :framework-connectivity-pre-jarjar) " +
- "$(location :framework-connectivity-t-pre-jarjar) " +
- "--prefix android.net.connectivity " +
- "--apistubs $(location :framework-connectivity.stubs.module_lib) " +
- "$(location :framework-connectivity-t.stubs.module_lib) " +
- "--unsupportedapi $(locations :connectivity-hiddenapi-files) " +
- "--excludes $(location jarjar-excludes.txt) " +
- "--dexdump $(location dexdump) " +
- "--output $(out)",
- visibility: [
- "//packages/modules/Connectivity/framework:__subpackages__",
- "//packages/modules/Connectivity/framework-t:__subpackages__",
- "//packages/modules/Connectivity/service",
- ],
-}
diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt
deleted file mode 100644
index 1311765..0000000
--- a/framework/jarjar-excludes.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-# INetworkStatsProvider / INetworkStatsProviderCallback are referenced from net-tests-utils, which
-# may be used by tests that do not apply connectivity jarjar rules.
-# TODO: move files to a known internal package (like android.net.connectivity.visiblefortesting)
-# so that they do not need jarjar
-android\.net\.netstats\.provider\.INetworkStatsProvider(\$.+)?
-android\.net\.netstats\.provider\.INetworkStatsProviderCallback(\$.+)?
-
-# INetworkAgent / INetworkAgentRegistry are used in NetworkAgentTest
-# TODO: move files to android.net.connectivity.visiblefortesting
-android\.net\.INetworkAgent(\$.+)?
-android\.net\.INetworkAgentRegistry(\$.+)?
-
-# IConnectivityDiagnosticsCallback used in ConnectivityDiagnosticsManagerTest
-# TODO: move files to android.net.connectivity.visiblefortesting
-android\.net\.IConnectivityDiagnosticsCallback(\$.+)?
-
-
-# KeepaliveUtils is used by ConnectivityManager CTS
-# TODO: move into service-connectivity so framework-connectivity stops using
-# ServiceConnectivityResources (callers need high permissions to find/query the resource apk anyway)
-# and have a ConnectivityManager test API instead
-android\.net\.util\.KeepaliveUtils(\$.+)?
-
-# TODO (b/217115866): add jarjar rules for Nearby
-android\.nearby\..+
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index 857ece5..7478b3e 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -232,8 +232,7 @@
return NULL;
}
- jclass class_TcpRepairWindow = env->FindClass(
- "android/net/connectivity/android/net/TcpRepairWindow");
+ jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "<init>", "(IIIIII)V");
return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window,
@@ -254,7 +253,7 @@
{ "bindSocketToNetworkHandle", "(Ljava/io/FileDescriptor;J)I", (void*) android_net_utils_bindSocketToNetworkHandle },
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
- { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/connectivity/android/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
+ { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
{ "resNetworkSend", "(J[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
{ "resNetworkQuery", "(JLjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
diff --git a/framework/src/android/net/DnsResolverServiceManager.java b/framework/src/android/net/DnsResolverServiceManager.java
index e64d2ae..79009e8 100644
--- a/framework/src/android/net/DnsResolverServiceManager.java
+++ b/framework/src/android/net/DnsResolverServiceManager.java
@@ -29,7 +29,7 @@
private final IBinder mResolver;
- public DnsResolverServiceManager(IBinder resolver) {
+ DnsResolverServiceManager(IBinder resolver) {
mResolver = resolver;
}
diff --git a/framework/src/android/net/NattSocketKeepalive.java b/framework/src/android/net/NattSocketKeepalive.java
index 56cc923..a15d165 100644
--- a/framework/src/android/net/NattSocketKeepalive.java
+++ b/framework/src/android/net/NattSocketKeepalive.java
@@ -33,7 +33,7 @@
@NonNull private final InetAddress mDestination;
private final int mResourceId;
- public NattSocketKeepalive(@NonNull IConnectivityManager service,
+ NattSocketKeepalive(@NonNull IConnectivityManager service,
@NonNull Network network,
@NonNull ParcelFileDescriptor pfd,
int resourceId,
@@ -48,7 +48,7 @@
}
@Override
- protected void startImpl(int intervalSec) {
+ void startImpl(int intervalSec) {
mExecutor.execute(() -> {
try {
mService.startNattKeepaliveWithFd(mNetwork, mPfd, mResourceId,
@@ -62,7 +62,7 @@
}
@Override
- protected void stopImpl() {
+ void stopImpl() {
mExecutor.execute(() -> {
try {
if (mSlot != null) {
diff --git a/framework/src/android/net/QosCallbackConnection.java b/framework/src/android/net/QosCallbackConnection.java
index cfceddd..de0fc24 100644
--- a/framework/src/android/net/QosCallbackConnection.java
+++ b/framework/src/android/net/QosCallbackConnection.java
@@ -35,7 +35,7 @@
*
* @hide
*/
-public class QosCallbackConnection extends android.net.IQosCallback.Stub {
+class QosCallbackConnection extends android.net.IQosCallback.Stub {
@NonNull private final ConnectivityManager mConnectivityManager;
@Nullable private volatile QosCallback mCallback;
@@ -56,7 +56,7 @@
* {@link Executor} must run callback sequentially, otherwise the order of
* callbacks cannot be guaranteed.
*/
- public QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager,
+ QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager,
@NonNull final QosCallback callback,
@NonNull final Executor executor) {
mConnectivityManager = Objects.requireNonNull(connectivityManager,
@@ -142,7 +142,7 @@
* There are no synchronization guarantees on exactly when the callback will stop receiving
* messages.
*/
- public void stopReceivingMessages() {
+ void stopReceivingMessages() {
mCallback = null;
}
}
diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java
index 400d03f..ed6eb15 100644
--- a/framework/src/android/net/QosCallbackException.java
+++ b/framework/src/android/net/QosCallbackException.java
@@ -77,7 +77,7 @@
* {@hide}
*/
@NonNull
- public static QosCallbackException createException(@ExceptionType final int type) {
+ static QosCallbackException createException(@ExceptionType final int type) {
switch (type) {
case EX_TYPE_FILTER_NETWORK_RELEASED:
return new QosCallbackException(new NetworkReleasedException());
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
index 458d81f..5c1c3cc 100644
--- a/framework/src/android/net/QosFilter.java
+++ b/framework/src/android/net/QosFilter.java
@@ -33,15 +33,13 @@
@SystemApi
public abstract class QosFilter {
- /** @hide */
- protected QosFilter() {
- // Ensure that all derived types are known, and known to be properly handled when being
- // passed to and from NetworkAgent.
- // For now the only known derived type is QosSocketFilter.
- if (!(this instanceof QosSocketFilter)) {
- throw new UnsupportedOperationException(
- "Unsupported QosFilter type: " + this.getClass().getName());
- }
+ /**
+ * The constructor is kept hidden from outside this package to ensure that all derived types
+ * are known and properly handled when being passed to and from {@link NetworkAgent}.
+ *
+ * @hide
+ */
+ QosFilter() {
}
/**
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
index acb825f..39c2f33 100644
--- a/framework/src/android/net/QosSocketInfo.java
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -73,10 +73,9 @@
* The parcel file descriptor wrapped around the socket's file descriptor.
*
* @return the parcel file descriptor of the socket
- * @hide
*/
@NonNull
- public ParcelFileDescriptor getParcelFileDescriptor() {
+ ParcelFileDescriptor getParcelFileDescriptor() {
return mParcelFileDescriptor;
}
diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java
index 57cf5e3..f6cae72 100644
--- a/framework/src/android/net/SocketKeepalive.java
+++ b/framework/src/android/net/SocketKeepalive.java
@@ -52,8 +52,7 @@
* request. If it does, it MUST support at least 3 concurrent keepalive slots.
*/
public abstract class SocketKeepalive implements AutoCloseable {
- /** @hide */
- protected static final String TAG = "SocketKeepalive";
+ static final String TAG = "SocketKeepalive";
/**
* Success. It indicates there is no error.
@@ -216,22 +215,15 @@
}
}
- /** @hide */
- @NonNull protected final IConnectivityManager mService;
- /** @hide */
- @NonNull protected final Network mNetwork;
- /** @hide */
- @NonNull protected final ParcelFileDescriptor mPfd;
- /** @hide */
- @NonNull protected final Executor mExecutor;
- /** @hide */
- @NonNull protected final ISocketKeepaliveCallback mCallback;
+ @NonNull final IConnectivityManager mService;
+ @NonNull final Network mNetwork;
+ @NonNull final ParcelFileDescriptor mPfd;
+ @NonNull final Executor mExecutor;
+ @NonNull final ISocketKeepaliveCallback mCallback;
// TODO: remove slot since mCallback could be used to identify which keepalive to stop.
- /** @hide */
- @Nullable protected Integer mSlot;
+ @Nullable Integer mSlot;
- /** @hide */
- public SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+ SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
@NonNull ParcelFileDescriptor pfd,
@NonNull Executor executor, @NonNull Callback callback) {
mService = service;
@@ -311,8 +303,7 @@
startImpl(intervalSec);
}
- /** @hide */
- protected abstract void startImpl(int intervalSec);
+ abstract void startImpl(int intervalSec);
/**
* Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
@@ -322,8 +313,7 @@
stopImpl();
}
- /** @hide */
- protected abstract void stopImpl();
+ abstract void stopImpl();
/**
* Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
diff --git a/framework/src/android/net/TcpSocketKeepalive.java b/framework/src/android/net/TcpSocketKeepalive.java
index 7131784..d89814d 100644
--- a/framework/src/android/net/TcpSocketKeepalive.java
+++ b/framework/src/android/net/TcpSocketKeepalive.java
@@ -24,9 +24,9 @@
import java.util.concurrent.Executor;
/** @hide */
-public final class TcpSocketKeepalive extends SocketKeepalive {
+final class TcpSocketKeepalive extends SocketKeepalive {
- public TcpSocketKeepalive(@NonNull IConnectivityManager service,
+ TcpSocketKeepalive(@NonNull IConnectivityManager service,
@NonNull Network network,
@NonNull ParcelFileDescriptor pfd,
@NonNull Executor executor,
@@ -50,7 +50,7 @@
* acknowledgement.
*/
@Override
- protected void startImpl(int intervalSec) {
+ void startImpl(int intervalSec) {
mExecutor.execute(() -> {
try {
mService.startTcpKeepalive(mNetwork, mPfd, intervalSec, mCallback);
@@ -62,7 +62,7 @@
}
@Override
- protected void stopImpl() {
+ void stopImpl() {
mExecutor.execute(() -> {
try {
if (mSlot != null) {
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index ea57bac..6def44f 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -513,7 +513,7 @@
break;
}
- String name = fullName.substring(0, index);
+ String name = unescape(fullName.substring(0, index));
String rest = fullName.substring(index);
String type = rest.replace(".local.", "");
@@ -590,6 +590,35 @@
}
}
+ // The full service name is escaped from standard DNS rules on mdnsresponder, making it suitable
+ // for passing to standard system DNS APIs such as res_query() . Thus, make the service name
+ // unescape for getting right service address. See "Notes on DNS Name Escaping" on
+ // external/mdnsresponder/mDNSShared/dns_sd.h for more details.
+ private String unescape(String s) {
+ StringBuilder sb = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); ++i) {
+ char c = s.charAt(i);
+ if (c == '\\') {
+ if (++i >= s.length()) {
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = s.charAt(i);
+ if (c != '.' && c != '\\') {
+ if (i + 2 >= s.length()) {
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = (char) ((c - '0') * 100 + (s.charAt(i + 1) - '0') * 10
+ + (s.charAt(i + 2) - '0'));
+ i += 2;
+ }
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
@VisibleForTesting
NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
mCleanupDelayMs = cleanupDelayMs;
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
index 6b623f4..6006539 100644
--- a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
+++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
@@ -16,23 +16,37 @@
package com.android.server.ethernet;
+import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+
import android.annotation.Nullable;
+import android.content.ApexEnvironment;
import android.net.IpConfiguration;
import android.os.Environment;
import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.net.IpConfigStore;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
/**
* This class provides an API to store and manage Ethernet network configuration.
*/
public class EthernetConfigStore {
- private static final String ipConfigFile = Environment.getDataDirectory() +
- "/misc/ethernet/ipconfig.txt";
+ private static final String TAG = EthernetConfigStore.class.getSimpleName();
+ private static final String CONFIG_FILE = "ipconfig.txt";
+ private static final String FILE_PATH = "/misc/ethernet/";
+ private static final String LEGACY_IP_CONFIG_FILE_PATH = Environment.getDataDirectory()
+ + FILE_PATH;
+ private static final String APEX_IP_CONFIG_FILE_PATH = ApexEnvironment.getApexEnvironment(
+ TETHERING_MODULE_NAME).getDeviceProtectedDataDir() + FILE_PATH;
private IpConfigStore mStore = new IpConfigStore();
- private ArrayMap<String, IpConfiguration> mIpConfigurations;
+ private final ArrayMap<String, IpConfiguration> mIpConfigurations;
private IpConfiguration mIpConfigurationForDefaultInterface;
private final Object mSync = new Object();
@@ -40,22 +54,70 @@
mIpConfigurations = new ArrayMap<>(0);
}
- public void read() {
- synchronized (mSync) {
- ArrayMap<String, IpConfiguration> configs =
- IpConfigStore.readIpConfigurations(ipConfigFile);
+ private static boolean doesConfigFileExist(final String filepath) {
+ return new File(filepath).exists();
+ }
- // This configuration may exist in old file versions when there was only a single active
- // Ethernet interface.
- if (configs.containsKey("0")) {
- mIpConfigurationForDefaultInterface = configs.remove("0");
+ private void writeLegacyIpConfigToApexPath(final String newFilePath, final String oldFilePath,
+ final String filename) {
+ final File directory = new File(newFilePath);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ // Write the legacy IP config to the apex file path.
+ FileOutputStream fos = null;
+ final AtomicFile dst = new AtomicFile(new File(newFilePath + filename));
+ final AtomicFile src = new AtomicFile(new File(oldFilePath + filename));
+ try {
+ final byte[] raw = src.readFully();
+ if (raw.length > 0) {
+ fos = dst.startWrite();
+ fos.write(raw);
+ fos.flush();
+ dst.finishWrite(fos);
}
-
- mIpConfigurations = configs;
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to sync the legacy IP config to the apex file path.");
+ dst.failWrite(fos);
}
}
+ public void read() {
+ read(APEX_IP_CONFIG_FILE_PATH, LEGACY_IP_CONFIG_FILE_PATH, CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void read(final String newFilePath, final String oldFilePath, final String filename) {
+ synchronized (mSync) {
+ // Attempt to read the IP configuration from apex file path first.
+ if (doesConfigFileExist(newFilePath + filename)) {
+ loadConfigFileLocked(newFilePath + filename);
+ return;
+ }
+
+ // If the config file doesn't exist in the apex file path, attempt to read it from
+ // the legacy file path, if config file exists, write the legacy IP configuration to
+ // apex config file path, this should just happen on the first boot. New or updated
+ // config entries are only written to the apex config file later.
+ if (!doesConfigFileExist(oldFilePath + filename)) return;
+ loadConfigFileLocked(oldFilePath + filename);
+ writeLegacyIpConfigToApexPath(newFilePath, oldFilePath, filename);
+ }
+ }
+
+ private void loadConfigFileLocked(final String filepath) {
+ final ArrayMap<String, IpConfiguration> configs =
+ IpConfigStore.readIpConfigurations(filepath);
+ mIpConfigurations.putAll(configs);
+ }
+
public void write(String iface, IpConfiguration config) {
+ write(iface, config, APEX_IP_CONFIG_FILE_PATH + CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void write(String iface, IpConfiguration config, String filepath) {
boolean modified;
synchronized (mSync) {
@@ -67,7 +129,7 @@
}
if (modified) {
- mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
+ mStore.writeIpConfigurations(filepath, mIpConfigurations);
}
}
}
@@ -80,9 +142,6 @@
@Nullable
public IpConfiguration getIpConfigurationForDefaultInterface() {
- synchronized (mSync) {
- return mIpConfigurationForDefaultInterface == null
- ? null : new IpConfiguration(mIpConfigurationForDefaultInterface);
- }
+ return null;
}
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 4ac6174..709b774 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -587,14 +587,18 @@
}
}
- private class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
+ @VisibleForTesting
+ class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
@Override
public void onInterfaceLinkStateChanged(String iface, boolean up) {
if (DBG) {
Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
}
- mHandler.post(() -> updateInterfaceState(iface, up));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ updateInterfaceState(iface, up);
+ });
}
@Override
@@ -602,7 +606,10 @@
if (DBG) {
Log.i(TAG, "onInterfaceAdded, iface: " + iface);
}
- mHandler.post(() -> maybeTrackInterface(iface));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ maybeTrackInterface(iface);
+ });
}
@Override
@@ -610,7 +617,10 @@
if (DBG) {
Log.i(TAG, "onInterfaceRemoved, iface: " + iface);
}
- mHandler.post(() -> stopTrackingInterface(iface));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ stopTrackingInterface(iface);
+ });
}
}
@@ -889,6 +899,8 @@
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
postAndWaitForRunnable(() -> {
pw.println(getClass().getSimpleName());
+ pw.println("Ethernet State: "
+ + (mEthernetState == ETHERNET_STATE_ENABLED ? "enabled" : "disabled"));
pw.println("Ethernet interface name filter: " + mIfaceMatch);
pw.println("Default interface: " + mDefaultInterface);
pw.println("Default interface mode: " + mDefaultInterfaceMode);
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
index 5011dec..3b44d81 100644
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -38,7 +38,7 @@
private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
// This is current path but may be changed soon.
private static final String IFACE_INDEX_NAME_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_iface_index_name_map";
+ "/sys/fs/bpf/netd_shared/map_netd_iface_index_name_map";
private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
private final INetd mNetd;
private final Handler mHandler;
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
index 6f070d7..d73e342 100644
--- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -21,6 +21,7 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+import android.annotation.NonNull;
import android.net.NetworkIdentitySet;
import android.net.NetworkStats;
import android.net.NetworkStats.NonMonotonicObserver;
@@ -68,7 +69,7 @@
private static final String TAG_NETSTATS_DUMP = "netstats_dump";
- /** Dump before deleting in {@link #recoverFromWtf()}. */
+ /** Dump before deleting in {@link #recoverAndDeleteData()}. */
private static final boolean DUMP_BEFORE_DELETE = true;
private final FileRotator mRotator;
@@ -156,6 +157,15 @@
return mSinceBoot;
}
+ public long getBucketDuration() {
+ return mBucketDuration;
+ }
+
+ @NonNull
+ public String getCookie() {
+ return mCookie;
+ }
+
/**
* Load complete history represented by {@link FileRotator}. Caches
* internally as a {@link WeakReference}, and updated with future
@@ -189,10 +199,10 @@
res.recordCollection(mPending);
} catch (IOException e) {
Log.wtf(TAG, "problem completely reading network stats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
} catch (OutOfMemoryError e) {
Log.wtf(TAG, "problem completely reading network stats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
}
return res;
}
@@ -300,10 +310,10 @@
mPending.reset();
} catch (IOException e) {
Log.wtf(TAG, "problem persisting pending stats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
} catch (OutOfMemoryError e) {
Log.wtf(TAG, "problem persisting pending stats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
}
}
}
@@ -319,10 +329,10 @@
mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
} catch (IOException e) {
Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
- recoverFromWtf();
+ recoverAndDeleteData();
} catch (OutOfMemoryError e) {
Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
- recoverFromWtf();
+ recoverAndDeleteData();
}
}
@@ -347,8 +357,7 @@
/**
* Rewriter that will combine current {@link NetworkStatsCollection} values
- * with anything read from disk, and write combined set to disk. Clears the
- * original {@link NetworkStatsCollection} when finished writing.
+ * with anything read from disk, and write combined set to disk.
*/
private static class CombiningRewriter implements FileRotator.Rewriter {
private final NetworkStatsCollection mCollection;
@@ -375,7 +384,6 @@
@Override
public void write(OutputStream out) throws IOException {
mCollection.write(out);
- mCollection.reset();
}
}
@@ -456,6 +464,23 @@
}
/**
+ * Import a specified {@link NetworkStatsCollection} instance into this recorder,
+ * and write it into a standalone file.
+ * @param collection The target {@link NetworkStatsCollection} instance to be imported.
+ */
+ public void importCollectionLocked(@NonNull NetworkStatsCollection collection)
+ throws IOException {
+ if (mRotator != null) {
+ mRotator.rewriteSingle(new CombiningRewriter(collection), collection.getStartMillis(),
+ collection.getEndMillis());
+ }
+
+ if (mComplete != null) {
+ throw new IllegalStateException("cannot import data when data already loaded");
+ }
+ }
+
+ /**
* Rewriter that will remove any histories or persisted data points before the
* specified cutoff time, only writing data back when modified.
*/
@@ -501,10 +526,10 @@
mBucketDuration, cutoffMillis));
} catch (IOException e) {
Log.wtf(TAG, "problem importing netstats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
} catch (OutOfMemoryError e) {
Log.wtf(TAG, "problem importing netstats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
}
}
@@ -555,7 +580,7 @@
* Recover from {@link FileRotator} failure by dumping state to
* {@link DropBoxManager} and deleting contents.
*/
- private void recoverFromWtf() {
+ void recoverAndDeleteData() {
if (DUMP_BEFORE_DELETE) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index b2d8b5e..63e6501 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -67,6 +67,7 @@
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.usage.NetworkStatsManager;
+import android.content.ApexEnvironment;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -75,6 +76,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
+import android.net.ConnectivityManager;
import android.net.DataUsageRequest;
import android.net.INetd;
import android.net.INetworkStatsService;
@@ -100,6 +102,7 @@
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
import android.net.netstats.IUsageCallback;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
import android.net.netstats.provider.INetworkStatsProvider;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.netstats.provider.NetworkStatsProvider;
@@ -118,6 +121,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.NetworkInterfaceProto;
@@ -143,6 +147,7 @@
import com.android.net.module.util.BinderUtils;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.NetworkStatsUtils;
@@ -155,7 +160,9 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.file.Path;
import java.time.Clock;
+import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -222,15 +229,31 @@
"netstats_combine_subtype_enabled";
private static final String UID_COUNTERSET_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_uid_counterset_map";
+ "/sys/fs/bpf/netd_shared/map_netd_uid_counterset_map";
private static final String COOKIE_TAG_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_cookie_tag_map";
+ "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map";
private static final String APP_UID_STATS_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_app_uid_stats_map";
+ "/sys/fs/bpf/netd_shared/map_netd_app_uid_stats_map";
private static final String STATS_MAP_A_PATH =
- "/sys/fs/bpf/net_shared/map_netd_stats_map_A";
+ "/sys/fs/bpf/netd_shared/map_netd_stats_map_A";
private static final String STATS_MAP_B_PATH =
- "/sys/fs/bpf/net_shared/map_netd_stats_map_B";
+ "/sys/fs/bpf/netd_shared/map_netd_stats_map_B";
+
+ /**
+ * DeviceConfig flag used to indicate whether the files should be stored in the apex data
+ * directory.
+ */
+ static final String NETSTATS_STORE_FILES_IN_APEXDATA = "netstats_store_files_in_apexdata";
+ /**
+ * DeviceConfig flag is used to indicate whether the legacy files need to be imported, and
+ * retry count before giving up. Only valid when {@link #NETSTATS_STORE_FILES_IN_APEXDATA}
+ * set to true. Note that the value gets rollback when the mainline module gets rollback.
+ */
+ static final String NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS =
+ "netstats_import_legacy_target_attempts";
+ static final int DEFAULT_NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS = 1;
+ static final String NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME = "import.attempts";
+ static final String NETSTATS_IMPORT_SUCCESS_COUNTER_NAME = "import.successes";
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@@ -239,8 +262,7 @@
private final NetworkStatsSettings mSettings;
private final NetworkStatsObservers mStatsObservers;
- private final File mSystemDir;
- private final File mBaseDir;
+ private final File mStatsDir;
private final PowerManager.WakeLock mWakeLock;
@@ -250,6 +272,12 @@
protected INetd mNetd;
private final AlertObserver mAlertObserver = new AlertObserver();
+ // Persistent counters that backed by AtomicFile which stored in the data directory as a file,
+ // to track attempts/successes count across reboot. Note that these counter values will be
+ // rollback as the module rollbacks.
+ private PersistentInt mImportLegacyAttemptsCounter = null;
+ private PersistentInt mImportLegacySuccessesCounter = null;
+
@VisibleForTesting
public static final String ACTION_NETWORK_STATS_POLL =
"com.android.server.action.NETWORK_STATS_POLL";
@@ -405,16 +433,6 @@
@NonNull
private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
- private static @NonNull File getDefaultSystemDir() {
- return new File(Environment.getDataDirectory(), "system");
- }
-
- private static @NonNull File getDefaultBaseDir() {
- File baseDir = new File(getDefaultSystemDir(), "netstats");
- baseDir.mkdirs();
- return baseDir;
- }
-
private static @NonNull Clock getDefaultClock() {
return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
Clock.systemUTC());
@@ -506,8 +524,7 @@
INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
alarmManager, wakeLock, getDefaultClock(),
new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
- new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
- new Dependencies());
+ new NetworkStatsObservers(), new Dependencies());
return service;
}
@@ -517,8 +534,8 @@
@VisibleForTesting
NetworkStatsService(Context context, INetd netd, AlarmManager alarmManager,
PowerManager.WakeLock wakeLock, Clock clock, NetworkStatsSettings settings,
- NetworkStatsFactory factory, NetworkStatsObservers statsObservers, File systemDir,
- File baseDir, @NonNull Dependencies deps) {
+ NetworkStatsFactory factory, NetworkStatsObservers statsObservers,
+ @NonNull Dependencies deps) {
mContext = Objects.requireNonNull(context, "missing Context");
mNetd = Objects.requireNonNull(netd, "missing Netd");
mAlarmManager = Objects.requireNonNull(alarmManager, "missing AlarmManager");
@@ -527,9 +544,11 @@
mWakeLock = Objects.requireNonNull(wakeLock, "missing WakeLock");
mStatsFactory = Objects.requireNonNull(factory, "missing factory");
mStatsObservers = Objects.requireNonNull(statsObservers, "missing NetworkStatsObservers");
- mSystemDir = Objects.requireNonNull(systemDir, "missing systemDir");
- mBaseDir = Objects.requireNonNull(baseDir, "missing baseDir");
mDeps = Objects.requireNonNull(deps, "missing Dependencies");
+ mStatsDir = mDeps.getOrCreateStatsDir();
+ if (!mStatsDir.exists()) {
+ throw new IllegalStateException("Persist data directory does not exist: " + mStatsDir);
+ }
final HandlerThread handlerThread = mDeps.makeHandlerThread();
handlerThread.start();
@@ -556,6 +575,87 @@
@VisibleForTesting
public static class Dependencies {
/**
+ * Get legacy platform stats directory.
+ */
+ @NonNull
+ public File getLegacyStatsDir() {
+ final File systemDataDir = new File(Environment.getDataDirectory(), "system");
+ return new File(systemDataDir, "netstats");
+ }
+
+ /**
+ * Get or create the directory that stores the persisted data usage.
+ */
+ @NonNull
+ public File getOrCreateStatsDir() {
+ final boolean storeInApexDataDir = getStoreFilesInApexData();
+
+ final File statsDataDir;
+ if (storeInApexDataDir) {
+ final File apexDataDir = ApexEnvironment
+ .getApexEnvironment(DeviceConfigUtils.TETHERING_MODULE_NAME)
+ .getDeviceProtectedDataDir();
+ statsDataDir = new File(apexDataDir, "netstats");
+
+ } else {
+ statsDataDir = getLegacyStatsDir();
+ }
+
+ if (statsDataDir.exists() || statsDataDir.mkdirs()) {
+ return statsDataDir;
+ }
+ throw new IllegalStateException("Cannot write into stats data directory: "
+ + statsDataDir);
+ }
+
+ /**
+ * Get the count of import legacy target attempts.
+ */
+ public int getImportLegacyTargetAttempts() {
+ return DeviceConfigUtils.getDeviceConfigPropertyInt(
+ DeviceConfig.NAMESPACE_TETHERING,
+ NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS,
+ DEFAULT_NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS);
+ }
+
+ /**
+ * Create the persistent counter that counts total import legacy stats attempts.
+ */
+ public PersistentInt createImportLegacyAttemptsCounter(@NonNull Path path)
+ throws IOException {
+ // TODO: Modify PersistentInt to call setStartTime every time a write is made.
+ // Create and pass a real logger here.
+ return new PersistentInt(path.toString(), null /* logger */);
+ }
+
+ /**
+ * Create the persistent counter that counts total import legacy stats successes.
+ */
+ public PersistentInt createImportLegacySuccessesCounter(@NonNull Path path)
+ throws IOException {
+ return new PersistentInt(path.toString(), null /* logger */);
+ }
+
+ /**
+ * Get the flag of storing files in the apex data directory.
+ * @return whether to store files in the apex data directory.
+ */
+ public boolean getStoreFilesInApexData() {
+ return DeviceConfigUtils.getDeviceConfigPropertyBoolean(
+ DeviceConfig.NAMESPACE_TETHERING,
+ NETSTATS_STORE_FILES_IN_APEXDATA, true);
+ }
+
+ /**
+ * Read legacy persisted network stats from disk.
+ */
+ @NonNull
+ public NetworkStatsCollection readPlatformCollection(
+ @NonNull String prefix, long bucketDuration) throws IOException {
+ return NetworkStatsDataMigrationUtils.readPlatformCollection(prefix, bucketDuration);
+ }
+
+ /**
* Create a HandlerThread to use in NetworkStatsService.
*/
@NonNull
@@ -690,14 +790,15 @@
mSystemReady = true;
// create data recorders along with historical rotators
- mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
- mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
- mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
- mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
+ mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, mStatsDir);
+ mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, mStatsDir);
+ mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, mStatsDir);
+ mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true,
+ mStatsDir);
updatePersistThresholdsLocked();
- // upgrade any legacy stats, migrating them to rotated files
+ // upgrade any legacy stats
maybeUpgradeLegacyStatsLocked();
// read historical network stats from disk, since policy service
@@ -757,11 +858,12 @@
}
private NetworkStatsRecorder buildRecorder(
- String prefix, NetworkStatsSettings.Config config, boolean includeTags) {
+ String prefix, NetworkStatsSettings.Config config, boolean includeTags,
+ File baseDir) {
final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
Context.DROPBOX_SERVICE);
return new NetworkStatsRecorder(new FileRotator(
- mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+ baseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags);
}
@@ -791,32 +893,285 @@
mSystemReady = false;
}
+ private static class MigrationInfo {
+ public final NetworkStatsRecorder recorder;
+ public NetworkStatsCollection collection;
+ public boolean imported;
+ MigrationInfo(@NonNull final NetworkStatsRecorder recorder) {
+ this.recorder = recorder;
+ collection = null;
+ imported = false;
+ }
+ }
+
@GuardedBy("mStatsLock")
private void maybeUpgradeLegacyStatsLocked() {
- File file;
- try {
- file = new File(mSystemDir, "netstats.bin");
- if (file.exists()) {
- mDevRecorder.importLegacyNetworkLocked(file);
- file.delete();
- }
-
- file = new File(mSystemDir, "netstats_xt.bin");
- if (file.exists()) {
- file.delete();
- }
-
- file = new File(mSystemDir, "netstats_uid.bin");
- if (file.exists()) {
- mUidRecorder.importLegacyUidLocked(file);
- mUidTagRecorder.importLegacyUidLocked(file);
- file.delete();
- }
- } catch (IOException e) {
- Log.wtf(TAG, "problem during legacy upgrade", e);
- } catch (OutOfMemoryError e) {
- Log.wtf(TAG, "problem during legacy upgrade", e);
+ final boolean storeFilesInApexData = mDeps.getStoreFilesInApexData();
+ if (!storeFilesInApexData) {
+ return;
}
+ try {
+ mImportLegacyAttemptsCounter = mDeps.createImportLegacyAttemptsCounter(
+ mStatsDir.toPath().resolve(NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME));
+ mImportLegacySuccessesCounter = mDeps.createImportLegacySuccessesCounter(
+ mStatsDir.toPath().resolve(NETSTATS_IMPORT_SUCCESS_COUNTER_NAME));
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to create persistent counters, skip.", e);
+ return;
+ }
+
+ final int targetAttempts = mDeps.getImportLegacyTargetAttempts();
+ final int attempts;
+ try {
+ attempts = mImportLegacyAttemptsCounter.get();
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read attempts counter, skip.", e);
+ return;
+ }
+ if (attempts >= targetAttempts) return;
+
+ Log.i(TAG, "Starting import : attempts " + attempts + "/" + targetAttempts);
+
+ final MigrationInfo[] migrations = new MigrationInfo[]{
+ new MigrationInfo(mDevRecorder), new MigrationInfo(mXtRecorder),
+ new MigrationInfo(mUidRecorder), new MigrationInfo(mUidTagRecorder)
+ };
+
+ // Legacy directories will be created by recorders if they do not exist
+ final File legacyBaseDir = mDeps.getLegacyStatsDir();
+ final NetworkStatsRecorder[] legacyRecorders = new NetworkStatsRecorder[]{
+ buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true, legacyBaseDir)
+ };
+
+ long migrationEndTime = Long.MIN_VALUE;
+ boolean endedWithFallback = false;
+ try {
+ // First, read all legacy collections. This is OEM code and it can throw. Don't
+ // commit any data to disk until all are read.
+ for (int i = 0; i < migrations.length; i++) {
+ final MigrationInfo migration = migrations[i];
+ migration.collection = readPlatformCollectionForRecorder(migration.recorder);
+
+ // Also read the collection with legacy method
+ final NetworkStatsRecorder legacyRecorder = legacyRecorders[i];
+
+ final NetworkStatsCollection legacyStats;
+ try {
+ legacyStats = legacyRecorder.getOrLoadCompleteLocked();
+ } catch (Throwable e) {
+ Log.wtf(TAG, "Failed to read stats with legacy method", e);
+ // Newer stats will be used here; that's the only thing that is usable
+ continue;
+ }
+
+ String errMsg;
+ Throwable exception = null;
+ try {
+ errMsg = compareStats(migration.collection, legacyStats);
+ } catch (Throwable e) {
+ errMsg = "Failed to compare migrated stats with all stats";
+ exception = e;
+ }
+
+ if (errMsg != null) {
+ Log.wtf(TAG, "NetworkStats import for migration " + i
+ + " returned invalid data: " + errMsg, exception);
+ // Fall back to legacy stats for this boot. The stats for old data will be
+ // re-imported again on next boot until they succeed the import. This is fine
+ // since every import clears the previous stats for the imported timespan.
+ migration.collection = legacyStats;
+ endedWithFallback = true;
+ }
+ }
+
+ // Find the latest end time.
+ for (final MigrationInfo migration : migrations) {
+ final long migrationEnd = migration.collection.getEndMillis();
+ if (migrationEnd > migrationEndTime) migrationEndTime = migrationEnd;
+ }
+
+ // Reading all collections from legacy data has succeeded. At this point it is
+ // safe to start overwriting the files on disk. The next step is to remove all
+ // data in the new location that overlaps with imported data. This ensures that
+ // any data in the new location that was created by a previous failed import is
+ // ignored. After that, write the imported data into the recorder. The code
+ // below can still possibly throw (disk error or OutOfMemory for example), but
+ // does not depend on code from non-mainline code.
+ Log.i(TAG, "Rewriting data with imported collections with cutoff "
+ + Instant.ofEpochMilli(migrationEndTime));
+ for (final MigrationInfo migration : migrations) {
+ migration.imported = true;
+ migration.recorder.removeDataBefore(migrationEndTime);
+ if (migration.collection.isEmpty()) continue;
+ migration.recorder.importCollectionLocked(migration.collection);
+ }
+
+ if (endedWithFallback) {
+ Log.wtf(TAG, "Imported platform collections with legacy fallback");
+ } else {
+ Log.i(TAG, "Successfully imported platform collections");
+ }
+ } catch (Throwable e) {
+ // The code above calls OEM code that may behave differently across devices.
+ // It can throw any exception including RuntimeExceptions and
+ // OutOfMemoryErrors. Try to recover anyway.
+ Log.wtf(TAG, "Platform data import failed. Remaining tries "
+ + (targetAttempts - attempts), e);
+
+ // Failed this time around : try again next time unless we're out of tries.
+ try {
+ mImportLegacyAttemptsCounter.set(attempts + 1);
+ } catch (IOException ex) {
+ Log.wtf(TAG, "Failed to update attempts counter.", ex);
+ }
+
+ // Try to remove any data from the failed import.
+ if (migrationEndTime > Long.MIN_VALUE) {
+ try {
+ for (final MigrationInfo migration : migrations) {
+ if (migration.imported) {
+ migration.recorder.removeDataBefore(migrationEndTime);
+ }
+ }
+ } catch (Throwable f) {
+ // If rollback still throws, there isn't much left to do. Try nuking
+ // all data, since that's the last stop. If nuking still throws, the
+ // framework will reboot, and if there are remaining tries, the migration
+ // process will retry, which is fine because it's idempotent.
+ for (final MigrationInfo migration : migrations) {
+ migration.recorder.recoverAndDeleteData();
+ }
+ }
+ }
+
+ return;
+ }
+
+ // Success ! No need to import again next time.
+ try {
+ mImportLegacyAttemptsCounter.set(targetAttempts);
+ // The successes counter is only for debugging. Hence, the synchronization
+ // between these two counters are not very critical.
+ final int successCount = mImportLegacySuccessesCounter.get();
+ mImportLegacySuccessesCounter.set(successCount + 1);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Succeed but failed to update counters.", e);
+ }
+ }
+
+ private static String str(NetworkStatsCollection.Key key) {
+ StringBuilder sb = new StringBuilder()
+ .append(key.ident.toString())
+ .append(" uid=").append(key.uid);
+ if (key.set != SET_FOREGROUND) {
+ sb.append(" set=").append(key.set);
+ }
+ if (key.tag != 0) {
+ sb.append(" tag=").append(key.tag);
+ }
+ return sb.toString();
+ }
+
+ // The importer will modify some keys when importing them.
+ // In order to keep the comparison code simple, add such special cases here and simply
+ // ignore them. This should not impact fidelity much because the start/end checks and the total
+ // bytes check still need to pass.
+ private static boolean couldKeyChangeOnImport(NetworkStatsCollection.Key key) {
+ if (key.ident.isEmpty()) return false;
+ final NetworkIdentity firstIdent = key.ident.iterator().next();
+
+ // Non-mobile network with non-empty RAT type.
+ // This combination is invalid and the NetworkIdentity.Builder will throw if it is passed
+ // in, but it looks like it was previously possible to persist it to disk. The importer sets
+ // the RAT type to NETWORK_TYPE_ALL.
+ if (firstIdent.getType() != ConnectivityManager.TYPE_MOBILE
+ && firstIdent.getRatType() != NetworkTemplate.NETWORK_TYPE_ALL) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Nullable
+ private static String compareStats(
+ NetworkStatsCollection migrated, NetworkStatsCollection legacy) {
+ final Map<NetworkStatsCollection.Key, NetworkStatsHistory> migEntries =
+ migrated.getEntries();
+ final Map<NetworkStatsCollection.Key, NetworkStatsHistory> legEntries = legacy.getEntries();
+
+ final ArraySet<NetworkStatsCollection.Key> unmatchedLegKeys =
+ new ArraySet<>(legEntries.keySet());
+
+ for (NetworkStatsCollection.Key legKey : legEntries.keySet()) {
+ final NetworkStatsHistory legHistory = legEntries.get(legKey);
+ final NetworkStatsHistory migHistory = migEntries.get(legKey);
+
+ if (migHistory == null && couldKeyChangeOnImport(legKey)) {
+ unmatchedLegKeys.remove(legKey);
+ continue;
+ }
+
+ if (migHistory == null) {
+ return "Missing migrated history for legacy key " + str(legKey)
+ + ", legacy history was " + legHistory;
+ }
+ if (!migHistory.isSameAs(legHistory)) {
+ return "Difference in history for key " + legKey + "; legacy history " + legHistory
+ + ", migrated history " + migHistory;
+ }
+ unmatchedLegKeys.remove(legKey);
+ }
+
+ if (!unmatchedLegKeys.isEmpty()) {
+ final NetworkStatsHistory first = legEntries.get(unmatchedLegKeys.valueAt(0));
+ return "Found unmatched legacy keys: count=" + unmatchedLegKeys.size()
+ + ", first unmatched collection " + first;
+ }
+
+ if (migrated.getStartMillis() != legacy.getStartMillis()
+ || migrated.getEndMillis() != legacy.getEndMillis()) {
+ return "Start / end of the collections "
+ + migrated.getStartMillis() + "/" + legacy.getStartMillis() + " and "
+ + migrated.getEndMillis() + "/" + legacy.getEndMillis()
+ + " don't match";
+ }
+
+ if (migrated.getTotalBytes() != legacy.getTotalBytes()) {
+ return "Total bytes " + migrated.getTotalBytes() + " and " + legacy.getTotalBytes()
+ + " don't match for collections with start/end "
+ + migrated.getStartMillis()
+ + "/" + legacy.getStartMillis();
+ }
+
+ return null;
+ }
+
+ @GuardedBy("mStatsLock")
+ @NonNull
+ private NetworkStatsCollection readPlatformCollectionForRecorder(
+ @NonNull final NetworkStatsRecorder rec) throws IOException {
+ final String prefix = rec.getCookie();
+ Log.i(TAG, "Importing platform collection for prefix " + prefix);
+ final NetworkStatsCollection collection = Objects.requireNonNull(
+ mDeps.readPlatformCollection(prefix, rec.getBucketDuration()),
+ "Imported platform collection for prefix " + prefix + " must not be null");
+
+ final long bootTimestamp = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+ if (!collection.isEmpty() && bootTimestamp < collection.getStartMillis()) {
+ throw new IllegalArgumentException("Platform collection for prefix " + prefix
+ + " contains data that could not possibly come from the previous boot "
+ + "(start timestamp = " + Instant.ofEpochMilli(collection.getStartMillis())
+ + ", last booted at " + Instant.ofEpochMilli(bootTimestamp));
+ }
+
+ Log.i(TAG, "Successfully read platform collection spanning from "
+ // Instant uses ISO-8601 for toString()
+ + Instant.ofEpochMilli(collection.getStartMillis()).toString() + " to "
+ + Instant.ofEpochMilli(collection.getEndMillis()).toString());
+ return collection;
}
/**
@@ -2102,10 +2457,32 @@
return;
}
+ pw.println("Directory:");
+ pw.increaseIndent();
+ pw.println(mStatsDir);
+ pw.decreaseIndent();
+
pw.println("Configs:");
pw.increaseIndent();
pw.print(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled());
pw.println();
+ pw.print(NETSTATS_STORE_FILES_IN_APEXDATA, mDeps.getStoreFilesInApexData());
+ pw.println();
+ pw.print(NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS, mDeps.getImportLegacyTargetAttempts());
+ pw.println();
+ if (mDeps.getStoreFilesInApexData()) {
+ try {
+ pw.print("platform legacy stats import attempts count",
+ mImportLegacyAttemptsCounter.get());
+ pw.println();
+ pw.print("platform legacy stats import successes count",
+ mImportLegacySuccessesCounter.get());
+ pw.println();
+ } catch (IOException e) {
+ pw.println("(failed to dump platform legacy stats import counters)");
+ }
+ }
+
pw.decreaseIndent();
pw.println("Active interfaces:");
diff --git a/service-t/src/com/android/server/net/PersistentInt.java b/service-t/src/com/android/server/net/PersistentInt.java
new file mode 100644
index 0000000..c212b77
--- /dev/null
+++ b/service-t/src/com/android/server/net/PersistentInt.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 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.server.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.AtomicFile;
+import android.util.SystemConfigFileCommitEventLogger;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * A simple integer backed by an on-disk {@link AtomicFile}. Not thread-safe.
+ */
+public class PersistentInt {
+ private final String mPath;
+ private final AtomicFile mFile;
+
+ /**
+ * Constructs a new {@code PersistentInt}. The counter is set to 0 if the file does not exist.
+ * Before returning, the constructor checks that the file is readable and writable. This
+ * indicates that in the future {@link #get} and {@link #set} are likely to succeed,
+ * though other events (data corruption, other code deleting the file, etc.) may cause these
+ * calls to fail in the future.
+ *
+ * @param path the path of the file to use.
+ * @param logger the logger
+ * @throws IOException the counter could not be read or written
+ */
+ public PersistentInt(@NonNull String path, @Nullable SystemConfigFileCommitEventLogger logger)
+ throws IOException {
+ mPath = path;
+ mFile = new AtomicFile(new File(path), logger);
+ checkReadWrite();
+ }
+
+ private void checkReadWrite() throws IOException {
+ int value;
+ try {
+ value = get();
+ } catch (FileNotFoundException e) {
+ // Counter does not exist. Attempt to initialize to 0.
+ // Note that we cannot tell here if the file does not exist or if opening it failed,
+ // because in Java both of those throw FileNotFoundException.
+ value = 0;
+ }
+ set(value);
+ get();
+ // No exceptions? Good.
+ }
+
+ /**
+ * Gets the current value.
+ *
+ * @return the current value of the counter.
+ * @throws IOException if reading the value failed.
+ */
+ public int get() throws IOException {
+ try (FileInputStream fin = mFile.openRead();
+ DataInputStream din = new DataInputStream(fin)) {
+ return din.readInt();
+ }
+ }
+
+ /**
+ * Sets the current value.
+ * @param value the value to set
+ * @throws IOException if writing the value failed.
+ */
+ public void set(int value) throws IOException {
+ FileOutputStream fout = null;
+ try {
+ fout = mFile.startWrite();
+ DataOutputStream dout = new DataOutputStream(fout);
+ dout.writeInt(value);
+ mFile.finishWrite(fout);
+ } catch (IOException e) {
+ if (fout != null) {
+ mFile.failWrite(fout);
+ }
+ throw e;
+ }
+ }
+
+ public String getPath() {
+ return mPath;
+ }
+}
diff --git a/service/Android.bp b/service/Android.bp
index 0393c79..45e43bc 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -198,11 +198,10 @@
lint: { strict_updatability_linting: true },
}
-java_library {
- name: "service-connectivity",
+java_defaults {
+ name: "service-connectivity-defaults",
sdk_version: "system_server_current",
min_sdk_version: "30",
- installable: true,
// This library combines system server jars that have access to different bootclasspath jars.
// Lower SDK service jars must not depend on higher SDK jars as that would let them
// transitively depend on the wrong bootclasspath jars. Sources also cannot be added here as
@@ -224,15 +223,27 @@
lint: { strict_updatability_linting: true },
}
-genrule {
+// A special library created strictly for use by the tests as they need the
+// implementation library but that is not available when building from prebuilts.
+// Using a library with a different name to what is used by the prebuilts ensures
+// that this will never depend on the prebuilt.
+// Switching service-connectivity to a java_sdk_library would also have worked as
+// that has built in support for managing this but that is too big a change at this
+// point.
+java_library {
+ name: "service-connectivity-for-tests",
+ defaults: ["service-connectivity-defaults"],
+}
+
+java_library {
+ name: "service-connectivity",
+ defaults: ["service-connectivity-defaults"],
+ installable: true,
+}
+
+filegroup {
name: "connectivity-jarjar-rules",
- defaults: ["jarjar-rules-combine-defaults"],
- srcs: [
- ":framework-connectivity-jarjar-rules",
- ":service-connectivity-jarjar-gen",
- ":service-nearby-jarjar-gen",
- ],
- out: ["connectivity-jarjar-rules.txt"],
+ srcs: ["jarjar-rules.txt"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
@@ -243,45 +254,3 @@
srcs: ["src/com/android/server/BpfNetMaps.java"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
-
-java_genrule {
- name: "service-connectivity-jarjar-gen",
- tool_files: [
- ":service-connectivity-pre-jarjar",
- ":service-connectivity-tiramisu-pre-jarjar",
- "jarjar-excludes.txt",
- ],
- tools: [
- "jarjar-rules-generator",
- "dexdump",
- ],
- out: ["service_connectivity_jarjar_rules.txt"],
- cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :service-connectivity-pre-jarjar) " +
- "$(location :service-connectivity-tiramisu-pre-jarjar) " +
- "--prefix android.net.connectivity " +
- "--excludes $(location jarjar-excludes.txt) " +
- "--dexdump $(location dexdump) " +
- "--output $(out)",
- visibility: ["//visibility:private"],
-}
-
-java_genrule {
- name: "service-nearby-jarjar-gen",
- tool_files: [
- ":service-nearby-pre-jarjar",
- "jarjar-excludes.txt",
- ],
- tools: [
- "jarjar-rules-generator",
- "dexdump",
- ],
- out: ["service_nearby_jarjar_rules.txt"],
- cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :service-nearby-pre-jarjar) " +
- "--prefix com.android.server.nearby " +
- "--excludes $(location jarjar-excludes.txt) " +
- "--dexdump $(location dexdump) " +
- "--output $(out)",
- visibility: ["//visibility:private"],
-}
diff --git a/service/jarjar-excludes.txt b/service/jarjar-excludes.txt
deleted file mode 100644
index b0d6763..0000000
--- a/service/jarjar-excludes.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-# Classes loaded by SystemServer via their hardcoded name, so they can't be jarjared
-com\.android\.server\.ConnectivityServiceInitializer(\$.+)?
-com\.android\.server\.NetworkStatsServiceInitializer(\$.+)?
-
-# Do not jarjar com.android.server, as several unit tests fail because they lose
-# package-private visibility between jarjared and non-jarjared classes.
-# TODO: fix the tests and also jarjar com.android.server, or at least only exclude a package that
-# is specific to the module like com.android.server.connectivity
-com\.android\.server\..+
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
new file mode 100644
index 0000000..c7223fc
--- /dev/null
+++ b/service/jarjar-rules.txt
@@ -0,0 +1,123 @@
+# Classes in framework-connectivity are restricted to the android.net package.
+# This cannot be changed because it is harcoded in ART in S.
+# Any missing jarjar rule for framework-connectivity would be caught by the
+# build as an unexpected class outside of the android.net package.
+rule com.android.net.module.util.** android.net.connectivity.@0
+rule com.android.modules.utils.** android.net.connectivity.@0
+rule android.net.NetworkFactory* android.net.connectivity.@0
+
+# From modules-utils-preconditions
+rule com.android.internal.util.Preconditions* android.net.connectivity.@0
+
+# From framework-connectivity-shared-srcs
+rule android.util.LocalLog* android.net.connectivity.@0
+rule android.util.IndentingPrintWriter* android.net.connectivity.@0
+rule com.android.internal.util.IndentingPrintWriter* android.net.connectivity.@0
+rule com.android.internal.util.MessageUtils* android.net.connectivity.@0
+rule com.android.internal.util.WakeupMessage* android.net.connectivity.@0
+rule com.android.internal.util.FileRotator* android.net.connectivity.@0
+rule com.android.internal.util.ProcFileReader* android.net.connectivity.@0
+
+# From framework-connectivity-protos
+rule com.google.protobuf.** android.net.connectivity.@0
+rule android.service.** android.net.connectivity.@0
+
+rule android.sysprop.** com.android.connectivity.@0
+
+rule com.android.internal.messages.** com.android.connectivity.@0
+
+# From dnsresolver_aidl_interface (newer AIDLs should go to android.net.resolv.aidl)
+rule android.net.resolv.aidl.** com.android.connectivity.@0
+rule android.net.IDnsResolver* com.android.connectivity.@0
+rule android.net.ResolverHostsParcel* com.android.connectivity.@0
+rule android.net.ResolverOptionsParcel* com.android.connectivity.@0
+rule android.net.ResolverParamsParcel* com.android.connectivity.@0
+rule android.net.ResolverParamsParcel* com.android.connectivity.@0
+# Also includes netd event listener AIDL, but this is handled by netd-client rules
+
+# From netd-client (newer AIDLs should go to android.net.netd.aidl)
+rule android.net.netd.aidl.** com.android.connectivity.@0
+# Avoid including android.net.INetdEventCallback, used in tests but not part of the module
+rule android.net.INetd com.android.connectivity.@0
+rule android.net.INetd$* com.android.connectivity.@0
+rule android.net.INetdUnsolicitedEventListener* com.android.connectivity.@0
+rule android.net.InterfaceConfigurationParcel* com.android.connectivity.@0
+rule android.net.MarkMaskParcel* com.android.connectivity.@0
+rule android.net.NativeNetworkConfig* com.android.connectivity.@0
+rule android.net.NativeNetworkType* com.android.connectivity.@0
+rule android.net.NativeVpnType* com.android.connectivity.@0
+rule android.net.RouteInfoParcel* com.android.connectivity.@0
+rule android.net.TetherConfigParcel* com.android.connectivity.@0
+rule android.net.TetherOffloadRuleParcel* com.android.connectivity.@0
+rule android.net.TetherStatsParcel* com.android.connectivity.@0
+rule android.net.UidRangeParcel* com.android.connectivity.@0
+rule android.net.metrics.INetdEventListener* com.android.connectivity.@0
+
+# From netlink-client
+rule android.net.netlink.** com.android.connectivity.@0
+
+# From networkstack-client (newer AIDLs should go to android.net.[networkstack|ipmemorystore].aidl)
+rule android.net.networkstack.aidl.** com.android.connectivity.@0
+rule android.net.ipmemorystore.aidl.** com.android.connectivity.@0
+rule android.net.ipmemorystore.aidl.** com.android.connectivity.@0
+rule android.net.DataStallReportParcelable* com.android.connectivity.@0
+rule android.net.DhcpResultsParcelable* com.android.connectivity.@0
+rule android.net.IIpMemoryStore* com.android.connectivity.@0
+rule android.net.INetworkMonitor* com.android.connectivity.@0
+rule android.net.INetworkStackConnector* com.android.connectivity.@0
+rule android.net.INetworkStackStatusCallback* com.android.connectivity.@0
+rule android.net.InformationElementParcelable* com.android.connectivity.@0
+rule android.net.InitialConfigurationParcelable* com.android.connectivity.@0
+rule android.net.IpMemoryStore* com.android.connectivity.@0
+rule android.net.Layer2InformationParcelable* com.android.connectivity.@0
+rule android.net.Layer2PacketParcelable* com.android.connectivity.@0
+rule android.net.NattKeepalivePacketDataParcelable* com.android.connectivity.@0
+rule android.net.NetworkMonitorManager* com.android.connectivity.@0
+rule android.net.NetworkTestResultParcelable* com.android.connectivity.@0
+rule android.net.PrivateDnsConfigParcel* com.android.connectivity.@0
+rule android.net.ProvisioningConfigurationParcelable* com.android.connectivity.@0
+rule android.net.ScanResultInfoParcelable* com.android.connectivity.@0
+rule android.net.TcpKeepalivePacketDataParcelable* com.android.connectivity.@0
+rule android.net.dhcp.DhcpLeaseParcelable* com.android.connectivity.@0
+rule android.net.dhcp.DhcpServingParamsParcel* com.android.connectivity.@0
+rule android.net.dhcp.IDhcpEventCallbacks* com.android.connectivity.@0
+rule android.net.dhcp.IDhcpServer* com.android.connectivity.@0
+rule android.net.ip.IIpClient* com.android.connectivity.@0
+rule android.net.ip.IpClientCallbacks* com.android.connectivity.@0
+rule android.net.ip.IpClientManager* com.android.connectivity.@0
+rule android.net.ip.IpClientUtil* com.android.connectivity.@0
+rule android.net.ipmemorystore.** com.android.connectivity.@0
+rule android.net.networkstack.** com.android.connectivity.@0
+rule android.net.shared.** com.android.connectivity.@0
+rule android.net.util.KeepalivePacketDataUtil* com.android.connectivity.@0
+
+# From connectivity-module-utils
+rule android.net.util.SharedLog* com.android.connectivity.@0
+rule android.net.shared.** com.android.connectivity.@0
+
+# From services-connectivity-shared-srcs
+rule android.net.util.NetworkConstants* com.android.connectivity.@0
+
+# From modules-utils-statemachine
+rule com.android.internal.util.IState* com.android.connectivity.@0
+rule com.android.internal.util.State* com.android.connectivity.@0
+
+# From the API shims
+rule com.android.networkstack.apishim.** com.android.connectivity.@0
+
+# From filegroup framework-connectivity-protos
+rule android.service.*Proto com.android.connectivity.@0
+
+# From mdns-aidl-interface
+rule android.net.mdns.aidl.** android.net.connectivity.@0
+
+# From nearby-service, including proto
+rule service.proto.** com.android.server.nearby.@0
+rule androidx.annotation.Keep* com.android.server.nearby.@0
+rule androidx.collection.** com.android.server.nearby.@0
+rule androidx.core.** com.android.server.nearby.@0
+rule androidx.versionedparcelable.** com.android.server.nearby.@0
+rule com.google.common.** com.android.server.nearby.@0
+
+# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
+# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/proguard.flags b/service/proguard.flags
index 557ba59..94397ab 100644
--- a/service/proguard.flags
+++ b/service/proguard.flags
@@ -2,6 +2,8 @@
# TODO: instead of keeping everything, consider listing only "entry points"
# (service loader, JNI registered methods, etc) and letting the optimizer do its job
-keep class android.net.** { *; }
+-keep class com.android.connectivity.** { *; }
+-keep class com.android.net.** { *; }
-keep class !com.android.server.nearby.**,com.android.server.** { *; }
# Prevent proguard from stripping out any nearby-service and fast-pair-lite-protos fields.
@@ -13,4 +15,4 @@
# This replicates the base proguard rule used by the build by default
# (proguard_basic_keeps.flags), but needs to be specified here because the
# com.google.protobuf package is jarjared to the below package.
--keepclassmembers class * extends com.android.server.nearby.com.google.protobuf.MessageLite { <fields>; }
+-keepclassmembers class * extends com.android.connectivity.com.google.protobuf.MessageLite { <fields>; }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index fb0a48d..b535fa9 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -748,7 +748,7 @@
* The BPF program attached to the tc-police hook to account for to-be-dropped traffic.
*/
private static final String TC_POLICE_BPF_PROG_PATH =
- "/sys/fs/bpf/net_shared/prog_netd_schedact_ingress_account";
+ "/sys/fs/bpf/netd_shared/prog_netd_schedact_ingress_account";
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
@@ -7831,6 +7831,7 @@
}
nai.declaredCapabilities = new NetworkCapabilities(nc);
NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE),
mCarrierPrivilegeAuthenticator);
}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 323888a..b40b6e0 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -19,6 +19,7 @@
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.transportNamesOf;
@@ -1224,20 +1225,22 @@
*
* @param nc the capabilities to sanitize
* @param creatorUid the UID of the process creating this network agent
+ * @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise
* @param authenticator the carrier privilege authenticator to check for telephony constraints
*/
public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
- final int creatorUid, @NonNull final CarrierPrivilegeAuthenticator authenticator) {
+ final int creatorUid, final boolean hasAutomotiveFeature,
+ @Nullable final CarrierPrivilegeAuthenticator authenticator) {
if (nc.hasTransport(TRANSPORT_TEST)) {
nc.restrictCapabilitiesForTestNetwork(creatorUid);
}
- if (!areAllowedUidsAcceptableFromNetworkAgent(nc, authenticator)) {
+ if (!areAllowedUidsAcceptableFromNetworkAgent(nc, hasAutomotiveFeature, authenticator)) {
nc.setAllowedUids(new ArraySet<>());
}
}
private static boolean areAllowedUidsAcceptableFromNetworkAgent(
- @NonNull final NetworkCapabilities nc,
+ @NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
@Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
// NCs without access UIDs are fine.
if (!nc.hasAllowedUids()) return true;
@@ -1252,6 +1255,11 @@
// access UIDs
if (nc.hasTransport(TRANSPORT_TEST)) return true;
+ // Factories that make ethernet networks can allow UIDs for automotive devices.
+ if (nc.hasSingleTransport(TRANSPORT_ETHERNET) && hasAutomotiveFeature) {
+ return true;
+ }
+
// Factories that make cell networks can allow the UID for the carrier service package.
// This can only work in T where there is support for CarrierPrivilegeAuthenticator
if (null != carrierPrivilegeAuthenticator
@@ -1262,8 +1270,6 @@
return true;
}
- // TODO : accept Railway callers
-
return false;
}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index efea0f9..509e881 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -23,7 +23,7 @@
java_library {
name: "FrameworksNetCommonTests",
- defaults: ["framework-connectivity-internal-test-defaults"],
+ defaults: ["framework-connectivity-test-defaults"],
srcs: [
"java/**/*.java",
"java/**/*.kt",
@@ -49,7 +49,6 @@
// jarjar stops at the first matching rule, so order of concatenation affects the output.
genrule {
name: "ConnectivityCoverageJarJarRules",
- defaults: ["jarjar-rules-combine-defaults"],
srcs: [
"tethering-jni-jarjar-rules.txt",
":connectivity-jarjar-rules",
@@ -57,6 +56,8 @@
":NetworkStackJarJarRules",
],
out: ["jarjar-rules-connectivity-coverage.txt"],
+ // Concat files with a line break in the middle
+ cmd: "for src in $(in); do cat $${src}; echo; done > $(out)",
visibility: ["//visibility:private"],
}
@@ -83,7 +84,7 @@
target_sdk_version: "31",
test_suites: ["general-tests", "mts-tethering"],
defaults: [
- "framework-connectivity-internal-test-defaults",
+ "framework-connectivity-test-defaults",
"FrameworksNetTests-jni-defaults",
"libnetworkstackutilsjni_deps",
],
@@ -139,30 +140,6 @@
],
}
-// defaults for tests that need to build against framework-connectivity's @hide APIs, but also
-// using fully @hide classes that are jarjared (because they have no API member). Similar to
-// framework-connectivity-test-defaults above but uses pre-jarjar class names.
-// Only usable from targets that have visibility on framework-connectivity-pre-jarjar, and apply
-// connectivity jarjar rules so that references to jarjared classes still match: this is limited to
-// connectivity internal tests only.
-java_defaults {
- name: "framework-connectivity-internal-test-defaults",
- sdk_version: "core_platform", // tests can use @CorePlatformApi's
- libs: [
- // order matters: classes in framework-connectivity are resolved before framework,
- // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
- // stubs in framework
- "framework-connectivity-pre-jarjar",
- "framework-connectivity-t-pre-jarjar",
- "framework-tethering.impl",
- "framework",
-
- // if sdk_version="" this gets automatically included, but here we need to add manually.
- "framework-res",
- ],
- defaults_visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
-}
-
// Defaults for tests that want to run in mainline-presubmit.
// Not widely used because many of our tests have AndroidTest.xml files and
// use the mainline-param config-descriptor metadata in AndroidTest.xml.
diff --git a/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt b/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt
index c2654c5..f8e041a 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt
@@ -27,6 +27,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
@ConnectivityModuleTest
@RunWith(JUnit4::class)
@@ -51,12 +52,22 @@
.build()
statsSingle.assertEntriesEqual(entry1)
assertEquals(DateUtils.HOUR_IN_MILLIS, statsSingle.bucketDuration)
+
+ // Verify the builder throws if the timestamp of added entry is not greater than
+ // that of any previously-added entry.
+ assertFailsWith(IllegalArgumentException::class) {
+ NetworkStatsHistory
+ .Builder(DateUtils.SECOND_IN_MILLIS, /* initialCapacity */ 0)
+ .addEntry(entry1).addEntry(entry2).addEntry(entry3)
+ .build()
+ }
+
val statsMultiple = NetworkStatsHistory
.Builder(DateUtils.SECOND_IN_MILLIS, /* initialCapacity */ 0)
- .addEntry(entry1).addEntry(entry2).addEntry(entry3)
+ .addEntry(entry3).addEntry(entry1).addEntry(entry2)
.build()
assertEquals(DateUtils.SECOND_IN_MILLIS, statsMultiple.bucketDuration)
- statsMultiple.assertEntriesEqual(entry1, entry2, entry3)
+ statsMultiple.assertEntriesEqual(entry3, entry1, entry2)
}
fun NetworkStatsHistory.assertEntriesEqual(vararg entries: NetworkStatsHistory.Entry) {
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 04434e5..b12c4db 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -15,49 +15,51 @@
*/
package android.net.cts
+import android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
+import android.content.Context
import android.net.InetAddresses
import android.net.IpConfiguration
import android.net.MacAddress
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
-import android.platform.test.annotations.AppModeFull
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
-import com.android.net.module.util.ArrayTrackRecord
-import com.android.net.module.util.TrackRecord
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.SC_V2
-import com.android.testutils.runAsShell
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import android.content.Context
-import org.junit.runner.RunWith
-import kotlin.test.assertNull
-import kotlin.test.fail
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
import android.os.Handler
import android.os.HandlerExecutor
import android.os.Looper
+import android.platform.test.annotations.AppModeFull
+import android.util.ArraySet
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.net.module.util.TrackRecord
+import com.android.networkstack.apishim.EthernetManagerShimImpl
import com.android.networkstack.apishim.common.EthernetManagerShim.InterfaceStateListener
+import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
+import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_ABSENT
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_DOWN
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_UP
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
-import com.android.networkstack.apishim.EthernetManagerShimImpl
+import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.RouterAdvertisementResponder
+import com.android.testutils.SC_V2
import com.android.testutils.TapPacketReader
+import com.android.testutils.runAsShell
import com.android.testutils.waitForIdle
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
import java.net.Inet6Address
-import java.util.concurrent.Executor
-import kotlin.test.assertFalse
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
import java.net.NetworkInterface
+import java.util.concurrent.Executor
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
private const val TIMEOUT_MS = 1000L
private const val NO_CALLBACK_TIMEOUT_MS = 200L
@@ -141,10 +143,13 @@
}
fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
- expectCallback(InterfaceStateChanged(iface.interfaceName, state, role,
- if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null))
+ expectCallback(createChangeEvent(iface, state, role))
}
+ fun createChangeEvent(iface: EthernetTestInterface, state: Int, role: Int) =
+ InterfaceStateChanged(iface.interfaceName, state, role,
+ if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null)
+
fun pollForNextCallback(): CallbackEntry {
return events.poll(TIMEOUT_MS) ?: fail("Did not receive callback after ${TIMEOUT_MS}ms")
}
@@ -172,7 +177,9 @@
}
private fun addInterfaceStateListener(executor: Executor, listener: EthernetStateListener) {
- em.addInterfaceStateListener(executor, listener)
+ runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
+ em.addInterfaceStateListener(executor, listener)
+ }
addedListeners.add(listener)
}
@@ -195,28 +202,27 @@
}
@Test
- public fun testCallbacks() {
+ fun testCallbacks() {
val executor = HandlerExecutor(Handler(Looper.getMainLooper()))
// If an interface exists when the callback is registered, it is reported on registration.
val iface = createInterface()
- val listener = EthernetStateListener()
- addInterfaceStateListener(executor, listener)
- listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ val listener1 = EthernetStateListener()
+ addInterfaceStateListener(executor, listener1)
+ validateListenerOnRegistration(listener1)
// If an interface appears, existing callbacks see it.
// TODO: fix the up/up/down/up callbacks and only send down/up.
val iface2 = createInterface()
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
// Register a new listener, it should see state of all existing interfaces immediately.
val listener2 = EthernetStateListener()
addInterfaceStateListener(executor, listener2)
- listener2.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
- listener2.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ validateListenerOnRegistration(listener2)
// Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
removeInterface(iface)
@@ -233,8 +239,30 @@
}
}
+ /**
+ * Validate all interfaces are returned for an EthernetStateListener upon registration.
+ */
+ private fun validateListenerOnRegistration(listener: EthernetStateListener) {
+ // Get all tracked interfaces to validate on listener registration. Ordering and interface
+ // state (up/down) can't be validated for interfaces not created as part of testing.
+ val ifaces = em.getInterfaceList()
+ val polledIfaces = ArraySet<String>()
+ for (i in ifaces) {
+ val event = (listener.pollForNextCallback() as InterfaceStateChanged)
+ val iface = event.iface
+ assertTrue(polledIfaces.add(iface), "Duplicate interface $iface returned")
+ assertTrue(ifaces.contains(iface), "Untracked interface $iface returned")
+ // If the event's iface was created in the test, additional criteria can be validated.
+ createdIfaces.find { it.interfaceName.equals(iface) }?.let {
+ assertEquals(event, listener.createChangeEvent(it, STATE_LINK_UP, ROLE_CLIENT))
+ }
+ }
+ // Assert all callbacks are accounted for.
+ listener.assertNoCallback()
+ }
+
@Test
- public fun testGetInterfaceList() {
+ fun testGetInterfaceList() {
setIncludeTestInterfaces(true)
// Create two test interfaces and check the return list contains the interface names.
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 7b0451f..33a0a83 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -52,10 +52,7 @@
import androidx.test.runner.AndroidJUnit4
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
-import com.android.networkstack.apishim.ConstantsShim
import com.android.networkstack.apishim.NsdShimImpl
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.SC_V2
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
@@ -65,7 +62,6 @@
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.net.ServerSocket
@@ -90,10 +86,6 @@
@AppModeFull(reason = "Socket cannot bind in instant app mode")
@RunWith(AndroidJUnit4::class)
class NsdManagerTest {
- // NsdManager is not updatable before S, so tests do not need to be backwards compatible
- @get:Rule
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
-
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
@@ -255,9 +247,11 @@
fun setUp() {
handlerThread.start()
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1 = createTestNetwork()
- testNetwork2 = createTestNetwork()
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1 = createTestNetwork()
+ testNetwork2 = createTestNetwork()
+ }
}
}
@@ -296,9 +290,11 @@
@After
fun tearDown() {
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1.close(cm)
- testNetwork2.close(cm)
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1.close(cm)
+ testNetwork2.close(cm)
+ }
}
handlerThread.quitSafely()
}
@@ -399,14 +395,17 @@
si2.serviceName = serviceName
si2.port = localPort
val registrationRecord2 = NsdRegistrationRecord()
- val registeredInfo2 = registerService(registrationRecord2, si2)
+ nsdManager.registerService(si2, NsdManager.PROTOCOL_DNS_SD, registrationRecord2)
+ val registeredInfo2 = registrationRecord2.expectCallback<ServiceRegistered>().serviceInfo
// Expect a service record to be discovered (and filter the ones
// that are unrelated to this test)
val foundInfo2 = discoveryRecord.waitForServiceDiscovered(registeredInfo2.serviceName)
// Resolve the service
- val resolvedService2 = resolveService(foundInfo2)
+ val resolveRecord2 = NsdResolveRecord()
+ nsdManager.resolveService(foundInfo2, resolveRecord2)
+ val resolvedService2 = resolveRecord2.expectCallback<ServiceResolved>().serviceInfo
// Check that the resolved service doesn't have any TXT records
assertEquals(0, resolvedService2.attributes.size)
@@ -422,7 +421,7 @@
@Test
fun testNsdManager_DiscoverOnNetwork() {
// This test requires shims supporting T+ APIs (discovering on specific network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -456,7 +455,7 @@
@Test
fun testNsdManager_DiscoverWithNetworkRequest() {
// This test requires shims supporting T+ APIs (discovering on network request)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -521,7 +520,7 @@
@Test
fun testNsdManager_ResolveOnNetwork() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -565,7 +564,7 @@
@Test
fun testNsdManager_RegisterOnNetwork() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -611,6 +610,41 @@
}
}
+ @Test
+ fun testNsdManager_RegisterServiceNameWithNonStandardCharacters() {
+ val serviceNames = "^Nsd.Test|Non-#AsCiI\\Characters&\\ufffe テスト 測試"
+ val si = NsdServiceInfo().apply {
+ serviceType = SERVICE_TYPE
+ serviceName = serviceNames
+ port = 12345 // Test won't try to connect so port does not matter
+ }
+
+ // Register the service name which contains non-standard characters.
+ val registrationRecord = NsdRegistrationRecord()
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
+ registrationRecord.expectCallback<ServiceRegistered>()
+
+ tryTest {
+ // Discover that service name.
+ val discoveryRecord = NsdDiscoveryRecord()
+ nsdManager.discoverServices(
+ SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord
+ )
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(serviceNames)
+
+ // Expect that resolving the service name works properly even service name contains
+ // non-standard characters.
+ val resolveRecord = NsdResolveRecord()
+ nsdManager.resolveService(foundInfo, resolveRecord)
+ val resolvedCb = resolveRecord.expectCallback<ServiceResolved>()
+ assertEquals(foundInfo.serviceName, resolvedCb.serviceInfo.serviceName)
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ } cleanup {
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
/**
* Register a service and return its registration record.
*/
diff --git a/tests/cts/net/src/android/net/cts/UriTest.java b/tests/cts/net/src/android/net/cts/UriTest.java
index 40b8fb7..741947b 100644
--- a/tests/cts/net/src/android/net/cts/UriTest.java
+++ b/tests/cts/net/src/android/net/cts/UriTest.java
@@ -20,6 +20,9 @@
import android.net.Uri;
import android.os.Parcel;
import android.test.AndroidTestCase;
+
+import com.android.modules.utils.build.SdkLevel;
+
import java.io.File;
import java.util.Arrays;
import java.util.ArrayList;
@@ -577,11 +580,21 @@
"rtsp://username:password@rtsp.android.com:2121/");
}
- public void testToSafeString_notSupport() {
- checkToSafeString("unsupported://ajkakjah/askdha/secret?secret",
- "unsupported://ajkakjah/askdha/secret?secret");
- checkToSafeString("unsupported:ajkakjah/askdha/secret?secret",
- "unsupported:ajkakjah/askdha/secret?secret");
+ public void testToSafeString_customUri() {
+ if (SdkLevel.isAtLeastT()) {
+ checkToSafeString("other://ajkakjah/...",
+ "other://ajkakjah/askdha/secret?secret");
+ checkToSafeString("unsupported:", "unsupported:foo//bar");
+ checkToSafeString("other://host:80/...", "other://user@host:80/secret/path/");
+ checkToSafeString("content://contacts/...",
+ "content://contacts/secret/path/name@foo.com");
+ checkToSafeString("file:///...", "file:///path/to/secret.doc");
+ } else {
+ checkToSafeString("unsupported://ajkakjah/askdha/secret?secret",
+ "unsupported://ajkakjah/askdha/secret?secret");
+ checkToSafeString("unsupported:ajkakjah/askdha/secret?secret",
+ "unsupported:ajkakjah/askdha/secret?secret");
+ }
}
private void checkToSafeString(String expectedSafeString, String original) {
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index e3d80a0..b3684ac 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -21,7 +21,7 @@
android_test {
name: "FrameworksNetIntegrationTests",
- defaults: ["framework-connectivity-internal-test-defaults"],
+ defaults: ["framework-connectivity-test-defaults"],
platform_apis: true,
certificate: "platform",
srcs: [
@@ -71,12 +71,8 @@
"net-tests-utils",
],
libs: [
- "service-connectivity-pre-jarjar",
+ "service-connectivity-for-tests",
"services.core",
"services.net",
],
- visibility: [
- "//packages/modules/Connectivity/tests/integration",
- "//packages/modules/Connectivity/tests/unit",
- ],
}
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 25694d7..db39e6f 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -42,7 +42,9 @@
#define PLATFORM "/sys/fs/bpf/"
#define TETHERING "/sys/fs/bpf/tethering/"
+#define PRIVATE "/sys/fs/bpf/net_private/"
#define SHARED "/sys/fs/bpf/net_shared/"
+#define NETD "/sys/fs/bpf/netd_shared/"
class BpfExistenceTest : public ::testing::Test {
};
@@ -95,32 +97,35 @@
SHARED "map_dscp_policy_ipv6_socket_to_policies_map_A",
SHARED "map_dscp_policy_ipv6_socket_to_policies_map_B",
SHARED "map_dscp_policy_switch_comp_map",
- SHARED "map_netd_app_uid_stats_map",
- SHARED "map_netd_configuration_map",
- SHARED "map_netd_cookie_tag_map",
- SHARED "map_netd_iface_index_name_map",
- SHARED "map_netd_iface_stats_map",
- SHARED "map_netd_stats_map_A",
- SHARED "map_netd_stats_map_B",
- SHARED "map_netd_uid_counterset_map",
- SHARED "map_netd_uid_owner_map",
- SHARED "map_netd_uid_permission_map",
- SHARED "prog_block_bind4_block_port",
- SHARED "prog_block_bind6_block_port",
+ NETD "map_netd_app_uid_stats_map",
+ NETD "map_netd_configuration_map",
+ NETD "map_netd_cookie_tag_map",
+ NETD "map_netd_iface_index_name_map",
+ NETD "map_netd_iface_stats_map",
+ NETD "map_netd_stats_map_A",
+ NETD "map_netd_stats_map_B",
+ NETD "map_netd_uid_counterset_map",
+ NETD "map_netd_uid_owner_map",
+ NETD "map_netd_uid_permission_map",
SHARED "prog_clatd_schedcls_egress4_clat_ether",
SHARED "prog_clatd_schedcls_egress4_clat_rawip",
SHARED "prog_clatd_schedcls_ingress6_clat_ether",
SHARED "prog_clatd_schedcls_ingress6_clat_rawip",
+ NETD "prog_netd_cgroupskb_egress_stats",
+ NETD "prog_netd_cgroupskb_ingress_stats",
+ NETD "prog_netd_cgroupsock_inet_create",
+ NETD "prog_netd_schedact_ingress_account",
+ NETD "prog_netd_skfilter_allowlist_xtbpf",
+ NETD "prog_netd_skfilter_denylist_xtbpf",
+ NETD "prog_netd_skfilter_egress_xtbpf",
+ NETD "prog_netd_skfilter_ingress_xtbpf",
+};
+
+static const set<string> INTRODUCED_T_5_4 = {
+ SHARED "prog_block_bind4_block_port",
+ SHARED "prog_block_bind6_block_port",
SHARED "prog_dscp_policy_schedcls_set_dscp_ether",
SHARED "prog_dscp_policy_schedcls_set_dscp_raw_ip",
- SHARED "prog_netd_cgroupskb_egress_stats",
- SHARED "prog_netd_cgroupskb_ingress_stats",
- SHARED "prog_netd_cgroupsock_inet_create",
- SHARED "prog_netd_schedact_ingress_account",
- SHARED "prog_netd_skfilter_allowlist_xtbpf",
- SHARED "prog_netd_skfilter_denylist_xtbpf",
- SHARED "prog_netd_skfilter_egress_xtbpf",
- SHARED "prog_netd_skfilter_ingress_xtbpf",
};
static const set<string> REMOVED_T = {
@@ -162,6 +167,7 @@
if (IsAtLeastT()) {
addAll(expected, INTRODUCED_T);
+ if (android::bpf::isAtLeastKernelVersion(5, 4, 0)) addAll(expected, INTRODUCED_T_5_4);
removeAll(expected, REMOVED_T);
addAll(unexpected, REMOVED_T);
diff --git a/tests/smoketest/Android.bp b/tests/smoketest/Android.bp
index df8ab74..4ab24fc 100644
--- a/tests/smoketest/Android.bp
+++ b/tests/smoketest/Android.bp
@@ -22,6 +22,6 @@
static_libs: [
"androidx.test.rules",
"mockito-target-minus-junit4",
- "service-connectivity",
+ "service-connectivity-for-tests",
],
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 18ace4e..c9a41ba 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -112,7 +112,7 @@
name: "FrameworksNetTestsDefaults",
min_sdk_version: "30",
defaults: [
- "framework-connectivity-internal-test-defaults",
+ "framework-connectivity-test-defaults",
],
srcs: [
"java/**/*.java",
diff --git a/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
index 743d39e..aa5a246 100644
--- a/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
+++ b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
@@ -61,14 +61,6 @@
assertValues(builder.build(), 55, 1814302L, 21050L, 31001636L, 26152L)
}
- @Test
- fun testMaybeReadLegacyUid() {
- val builder = NetworkStatsCollection.Builder(BUCKET_DURATION_MS)
- NetworkStatsDataMigrationUtils.readLegacyUid(builder,
- getInputStreamForResource(R.raw.netstats_uid_v4), false /* taggedData */)
- assertValues(builder.build(), 223, 106245210L, 710722L, 1130647496L, 1103989L)
- }
-
private fun assertValues(
collection: NetworkStatsCollection,
expectedSize: Int,
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 9961978..7ee4031 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -15441,6 +15441,27 @@
}
@Test
+ public void testAutomotiveEthernetAllowedUids() throws Exception {
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+
+ // In this test the automotive feature will be enabled.
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+
+ // Simulate a restricted ethernet network.
+ final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_ETHERNET)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+
+ mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET,
+ new LinkProperties(), agentNetCaps.build());
+ validateAllowedUids(mEthernetNetworkAgent, TRANSPORT_ETHERNET, agentNetCaps, true);
+ }
+
+ @Test
public void testCbsAllowedUids() throws Exception {
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
@@ -15449,6 +15470,24 @@
doReturn(true).when(mCarrierPrivilegeAuthenticator)
.hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+ // Simulate a restricted telephony network. The telephony factory is entitled to set
+ // the access UID to the service package on any of its restricted networks.
+ final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+ new LinkProperties(), agentNetCaps.build());
+ validateAllowedUids(mCellNetworkAgent, TRANSPORT_CELLULAR, agentNetCaps, false);
+ }
+
+ private void validateAllowedUids(final TestNetworkAgentWrapper testAgent,
+ @NetworkCapabilities.Transport final int transportUnderTest,
+ final NetworkCapabilities.Builder ncb, final boolean forAutomotive) throws Exception {
final ArraySet<Integer> serviceUidSet = new ArraySet<>();
serviceUidSet.add(TEST_PACKAGE_UID);
final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
@@ -15459,40 +15498,34 @@
final TestNetworkCallback cb = new TestNetworkCallback();
- // Simulate a restricted telephony network. The telephony factory is entitled to set
- // the access UID to the service package on any of its restricted networks.
- final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
- .addCapability(NET_CAPABILITY_INTERNET)
- .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
-
+ /* Test setting UIDs */
// Cell gets to set the service UID as access UID
mCm.requestNetwork(new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
+ .addTransportType(transportUnderTest)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build(), cb);
- mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
- new LinkProperties(), ncb.build());
- mCellNetworkAgent.connect(true);
- cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ testAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(testAgent);
ncb.setAllowedUids(serviceUidSet);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(mCellNetworkAgent,
+ cb.expectCapabilitiesThat(testAgent,
caps -> caps.getAllowedUids().equals(serviceUidSet));
} else {
// S must ignore access UIDs.
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
}
+ /* Test setting UIDs is rejected when expected */
+ if (forAutomotive) {
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+ }
+
// ...but not to some other UID. Rejection sets UIDs to the empty set
ncb.setAllowedUids(nonServiceUidSet);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(mCellNetworkAgent,
+ cb.expectCapabilitiesThat(testAgent,
caps -> caps.getAllowedUids().isEmpty());
} else {
// S must ignore access UIDs.
@@ -15501,18 +15534,18 @@
// ...and also not to multiple UIDs even including the service UID
ncb.setAllowedUids(serviceUidSetPlus);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
- mCellNetworkAgent.disconnect();
- cb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ testAgent.disconnect();
+ cb.expectCallback(CallbackEntry.LOST, testAgent);
mCm.unregisterNetworkCallback(cb);
// Must be unset before touching the transports, because remove and add transport types
// check the specifier on the builder immediately, contradicting normal builder semantics
// TODO : fix the builder
ncb.setNetworkSpecifier(null);
- ncb.removeTransportType(TRANSPORT_CELLULAR);
+ ncb.removeTransportType(transportUnderTest);
ncb.addTransportType(TRANSPORT_WIFI);
// Wifi does not get to set access UID, even to the correct UID
mCm.requestNetwork(new NetworkRequest.Builder()
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
new file mode 100644
index 0000000..a9f80ea
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.server.ethernet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.InetAddresses;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.util.ArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class EthernetConfigStoreTest {
+ private static final LinkAddress LINKADDR = new LinkAddress("192.168.1.100/25");
+ private static final InetAddress GATEWAY = InetAddresses.parseNumericAddress("192.168.1.1");
+ private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("8.8.8.8");
+ private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("8.8.4.4");
+ private static final StaticIpConfiguration STATIC_IP_CONFIG =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(LINKADDR)
+ .setGateway(GATEWAY)
+ .setDnsServers(new ArrayList<InetAddress>(
+ List.of(DNS1, DNS2)))
+ .build();
+ private static final ProxyInfo PROXY_INFO = ProxyInfo.buildDirectProxy("test", 8888);
+ private static final IpConfiguration APEX_IP_CONFIG =
+ new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
+ private static final IpConfiguration LEGACY_IP_CONFIG =
+ new IpConfiguration(IpAssignment.STATIC, ProxySettings.STATIC, STATIC_IP_CONFIG,
+ PROXY_INFO);
+
+ private EthernetConfigStore mEthernetConfigStore;
+ private File mApexTestDir;
+ private File mLegacyTestDir;
+ private File mApexConfigFile;
+ private File mLegacyConfigFile;
+
+ private void createTestDir() {
+ final Context context = InstrumentationRegistry.getContext();
+ final File baseDir = context.getFilesDir();
+ mApexTestDir = new File(baseDir.getPath() + "/apex");
+ mApexTestDir.mkdirs();
+
+ mLegacyTestDir = new File(baseDir.getPath() + "/legacy");
+ mLegacyTestDir.mkdirs();
+ }
+
+ @Before
+ public void setUp() {
+ createTestDir();
+ mEthernetConfigStore = new EthernetConfigStore();
+ }
+
+ @After
+ public void tearDown() {
+ mApexTestDir.delete();
+ mLegacyTestDir.delete();
+ }
+
+ private void assertConfigFileExist(final String filepath) {
+ assertTrue(new File(filepath).exists());
+ }
+
+ /** Wait for the delayed write operation completes. */
+ private void waitForMs(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (final InterruptedException e) {
+ fail("Thread was interrupted");
+ }
+ }
+
+ @Test
+ public void testWriteIpConfigToApexFilePathAndRead() throws Exception {
+ // Write the config file to the apex file path, pretend the config file exits and
+ // check if IP config should be read from apex file path.
+ mApexConfigFile = new File(mApexTestDir.getPath(), "test.txt");
+ mEthernetConfigStore.write("eth0", APEX_IP_CONFIG, mApexConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(APEX_IP_CONFIG, ipConfigurations.get("eth0"));
+
+ mApexConfigFile.delete();
+ }
+
+ @Test
+ public void testWriteIpConfigToLegacyFilePathAndRead() throws Exception {
+ // Write the config file to the legacy file path, pretend the config file exits and
+ // check if IP config should be read from legacy file path.
+ mLegacyConfigFile = new File(mLegacyTestDir, "test.txt");
+ mEthernetConfigStore.write("0", LEGACY_IP_CONFIG, mLegacyConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(LEGACY_IP_CONFIG, ipConfigurations.get("0"));
+
+ // Check the same config file in apex file path is created.
+ assertConfigFileExist(mApexTestDir.getPath() + "/test.txt");
+
+ final File apexConfigFile = new File(mApexTestDir.getPath() + "/test.txt");
+ apexConfigFile.delete();
+ mLegacyConfigFile.delete();
+ }
+}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index b1831c4..33b36fd 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -29,22 +29,23 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.content.res.Resources;
import android.net.EthernetManager;
-import android.net.InetAddresses;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IEthernetServiceListener;
import android.net.INetd;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
-import android.net.InterfaceConfigurationParcel;
import android.net.LinkAddress;
import android.net.NetworkCapabilities;
import android.net.StaticIpConfiguration;
@@ -54,13 +55,13 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.connectivity.resources.R;
import com.android.testutils.HandlerUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -410,18 +411,24 @@
IpConfiguration configuration) { }
}
+ private InterfaceConfigurationParcel createMockedIfaceParcel(final String ifname,
+ final String hwAddr) {
+ final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
+ ifaceParcel.ifName = ifname;
+ ifaceParcel.hwAddr = hwAddr;
+ ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
+ return ifaceParcel;
+ }
+
@Test
public void testListenEthernetStateChange() throws Exception {
- final String testIface = "testtap123";
- final String testHwAddr = "11:22:33:44:55:66";
- final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
- ifaceParcel.ifName = testIface;
- ifaceParcel.hwAddr = testHwAddr;
- ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
-
tracker.setIncludeTestInterfaces(true);
waitForIdle();
+ final String testIface = "testtap123";
+ final String testHwAddr = "11:22:33:44:55:66";
+ final InterfaceConfigurationParcel ifaceParcel = createMockedIfaceParcel(testIface,
+ testHwAddr);
when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel);
doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
@@ -453,4 +460,43 @@
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
anyInt(), any());
}
+
+ @Test
+ public void testListenEthernetStateChange_unsolicitedEventListener() throws Exception {
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {});
+ doReturn(new String[] {}).when(mFactory).getAvailableInterfaces(anyBoolean());
+
+ tracker.setIncludeTestInterfaces(true);
+ tracker.start();
+
+ final ArgumentCaptor<EthernetTracker.InterfaceObserver> captor =
+ ArgumentCaptor.forClass(EthernetTracker.InterfaceObserver.class);
+ verify(mNetd, timeout(TIMEOUT_MS)).registerUnsolicitedEventListener(captor.capture());
+ final EthernetTracker.InterfaceObserver observer = captor.getValue();
+
+ tracker.setEthernetEnabled(false);
+ waitForIdle();
+ reset(mFactory);
+ reset(mNetd);
+
+ final String testIface = "testtap1";
+ observer.onInterfaceAdded(testIface);
+ verify(mFactory, never()).addInterface(eq(testIface), anyString(), any(), any());
+ observer.onInterfaceRemoved(testIface);
+ verify(mFactory, never()).removeInterface(eq(testIface));
+
+ final String testHwAddr = "11:22:33:44:55:66";
+ final InterfaceConfigurationParcel testIfaceParce =
+ createMockedIfaceParcel(testIface, testHwAddr);
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
+ when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(testIfaceParce);
+ doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
+ tracker.setEthernetEnabled(true);
+ waitForIdle();
+ reset(mFactory);
+
+ final String testIface2 = "testtap2";
+ observer.onInterfaceRemoved(testIface2);
+ verify(mFactory, timeout(TIMEOUT_MS)).removeInterface(eq(testIface2));
+ }
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index ceeb997..f1820b3 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -56,6 +57,9 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -77,6 +81,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -96,6 +101,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkStateSnapshot;
import android.net.NetworkStats;
+import android.net.NetworkStatsCollection;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
@@ -104,6 +110,7 @@
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.wifi.WifiInfo;
+import android.os.DropBoxManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -112,11 +119,13 @@
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.util.FileRotator;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
@@ -131,6 +140,16 @@
import com.android.testutils.TestBpfMap;
import com.android.testutils.TestableNetworkStatsProviderBinder;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import libcore.testing.io.TestIoUtils;
import org.junit.After;
@@ -142,13 +161,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.io.File;
-import java.time.Clock;
-import java.time.ZoneOffset;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* Tests for {@link NetworkStatsService}.
*
@@ -187,6 +199,7 @@
private long mElapsedRealtime;
private File mStatsDir;
+ private File mLegacyStatsDir;
private MockContext mServiceContext;
private @Mock TelephonyManager mTelephonyManager;
private static @Mock WifiInfo sWifiInfo;
@@ -220,6 +233,12 @@
private ContentObserver mContentObserver;
private Handler mHandler;
private TetheringManager.TetheringEventCallback mTetheringEventCallback;
+ private Map<String, NetworkStatsCollection> mPlatformNetworkStatsCollection =
+ new ArrayMap<String, NetworkStatsCollection>();
+ private boolean mStoreFilesInApexData = false;
+ private int mImportLegacyTargetAttempts = 0;
+ private @Mock PersistentInt mImportLegacyAttemptsCounter;
+ private @Mock PersistentInt mImportLegacySuccessesCounter;
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
@@ -286,6 +305,8 @@
any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
when(sWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
mStatsDir = TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
+ mLegacyStatsDir = TestIoUtils.createTemporaryDirectory(
+ getClass().getSimpleName() + "-legacy");
PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
Context.POWER_SERVICE);
@@ -295,8 +316,7 @@
mHandlerThread = new HandlerThread("HandlerThread");
final NetworkStatsService.Dependencies deps = makeDependencies();
mService = new NetworkStatsService(mServiceContext, mNetd, mAlarmManager, wakeLock,
- mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), mStatsDir,
- getBaseDir(mStatsDir), deps);
+ mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), deps);
mElapsedRealtime = 0L;
@@ -339,6 +359,44 @@
private NetworkStatsService.Dependencies makeDependencies() {
return new NetworkStatsService.Dependencies() {
@Override
+ public File getLegacyStatsDir() {
+ return mLegacyStatsDir;
+ }
+
+ @Override
+ public File getOrCreateStatsDir() {
+ return mStatsDir;
+ }
+
+ @Override
+ public boolean getStoreFilesInApexData() {
+ return mStoreFilesInApexData;
+ }
+
+ @Override
+ public int getImportLegacyTargetAttempts() {
+ return mImportLegacyTargetAttempts;
+ }
+
+ @Override
+ public PersistentInt createImportLegacyAttemptsCounter(
+ @androidx.annotation.NonNull Path path) {
+ return mImportLegacyAttemptsCounter;
+ }
+
+ @Override
+ public PersistentInt createImportLegacySuccessesCounter(
+ @androidx.annotation.NonNull Path path) {
+ return mImportLegacySuccessesCounter;
+ }
+
+ @Override
+ public NetworkStatsCollection readPlatformCollection(
+ @NonNull String prefix, long bucketDuration) {
+ return mPlatformNetworkStatsCollection.get(prefix);
+ }
+
+ @Override
public HandlerThread makeHandlerThread() {
return mHandlerThread;
}
@@ -1704,10 +1762,108 @@
assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
}
- private static File getBaseDir(File statsDir) {
- File baseDir = new File(statsDir, "netstats");
- baseDir.mkdirs();
- return baseDir;
+ /**
+ * Verify the service will perform data migration process can be controlled by the device flag.
+ */
+ @Test
+ public void testDataMigration() throws Exception {
+ assertStatsFilesExist(false);
+ expectDefaultSettings();
+
+ NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
+
+ mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ // expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+
+ mService.noteUidForeground(UID_RED, false);
+ verify(mUidCounterSetMap, never()).deleteEntry(any());
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
+ mService.noteUidForeground(UID_RED, true);
+ verify(mUidCounterSetMap).updateEntry(
+ eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
+
+ forcePollAndWaitForIdle();
+ // Simulate shutdown to force persisting data
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(true);
+
+ // Move the files to the legacy directory to simulate an import from old data
+ for (File f : mStatsDir.listFiles()) {
+ Files.move(f.toPath(), mLegacyStatsDir.toPath().resolve(f.getName()));
+ }
+ assertStatsFilesExist(false);
+
+ // Fetch the stats from the legacy files and set platform stats collection to be identical
+ mPlatformNetworkStatsCollection.put(PREFIX_DEV,
+ getLegacyCollection(PREFIX_DEV, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_XT,
+ getLegacyCollection(PREFIX_XT, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_UID,
+ getLegacyCollection(PREFIX_UID, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_UID_TAG,
+ getLegacyCollection(PREFIX_UID_TAG, true /* includeTags */));
+
+ // Mock zero usage and boot through serviceReady(), verify there is no imported data.
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+ mService.systemReady();
+ assertStatsFilesExist(false);
+
+ // Set the flag and reboot, verify the imported data is not there until next boot.
+ mStoreFilesInApexData = true;
+ mImportLegacyTargetAttempts = 3;
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(false);
+
+ // Boot through systemReady() again.
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+ mService.systemReady();
+
+ // After systemReady(), the service should have historical stats loaded again.
+ // Thus, verify
+ // 1. The stats are absorbed by the recorder.
+ // 2. The imported data are persisted.
+ // 3. The attempts count is set to target attempts count to indicate a successful
+ // migration.
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertStatsFilesExist(true);
+ verify(mImportLegacyAttemptsCounter).set(3);
+ verify(mImportLegacySuccessesCounter).set(1);
+
+ // TODO: Verify upgrading with Exception won't damege original data and
+ // will decrease the retry counter by 1.
+ }
+
+ private NetworkStatsRecorder makeTestRecorder(File directory, String prefix, Config config,
+ boolean includeTags) {
+ final NetworkStats.NonMonotonicObserver observer =
+ mock(NetworkStats.NonMonotonicObserver.class);
+ final DropBoxManager dropBox = mock(DropBoxManager.class);
+ return new NetworkStatsRecorder(new FileRotator(
+ directory, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+ observer, dropBox, prefix, config.bucketDuration, includeTags);
+ }
+
+ private NetworkStatsCollection getLegacyCollection(String prefix, boolean includeTags) {
+ final NetworkStatsRecorder recorder = makeTestRecorder(mLegacyStatsDir, PREFIX_DEV,
+ mSettings.getDevConfig(), includeTags);
+ return recorder.getOrLoadCompleteLocked();
}
private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
@@ -1816,11 +1972,10 @@
}
private void assertStatsFilesExist(boolean exist) {
- final File basePath = new File(mStatsDir, "netstats");
if (exist) {
- assertTrue(basePath.list().length > 0);
+ assertTrue(mStatsDir.list().length > 0);
} else {
- assertTrue(basePath.list().length == 0);
+ assertTrue(mStatsDir.list().length == 0);
}
}
diff --git a/tests/unit/java/com/android/server/net/PersistentIntTest.kt b/tests/unit/java/com/android/server/net/PersistentIntTest.kt
new file mode 100644
index 0000000..9268352
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/PersistentIntTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 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.server.net
+
+import android.util.SystemConfigFileCommitEventLogger
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import com.android.testutils.assertThrows
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+import java.io.IOException
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.attribute.PosixFilePermission
+import java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE
+import java.nio.file.attribute.PosixFilePermission.OWNER_READ
+import java.nio.file.attribute.PosixFilePermission.OWNER_WRITE
+import java.util.Random
+import kotlin.test.assertEquals
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(SC_V2)
+class PersistentIntTest {
+ val tempFilesCreated = mutableSetOf<Path>()
+ lateinit var tempDir: Path
+
+ @Before
+ fun setUp() {
+ tempDir = Files.createTempDirectory("tmp.PersistentIntTest.")
+ }
+
+ @After
+ fun tearDown() {
+ var permissions = setOf(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)
+ Files.setPosixFilePermissions(tempDir, permissions)
+
+ for (file in tempFilesCreated) {
+ Files.deleteIfExists(file)
+ }
+ Files.delete(tempDir)
+ }
+
+ @Test
+ fun testNormalReadWrite() {
+ // New, initialized to 0.
+ val pi = createPersistentInt()
+ assertEquals(0, pi.get())
+ pi.set(12345)
+ assertEquals(12345, pi.get())
+
+ // Existing.
+ val pi2 = createPersistentInt(pathOf(pi))
+ assertEquals(12345, pi2.get())
+ }
+
+ @Test
+ fun testReadOrWriteFailsInCreate() {
+ setWritable(tempDir, false)
+ assertThrows(IOException::class.java) {
+ createPersistentInt()
+ }
+ }
+
+ @Test
+ fun testReadOrWriteFailsAfterCreate() {
+ val pi = createPersistentInt()
+ pi.set(42)
+ assertEquals(42, pi.get())
+
+ val path = pathOf(pi)
+ setReadable(path, false)
+ assertThrows(IOException::class.java) { pi.get() }
+ pi.set(77)
+
+ setReadable(path, true)
+ setWritable(path, false)
+ setWritable(tempDir, false) // Writing creates a new file+renames, make this fail.
+ assertThrows(IOException::class.java) { pi.set(99) }
+ assertEquals(77, pi.get())
+ }
+
+ fun addOrRemovePermission(p: Path, permission: PosixFilePermission, add: Boolean) {
+ val permissions = Files.getPosixFilePermissions(p)
+ if (add) {
+ permissions.add(permission)
+ } else {
+ permissions.remove(permission)
+ }
+ Files.setPosixFilePermissions(p, permissions)
+ }
+
+ fun setReadable(p: Path, readable: Boolean) {
+ addOrRemovePermission(p, OWNER_READ, readable)
+ }
+
+ fun setWritable(p: Path, writable: Boolean) {
+ addOrRemovePermission(p, OWNER_WRITE, writable)
+ }
+
+ fun pathOf(pi: PersistentInt): Path {
+ return File(pi.path).toPath()
+ }
+
+ fun createPersistentInt(path: Path = randomTempPath()): PersistentInt {
+ tempFilesCreated.add(path)
+ return PersistentInt(path.toString(),
+ SystemConfigFileCommitEventLogger("PersistentIntTest"))
+ }
+
+ fun randomTempPath(): Path {
+ return tempDir.resolve(Integer.toHexString(Random().nextInt())).also {
+ tempFilesCreated.add(it)
+ }
+ }
+}
diff --git a/tools/Android.bp b/tools/Android.bp
deleted file mode 100644
index 27f9b75..0000000
--- a/tools/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// Copyright (C) 2022 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 {
- // See: http://go/android-license-faq
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// Build tool used to generate jarjar rules for all classes in a jar, except those that are
-// API, UnsupportedAppUsage or otherwise excluded.
-python_binary_host {
- name: "jarjar-rules-generator",
- srcs: [
- "gen_jarjar.py",
- ],
- main: "gen_jarjar.py",
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- },
- },
- visibility: ["//packages/modules/Connectivity:__subpackages__"],
-}
-
-genrule_defaults {
- name: "jarjar-rules-combine-defaults",
- // Concat files with a line break in the middle
- cmd: "for src in $(in); do cat $${src}; echo; done > $(out)",
- defaults_visibility: ["//packages/modules/Connectivity:__subpackages__"],
-}
diff --git a/tools/gen_jarjar.py b/tools/gen_jarjar.py
deleted file mode 100755
index 6fdf3f4..0000000
--- a/tools/gen_jarjar.py
+++ /dev/null
@@ -1,166 +0,0 @@
-#
-# Copyright (C) 2022 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.
-
-""" This script generates jarjar rule files to add a jarjar prefix to all classes, except those
-that are API, unsupported API or otherwise excluded."""
-
-import argparse
-import io
-import re
-import subprocess
-from xml import sax
-from xml.sax.handler import ContentHandler
-from zipfile import ZipFile
-
-
-def parse_arguments(argv):
- parser = argparse.ArgumentParser()
- parser.add_argument(
- '--jars', nargs='+',
- help='Path to pre-jarjar JAR. Can be followed by multiple space-separated paths.')
- parser.add_argument(
- '--prefix', required=True, help='Package prefix to use for jarjared classes.')
- parser.add_argument(
- '--output', required=True, help='Path to output jarjar rules file.')
- parser.add_argument(
- '--apistubs', nargs='*', default=[],
- help='Path to API stubs jar. Classes that are API will not be jarjared. Can be followed by '
- 'multiple space-separated paths.')
- parser.add_argument(
- '--unsupportedapi', nargs='*', default=[],
- help='Path to UnsupportedAppUsage hidden API .txt lists. '
- 'Classes that have UnsupportedAppUsage API will not be jarjared. Can be followed by '
- 'multiple space-separated paths.')
- parser.add_argument(
- '--excludes', nargs='*', default=[],
- help='Path to files listing classes that should not be jarjared. Can be followed by '
- 'multiple space-separated paths. '
- 'Each file should contain one full-match regex per line. Empty lines or lines '
- 'starting with "#" are ignored.')
- parser.add_argument(
- '--dexdump', default='dexdump', help='Path to dexdump binary.')
- return parser.parse_args(argv)
-
-
-class DumpHandler(ContentHandler):
- def __init__(self):
- super().__init__()
- self._current_package = None
- self.classes = []
-
- def startElement(self, name, attrs):
- if name == 'package':
- attr_name = attrs.getValue('name')
- assert attr_name != '', '<package> element missing name'
- assert self._current_package is None, f'Found nested package tags for {attr_name}'
- self._current_package = attr_name
- elif name == 'class':
- attr_name = attrs.getValue('name')
- assert attr_name != '', '<class> element missing name'
- self.classes.append(self._current_package + '.' + attr_name)
-
- def endElement(self, name):
- if name == 'package':
- self._current_package = None
-
-
-def _list_toplevel_dex_classes(jar, dexdump):
- """List all classes in a dexed .jar file that are not inner classes."""
- # Empty jars do net get a classes.dex: return an empty set for them
- with ZipFile(jar, 'r') as zip_file:
- if not zip_file.namelist():
- return set()
- cmd = [dexdump, '-l', 'xml', '-e', jar]
- dump = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE)
- handler = DumpHandler()
- xml_parser = sax.make_parser()
- xml_parser.setContentHandler(handler)
- xml_parser.parse(io.StringIO(dump.stdout))
- return set([_get_toplevel_class(c) for c in handler.classes])
-
-
-def _list_jar_classes(jar):
- with ZipFile(jar, 'r') as zip:
- files = zip.namelist()
- assert 'classes.dex' not in files, f'Jar file {jar} is dexed, ' \
- 'expected an intermediate zip of .class files'
- class_len = len('.class')
- return [f.replace('/', '.')[:-class_len] for f in files
- if f.endswith('.class') and not f.endswith('/package-info.class')]
-
-
-def _list_hiddenapi_classes(txt_file):
- out = set()
- with open(txt_file, 'r') as f:
- for line in f:
- if not line.strip():
- continue
- assert line.startswith('L') and ';' in line, f'Class name not recognized: {line}'
- clazz = line.replace('/', '.').split(';')[0][1:]
- out.add(_get_toplevel_class(clazz))
- return out
-
-
-def _get_toplevel_class(clazz):
- """Return the name of the toplevel (not an inner class) enclosing class of the given class."""
- if '$' not in clazz:
- return clazz
- return clazz.split('$')[0]
-
-
-def _get_excludes(path):
- out = []
- with open(path, 'r') as f:
- for line in f:
- stripped = line.strip()
- if not stripped or stripped.startswith('#'):
- continue
- out.append(re.compile(stripped))
- return out
-
-
-def make_jarjar_rules(args):
- excluded_classes = set()
- for apistubs_file in args.apistubs:
- excluded_classes.update(_list_toplevel_dex_classes(apistubs_file, args.dexdump))
-
- for unsupportedapi_file in args.unsupportedapi:
- excluded_classes.update(_list_hiddenapi_classes(unsupportedapi_file))
-
- exclude_regexes = []
- for exclude_file in args.excludes:
- exclude_regexes.extend(_get_excludes(exclude_file))
-
- with open(args.output, 'w') as outfile:
- for jar in args.jars:
- jar_classes = _list_jar_classes(jar)
- jar_classes.sort()
- for clazz in jar_classes:
- if (_get_toplevel_class(clazz) not in excluded_classes and
- not any(r.fullmatch(clazz) for r in exclude_regexes)):
- outfile.write(f'rule {clazz} {args.prefix}.@0\n')
- # Also include jarjar rules for unit tests of the class, so the package matches
- outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
- outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
-
-
-def _main():
- # Pass in None to use argv
- args = parse_arguments(None)
- make_jarjar_rules(args)
-
-
-if __name__ == '__main__':
- _main()