Merge changes from topic "androidx-settings-aosp"
* changes:
Migrated various apps under frameworks/base/packages/Shell to androidx
Migrated SystemUI & SettingsLib to androidx
diff --git a/Android.bp b/Android.bp
index ecbe249..f11341e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1171,6 +1171,48 @@
create_stubs: false,
}
+metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
+ "--hide-package com.android.okhttp " +
+ "--hide-package com.android.org.conscrypt --hide-package com.android.server " +
+ "--hide RequiresPermission " +
+ "--hide MissingPermission --hide BroadcastBehavior " +
+ "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
+ "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo"
+
+doc_defaults {
+ name: "metalava-api-stubs-default",
+ srcs: [
+ ":opt-telephony-srcs",
+ ":opt-net-voip-srcs",
+ ":openjdk_javadoc_files",
+ ":non_openjdk_javadoc_files",
+ ":android_icu4j_src_files_for_docs",
+ ],
+ srcs_lib: "framework",
+ srcs_lib_whitelist_dirs: frameworks_base_subdirs,
+ srcs_lib_whitelist_pkgs: packages_to_document,
+ libs: [
+ "core-oj",
+ "core-libart",
+ "conscrypt",
+ "bouncycastle",
+ "okhttp",
+ "ext",
+ "framework",
+ "voip-common",
+ "android.test.mock",
+ ],
+ local_sourcepaths: frameworks_base_subdirs,
+ installable: false,
+ metalava_enabled: true,
+ metalava_annotations_enabled: true,
+ metalava_previous_api: ":last-released-public-api",
+ metalava_merge_annotations_dirs: [
+ "metalava-manual",
+ "ojluni-annotated-stubs",
+ ],
+}
+
droiddoc {
name: "doc-comment-check-docs",
defaults: ["framework-docs-default"],
@@ -1398,26 +1440,21 @@
}
droiddoc {
- name: "hiddenapi-lists",
- defaults: ["api-stubs-default"],
+ name: "hiddenapi-lists-docs",
+ defaults: ["metalava-api-stubs-default"],
arg_files: [
"core/res/AndroidManifest.xml",
- ":api-version-xml",
- "core/java/overview.html",
- ":current-support-api",
- "api/current.txt",
],
dex_api_filename: "public-dex.txt",
private_dex_api_filename: "private-dex.txt",
removed_dex_api_filename: "removed-dex.txt",
- args: framework_docs_args +
- " -referenceonly" +
- " -nodocs" +
- " -showUnannotated" +
- " -showAnnotation android.annotation.SystemApi" +
- " -showAnnotation android.annotation.TestApi",
+ args: metalava_framework_docs_args +
+ " --show-unannotated " +
+ " --show-annotation android.annotation.SystemApi " +
+ " --show-annotation android.annotation.TestApi "
}
+
droiddoc {
name: "hiddenapi-mappings",
defaults: ["api-stubs-default"],
@@ -1461,48 +1498,6 @@
],
}
-metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
- "--hide-package com.android.okhttp " +
- "--hide-package com.android.org.conscrypt --hide-package com.android.server " +
- "--hide RequiresPermission " +
- "--hide MissingPermission --hide BroadcastBehavior " +
- "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
- "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo"
-
-doc_defaults {
- name: "metalava-api-stubs-default",
- srcs: [
- ":opt-telephony-srcs",
- ":opt-net-voip-srcs",
- ":openjdk_javadoc_files",
- ":non_openjdk_javadoc_files",
- ":android_icu4j_src_files_for_docs",
- ],
- srcs_lib: "framework",
- srcs_lib_whitelist_dirs: frameworks_base_subdirs,
- srcs_lib_whitelist_pkgs: packages_to_document,
- libs: [
- "core-oj",
- "core-libart",
- "conscrypt",
- "bouncycastle",
- "okhttp",
- "ext",
- "framework",
- "voip-common",
- "android.test.mock",
- ],
- local_sourcepaths: frameworks_base_subdirs,
- installable: false,
- metalava_enabled: true,
- metalava_annotations_enabled: true,
- metalava_previous_api: ":last-released-public-api",
- metalava_merge_annotations_dirs: [
- "metalava-manual",
- "ojluni-annotated-stubs",
- ],
-}
-
droiddoc {
name: "api-stubs-docs",
defaults: ["metalava-api-stubs-default"],
diff --git a/api/current.txt b/api/current.txt
index 3c7dcc8b..0ef9d30 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8689,6 +8689,8 @@
method public byte[] getServiceData();
method public byte[] getServiceDataMask();
method public android.os.ParcelUuid getServiceDataUuid();
+ method public android.os.ParcelUuid getServiceSolicitationUuid();
+ method public android.os.ParcelUuid getServiceSolicitationUuidMask();
method public android.os.ParcelUuid getServiceUuid();
method public android.os.ParcelUuid getServiceUuidMask();
method public boolean matches(android.bluetooth.le.ScanResult);
@@ -8705,6 +8707,8 @@
method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[], byte[]);
method public android.bluetooth.le.ScanFilter.Builder setServiceData(android.os.ParcelUuid, byte[]);
method public android.bluetooth.le.ScanFilter.Builder setServiceData(android.os.ParcelUuid, byte[], byte[]);
+ method public android.bluetooth.le.ScanFilter.Builder setServiceSolicitationUuid(android.os.ParcelUuid);
+ method public android.bluetooth.le.ScanFilter.Builder setServiceSolicitationUuid(android.os.ParcelUuid, android.os.ParcelUuid);
method public android.bluetooth.le.ScanFilter.Builder setServiceUuid(android.os.ParcelUuid);
method public android.bluetooth.le.ScanFilter.Builder setServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid);
}
@@ -8717,6 +8721,7 @@
method public byte[] getManufacturerSpecificData(int);
method public java.util.Map<android.os.ParcelUuid, byte[]> getServiceData();
method public byte[] getServiceData(android.os.ParcelUuid);
+ method public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
method public java.util.List<android.os.ParcelUuid> getServiceUuids();
method public int getTxPowerLevel();
}
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index c3fae7d..c5d435b 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -58,6 +58,11 @@
private final ParcelUuid mServiceUuidMask;
@Nullable
+ private final ParcelUuid mServiceSolicitationUuid;
+ @Nullable
+ private final ParcelUuid mServiceSolicitationUuidMask;
+
+ @Nullable
private final ParcelUuid mServiceDataUuid;
@Nullable
private final byte[] mServiceData;
@@ -75,12 +80,15 @@
private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
- ParcelUuid uuidMask, ParcelUuid serviceDataUuid,
+ ParcelUuid uuidMask, ParcelUuid solicitationUuid,
+ ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
byte[] serviceData, byte[] serviceDataMask,
int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
mDeviceName = name;
mServiceUuid = uuid;
mServiceUuidMask = uuidMask;
+ mServiceSolicitationUuid = solicitationUuid;
+ mServiceSolicitationUuidMask = solicitationUuidMask;
mDeviceAddress = deviceAddress;
mServiceDataUuid = serviceDataUuid;
mServiceData = serviceData;
@@ -113,6 +121,14 @@
dest.writeParcelable(mServiceUuidMask, flags);
}
}
+ dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1);
+ if (mServiceSolicitationUuid != null) {
+ dest.writeParcelable(mServiceSolicitationUuid, flags);
+ dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1);
+ if (mServiceSolicitationUuidMask != null) {
+ dest.writeParcelable(mServiceSolicitationUuidMask, flags);
+ }
+ }
dest.writeInt(mServiceDataUuid == null ? 0 : 1);
if (mServiceDataUuid != null) {
dest.writeParcelable(mServiceDataUuid, flags);
@@ -172,6 +188,17 @@
}
}
if (in.readInt() == 1) {
+ ParcelUuid solicitationUuid = in.readParcelable(
+ ParcelUuid.class.getClassLoader());
+ builder.setServiceSolicitationUuid(solicitationUuid);
+ if (in.readInt() == 1) {
+ ParcelUuid solicitationUuidMask = in.readParcelable(
+ ParcelUuid.class.getClassLoader());
+ builder.setServiceSolicitationUuid(solicitationUuid,
+ solicitationUuidMask);
+ }
+ }
+ if (in.readInt() == 1) {
ParcelUuid servcieDataUuid =
in.readParcelable(ParcelUuid.class.getClassLoader());
if (in.readInt() == 1) {
@@ -231,6 +258,22 @@
return mServiceUuidMask;
}
+ /**
+ * Returns the filter set on the service Solicitation uuid.
+ */
+ @Nullable
+ public ParcelUuid getServiceSolicitationUuid() {
+ return mServiceSolicitationUuid;
+ }
+
+ /**
+ * Returns the filter set on the service Solicitation uuid mask.
+ */
+ @Nullable
+ public ParcelUuid getServiceSolicitationUuidMask() {
+ return mServiceSolicitationUuidMask;
+ }
+
@Nullable
public String getDeviceAddress() {
return mDeviceAddress;
@@ -288,7 +331,7 @@
// Scan record is null but there exist filters on it.
if (scanRecord == null
&& (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
- || mServiceData != null)) {
+ || mServiceData != null || mServiceSolicitationUuid != null)) {
return false;
}
@@ -303,6 +346,13 @@
return false;
}
+ // solicitation UUID match.
+ if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids(
+ mServiceSolicitationUuid, mServiceSolicitationUuidMask,
+ scanRecord.getServiceSolicitationUuids())) {
+ return false;
+ }
+
// Service data match
if (mServiceDataUuid != null) {
if (!matchesPartialData(mServiceData, mServiceDataMask,
@@ -350,6 +400,36 @@
return BitUtils.maskedEquals(data, uuid, mask);
}
+ /**
+ * Check if the solicitation uuid pattern is contained in a list of parcel uuids.
+ *
+ */
+ private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid,
+ ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) {
+ if (solicitationUuid == null) {
+ return true;
+ }
+ if (solicitationUuids == null) {
+ return false;
+ }
+
+ for (ParcelUuid parcelSolicitationUuid : solicitationUuids) {
+ UUID solicitationUuidMask = parcelSolicitationUuidMask == null
+ ? null : parcelSolicitationUuidMask.getUuid();
+ if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask,
+ parcelSolicitationUuid.getUuid())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Check if the solicitation uuid pattern matches the particular service solicitation uuid.
+ private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid,
+ UUID solicitationUuidMask, UUID data) {
+ return BitUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask);
+ }
+
// Check whether the data pattern matches the parsed data.
private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
if (parsedData == null || parsedData.length < data.length) {
@@ -376,6 +456,8 @@
return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
+ mDeviceAddress
+ ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
+ + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid
+ + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask
+ ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData="
+ Arrays.toString(mServiceData) + ", mServiceDataMask="
+ Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
@@ -391,7 +473,8 @@
mServiceDataUuid,
Arrays.hashCode(mServiceData),
Arrays.hashCode(mServiceDataMask),
- mServiceUuid, mServiceUuidMask);
+ mServiceUuid, mServiceUuidMask,
+ mServiceSolicitationUuid, mServiceSolicitationUuidMask);
}
@Override
@@ -412,7 +495,10 @@
&& Objects.deepEquals(mServiceData, other.mServiceData)
&& Objects.deepEquals(mServiceDataMask, other.mServiceDataMask)
&& Objects.equals(mServiceUuid, other.mServiceUuid)
- && Objects.equals(mServiceUuidMask, other.mServiceUuidMask);
+ && Objects.equals(mServiceUuidMask, other.mServiceUuidMask)
+ && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid)
+ && Objects.equals(mServiceSolicitationUuidMask,
+ other.mServiceSolicitationUuidMask);
}
/**
@@ -435,6 +521,9 @@
private ParcelUuid mServiceUuid;
private ParcelUuid mUuidMask;
+ private ParcelUuid mServiceSolicitationUuid;
+ private ParcelUuid mServiceSolicitationUuidMask;
+
private ParcelUuid mServiceDataUuid;
private byte[] mServiceData;
private byte[] mServiceDataMask;
@@ -493,6 +582,36 @@
return this;
}
+
+ /**
+ * Set filter on service solicitation uuid.
+ */
+ public Builder setServiceSolicitationUuid(ParcelUuid serviceSolicitationUuid) {
+ mServiceSolicitationUuid = serviceSolicitationUuid;
+ return this;
+ }
+
+
+ /**
+ * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the
+ * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to
+ * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to
+ * ignore that bit.
+ *
+ * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but
+ * {@code serviceSolicitationUuidMask} is not {@code null}.
+ */
+ public Builder setServiceSolicitationUuid(ParcelUuid serviceSolicitationUuid,
+ ParcelUuid solicitationUuidMask) {
+ if (mServiceSolicitationUuidMask != null && mServiceSolicitationUuid == null) {
+ throw new IllegalArgumentException(
+ "SolicitationUuid is null while SolicitationUuidMask is not null!");
+ }
+ mServiceSolicitationUuid = serviceSolicitationUuid;
+ mServiceSolicitationUuidMask = solicitationUuidMask;
+ return this;
+ }
+
/**
* Set filtering on service data.
*
@@ -598,7 +717,8 @@
*/
public ScanFilter build() {
return new ScanFilter(mDeviceName, mDeviceAddress,
- mServiceUuid, mUuidMask,
+ mServiceUuid, mUuidMask, mServiceSolicitationUuid,
+ mServiceSolicitationUuidMask,
mServiceDataUuid, mServiceData, mServiceDataMask,
mManufacturerId, mManufacturerData, mManufacturerDataMask);
}
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
index 07ed18d..7988008 100644
--- a/core/java/android/bluetooth/le/ScanRecord.java
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -51,6 +51,9 @@
private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16;
private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20;
private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21;
+ private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT = 0x14;
+ private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT = 0x1F;
+ private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15;
private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
// Flags of the advertising data.
@@ -58,6 +61,8 @@
@Nullable
private final List<ParcelUuid> mServiceUuids;
+ @Nullable
+ private final List<ParcelUuid> mServiceSolicitationUuids;
private final SparseArray<byte[]> mManufacturerSpecificData;
@@ -89,6 +94,15 @@
}
/**
+ * Returns a list of service solicitation UUIDs within the advertisement that are used to
+ * identify the Bluetooth GATT services.
+ */
+ @Nullable
+ public List<ParcelUuid> getServiceSolicitationUuids() {
+ return mServiceSolicitationUuids;
+ }
+
+ /**
* Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
* data.
*/
@@ -151,10 +165,12 @@
}
private ScanRecord(List<ParcelUuid> serviceUuids,
+ List<ParcelUuid> serviceSolicitationUuids,
SparseArray<byte[]> manufacturerData,
Map<ParcelUuid, byte[]> serviceData,
int advertiseFlags, int txPowerLevel,
String localName, byte[] bytes) {
+ mServiceSolicitationUuids = serviceSolicitationUuids;
mServiceUuids = serviceUuids;
mManufacturerSpecificData = manufacturerData;
mServiceData = serviceData;
@@ -184,6 +200,7 @@
int currentPos = 0;
int advertiseFlag = -1;
List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
+ List<ParcelUuid> serviceSolicitationUuids = new ArrayList<ParcelUuid>();
String localName = null;
int txPowerLevel = Integer.MIN_VALUE;
@@ -220,6 +237,18 @@
parseServiceUuid(scanRecord, currentPos, dataLength,
BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
break;
+ case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT:
+ parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_16_BIT, serviceSolicitationUuids);
+ break;
+ case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT:
+ parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_32_BIT, serviceSolicitationUuids);
+ break;
+ case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT:
+ parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_128_BIT, serviceSolicitationUuids);
+ break;
case DATA_TYPE_LOCAL_NAME_SHORT:
case DATA_TYPE_LOCAL_NAME_COMPLETE:
localName = new String(
@@ -265,19 +294,23 @@
if (serviceUuids.isEmpty()) {
serviceUuids = null;
}
- return new ScanRecord(serviceUuids, manufacturerData, serviceData,
- advertiseFlag, txPowerLevel, localName, scanRecord);
+ if (serviceSolicitationUuids.isEmpty()) {
+ serviceSolicitationUuids = null;
+ }
+ return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData,
+ serviceData, advertiseFlag, txPowerLevel, localName, scanRecord);
} catch (Exception e) {
Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
// As the record is invalid, ignore all the parsed results for this packet
// and return an empty record with raw scanRecord bytes in results
- return new ScanRecord(null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
+ return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
}
}
@Override
public String toString() {
return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
+ + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids
+ ", mManufacturerSpecificData=" + BluetoothLeUtils.toString(
mManufacturerSpecificData)
+ ", mServiceData=" + BluetoothLeUtils.toString(mServiceData)
@@ -297,6 +330,20 @@
return currentPos;
}
+ /**
+ * Parse service Solicitation UUIDs.
+ */
+ private static int parseServiceSolicitationUuid(byte[] scanRecord, int currentPos,
+ int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids) {
+ while (dataLength > 0) {
+ byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength);
+ serviceSolicitationUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
+ dataLength -= uuidLength;
+ currentPos += uuidLength;
+ }
+ return currentPos;
+ }
+
// Helper method to extract bytes from byte array.
private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
byte[] bytes = new byte[length];
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index fb916d3..ce18796 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3840,7 +3840,7 @@
@UnsupportedAppUsage
public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
return NetworkUtils.bindProcessToNetworkForHostResolution(
- network == null ? NETID_UNSET : network.netId);
+ (network == null) ? NETID_UNSET : network.getNetIdForResolv());
}
/**
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 142023d..bf2344d 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -100,21 +100,29 @@
// anytime and (b) receivers should be explicit about attempts to bypass
// Private DNS so that the intent of the code is easily determined and
// code search audits are possible.
- private boolean mPrivateDnsBypass = false;
+ private final transient boolean mPrivateDnsBypass;
/**
* @hide
*/
@UnsupportedAppUsage
public Network(int netId) {
+ this(netId, false);
+ }
+
+ /**
+ * @hide
+ */
+ public Network(int netId, boolean privateDnsBypass) {
this.netId = netId;
+ this.mPrivateDnsBypass = privateDnsBypass;
}
/**
* @hide
*/
public Network(Network that) {
- this.netId = that.netId;
+ this(that.netId, that.mPrivateDnsBypass);
}
/**
@@ -133,8 +141,7 @@
* Operates the same as {@code InetAddress.getByName} except that host
* resolution is done on this network.
*
- * @param host
- * the hostName to be resolved to an address or {@code null}.
+ * @param host the hostname to be resolved to an address or {@code null}.
* @return the {@code InetAddress} instance representing the host.
* @throws UnknownHostException
* if the address lookup fails.
@@ -144,14 +151,14 @@
}
/**
- * Specify whether or not Private DNS should be bypassed when attempting
+ * Obtain a Network object for which Private DNS is to be bypassed when attempting
* to use {@link #getAllByName(String)}/{@link #getByName(String)} methods on the given
* instance for hostname resolution.
*
* @hide
*/
- public void setPrivateDnsBypass(boolean bypass) {
- mPrivateDnsBypass = bypass;
+ public Network getPrivateDnsBypassingCopy() {
+ return new Network(netId, true);
}
/**
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index 10c0ce2..b8d7cf1 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -85,19 +85,16 @@
* @return true if the transaction was successful.
*/
public boolean requestTime(String host, int timeout, Network network) {
- // This flag only affects DNS resolution and not other socket semantics,
- // therefore it's safe to set unilaterally rather than take more
- // defensive measures like making a copy.
- network.setPrivateDnsBypass(true);
+ final Network networkForResolv = network.getPrivateDnsBypassingCopy();
InetAddress address = null;
try {
- address = network.getByName(host);
+ address = networkForResolv.getByName(host);
} catch (Exception e) {
EventLogTags.writeNtpFailure(host, e.toString());
if (DBG) Log.d(TAG, "request time failed: " + e);
return false;
}
- return requestTime(address, NTP_PORT, timeout, network);
+ return requestTime(address, NTP_PORT, timeout, networkForResolv);
}
public boolean requestTime(InetAddress address, int port, int timeout, Network network) {
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 44f7e48..1b22911 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -139,6 +139,8 @@
public static final int TAG_SYSTEM_GPS = 0xFFFFFF44;
/** @hide */
public static final int TAG_SYSTEM_PAC = 0xFFFFFF45;
+ /** @hide */
+ public static final int TAG_SYSTEM_DHCP_SERVER = 0xFFFFFF46;
private static INetworkStatsService sStatsService;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 88d09af..7a4ef5d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9466,6 +9466,17 @@
public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
/**
+ * Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
+ *
+ * Integer values are interpreted as boolean, and the absence of an explicit setting
+ * is interpreted as |true|.
+ * TODO: make the default |false|
+ * @hide
+ */
+ public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
+ "tether_enable_legacy_dhcp_server";
+
+ /**
* List of certificate (hex string representation of the application's certificate - SHA-1
* or SHA-256) and carrier app package pairs which are whitelisted to prompt the user for
* install when a sim card with matching UICC carrier privilege rules is inserted. The
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ca204ec..632e439 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3446,6 +3446,9 @@
empty string is passed in -->
<string name="config_wlan_network_service_package" translatable="false"></string>
+ <!-- Telephony qualified networks service package name to bind to by default. -->
+ <string name="config_qualified_networks_service_package" translatable="false"></string>
+
<!-- Wear devices: Controls the radios affected by Activity Mode. -->
<string-array name="config_wearActivityModeRadios">
<item>"wifi"</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f2539e7..de5dc43 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -273,6 +273,7 @@
<java-symbol type="string" name="config_wlan_network_service_package" />
<java-symbol type="string" name="config_wwan_data_service_package" />
<java-symbol type="string" name="config_wlan_data_service_package" />
+ <java-symbol type="string" name="config_qualified_networks_service_package" />
<java-symbol type="bool" name="config_networkSamplingWakesDevice" />
<java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
<java-symbol type="bool" name="config_sip_wifi_only" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 815294c..02e0d02 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -423,6 +423,7 @@
Settings.Global.TETHER_DUN_REQUIRED,
Settings.Global.TETHER_OFFLOAD_DISABLED,
Settings.Global.TETHER_SUPPORTED,
+ Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER,
Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
Settings.Global.THEATER_MODE_ON,
Settings.Global.TIME_ONLY_MODE_CONSTANTS,
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 3630005..fd1212a 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -33,7 +33,6 @@
import android.net.Proxy;
import android.net.Uri;
import android.net.captiveportal.CaptivePortalProbeSpec;
-import android.net.dns.ResolvUtil;
import android.net.http.SslError;
import android.net.wifi.WifiInfo;
import android.os.Build;
@@ -132,9 +131,9 @@
}
// Also initializes proxy system properties.
+ mNetwork = mNetwork.getPrivateDnsBypassingCopy();
mCm.bindProcessToNetwork(mNetwork);
- mCm.setProcessDefaultNetworkForHostResolution(
- ResolvUtil.getNetworkWithUseLocalNameserversFlag(mNetwork));
+ mCm.setProcessDefaultNetworkForHostResolution(mNetwork);
// Proxy system properties must be initialized before setContentView is called because
// setContentView initializes the WebView logic which in turn reads the system properties.
@@ -334,7 +333,6 @@
// TODO: reuse NetworkMonitor facilities for consistent captive portal detection.
new Thread(new Runnable() {
public void run() {
- final Network network = ResolvUtil.makeNetworkWithPrivateDnsBypass(mNetwork);
// Give time for captive portal to open.
try {
Thread.sleep(1000);
@@ -344,7 +342,7 @@
int httpResponseCode = 500;
String locationHeader = null;
try {
- urlConnection = (HttpURLConnection) network.openConnection(mUrl);
+ urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl);
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 505cfea..aa8cae5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -27,7 +27,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.settingslib.wrapper.LocationManagerWrapper;
+
import java.text.NumberFormat;
public class Utils {
@@ -69,8 +69,7 @@
intent, UserHandle.of(userId), android.Manifest.permission.WRITE_SECURE_SETTINGS);
LocationManager locationManager =
(LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
- LocationManagerWrapper wrapper = new LocationManagerWrapper(locationManager);
- wrapper.setLocationEnabledForUser(enabled, UserHandle.of(userId));
+ locationManager.setLocationEnabledForUser(enabled, UserHandle.of(userId));
}
public static boolean updateLocationMode(Context context, int oldMode, int newMode, int userId,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 65535b5..541877c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -20,7 +20,6 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCodecConfig;
-import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
@@ -28,9 +27,7 @@
import android.os.ParcelUuid;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
-import com.android.settingslib.wrapper.BluetoothA2dpWrapper;
import java.util.ArrayList;
import java.util.Arrays;
@@ -43,7 +40,6 @@
private Context mContext;
private BluetoothA2dp mService;
- private BluetoothA2dpWrapper mServiceWrapper;
private boolean mIsProfileReady;
private final LocalBluetoothAdapter mLocalAdapter;
@@ -67,7 +63,6 @@
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothA2dp) proxy;
- mServiceWrapper = new BluetoothA2dpWrapper(mService);
// We just bound to the service, so refresh the UI for any connected A2DP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
@@ -110,11 +105,6 @@
BluetoothProfile.A2DP);
}
- @VisibleForTesting
- void setBluetoothA2dpWrapper(BluetoothA2dpWrapper wrapper) {
- mServiceWrapper = wrapper;
- }
-
public boolean isConnectable() {
return true;
}
@@ -194,12 +184,12 @@
}
public boolean supportsHighQualityAudio(BluetoothDevice device) {
- int support = mServiceWrapper.supportsOptionalCodecs(device);
+ int support = mService.supportsOptionalCodecs(device);
return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
}
public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
- int enabled = mServiceWrapper.getOptionalCodecsEnabled(device);
+ int enabled = mService.getOptionalCodecsEnabled(device);
if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) {
return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED;
} else if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED &&
@@ -210,8 +200,8 @@
return true;
}
BluetoothCodecConfig codecConfig = null;
- if (mServiceWrapper.getCodecStatus(device) != null) {
- codecConfig = mServiceWrapper.getCodecStatus(device).getCodecConfig();
+ if (mService.getCodecStatus(device) != null) {
+ codecConfig = mService.getCodecStatus(device).getCodecConfig();
}
if (codecConfig != null) {
return !codecConfig.isMandatoryCodec();
@@ -224,7 +214,7 @@
int prefValue = enabled
? BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED
: BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED;
- mServiceWrapper.setOptionalCodecsEnabled(device, prefValue);
+ mService.setOptionalCodecsEnabled(device, prefValue);
if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
return;
}
@@ -244,8 +234,8 @@
// We want to get the highest priority codec, since that's the one that will be used with
// this device, and see if it is high-quality (ie non-mandatory).
BluetoothCodecConfig[] selectable = null;
- if (mServiceWrapper.getCodecStatus(device) != null) {
- selectable = mServiceWrapper.getCodecStatus(device).getCodecsSelectableCapabilities();
+ if (mService.getCodecStatus(device) != null) {
+ selectable = mService.getCodecStatus(device).getCodecsSelectableCapabilities();
// To get the highest priority, we sort in reverse.
Arrays.sort(selectable,
(a, b) -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java
index 92044c3..307b025 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java
@@ -36,7 +36,9 @@
"/system/etc/NOTICE.xml.gz",
"/vendor/etc/NOTICE.xml.gz",
"/odm/etc/NOTICE.xml.gz",
- "/oem/etc/NOTICE.xml.gz"};
+ "/oem/etc/NOTICE.xml.gz",
+ "/product/etc/NOTICE.xml.gz",
+ "/product_services/etc/NOTICE.xml.gz"};
private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
private Context mContext;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
deleted file mode 100644
index 17e3401..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 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.settingslib.wrapper;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothCodecStatus;
-import android.bluetooth.BluetoothDevice;
-
-/**
- * This class replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
- * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
- * methods in production and a mock in tests.
- */
-public class BluetoothA2dpWrapper {
-
- private BluetoothA2dp mService;
-
- public BluetoothA2dpWrapper(BluetoothA2dp service) {
- mService = service;
- }
-
- /**
- * @return the real {@code BluetoothA2dp} object
- */
- public BluetoothA2dp getService() {
- return mService;
- }
-
- /**
- * Wraps {@code BluetoothA2dp.getCodecStatus}
- */
- public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
- return mService.getCodecStatus(device);
- }
-
- /**
- * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
- */
- public int supportsOptionalCodecs(BluetoothDevice device) {
- return mService.supportsOptionalCodecs(device);
- }
-
- /**
- * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
- */
- public int getOptionalCodecsEnabled(BluetoothDevice device) {
- return mService.getOptionalCodecsEnabled(device);
- }
-
- /**
- * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
- */
- public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
- mService.setOptionalCodecsEnabled(device, value);
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java
deleted file mode 100644
index 1a268a6..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 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.settingslib.wrapper;
-
-import android.location.LocationManager;
-import android.os.UserHandle;
-
-/**
- * This class replicates some methods of android.location.LocationManager that are new and not
- * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
- * methods in production and a mock in tests.
- */
-public class LocationManagerWrapper {
-
- private LocationManager mLocationManager;
-
- public LocationManagerWrapper(LocationManager locationManager) {
- mLocationManager = locationManager;
- }
-
- /** Returns the real {@code LocationManager} object */
- public LocationManager getLocationManager() {
- return mLocationManager;
- }
-
- /** Wraps {@code LocationManager.isProviderEnabled} method */
- public boolean isProviderEnabled(String provider) {
- return mLocationManager.isProviderEnabled(provider);
- }
-
- /** Wraps {@code LocationManager.setProviderEnabledForUser} method */
- public void setProviderEnabledForUser(String provider, boolean enabled, UserHandle userHandle) {
- mLocationManager.setProviderEnabledForUser(provider, enabled, userHandle);
- }
-
- /** Wraps {@code LocationManager.isLocationEnabled} method */
- public boolean isLocationEnabled() {
- return mLocationManager.isLocationEnabled();
- }
-
- /** Wraps {@code LocationManager.isLocationEnabledForUser} method */
- public boolean isLocationEnabledForUser(UserHandle userHandle) {
- return mLocationManager.isLocationEnabledForUser(userHandle);
- }
-
- /** Wraps {@code LocationManager.setLocationEnabledForUser} method */
- public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
- mLocationManager.setLocationEnabledForUser(enabled, userHandle);
- }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index a79f841..09a2bd2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -41,8 +41,6 @@
import android.provider.Settings.Secure;
import android.text.TextUtils;
-import com.android.settingslib.wrapper.LocationManagerWrapper;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,7 +61,7 @@
@RunWith(SettingsLibRobolectricTestRunner.class)
@Config(shadows = {
UtilsTest.ShadowSecure.class,
- UtilsTest.ShadowLocationManagerWrapper.class})
+ UtilsTest.ShadowLocationManager.class})
public class UtilsTest {
private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
private static final String PERCENTAGE_0 = "0%";
@@ -192,8 +190,8 @@
}
}
- @Implements(value = LocationManagerWrapper.class)
- public static class ShadowLocationManagerWrapper {
+ @Implements(value = LocationManager.class)
+ public static class ShadowLocationManager {
@Implementation
public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index 334ea16..ef13a5f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -31,7 +31,6 @@
import android.content.res.Resources;
import com.android.settingslib.R;
-import com.android.settingslib.wrapper.BluetoothA2dpWrapper;
import org.junit.Before;
import org.junit.Test;
@@ -49,7 +48,6 @@
@Mock LocalBluetoothProfileManager mProfileManager;
@Mock BluetoothDevice mDevice;
@Mock BluetoothA2dp mBluetoothA2dp;
- @Mock BluetoothA2dpWrapper mBluetoothA2dpWrapper;
BluetoothProfile.ServiceListener mServiceListener;
A2dpProfile mProfile;
@@ -68,31 +66,30 @@
mProfile = new A2dpProfile(mContext, mAdapter, mDeviceManager, mProfileManager);
mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp);
- mProfile.setBluetoothA2dpWrapper(mBluetoothA2dpWrapper);
}
@Test
public void supportsHighQualityAudio() {
- when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isTrue();
- when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
- when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
}
@Test
public void isHighQualityAudioEnabled() {
- when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+ when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
- when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+ when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
@@ -100,23 +97,23 @@
// then isHighQualityAudioEnabled() should return true or false based on whether optional
// codecs are supported. If the device is connected then we should ask it directly, but if
// the device isn't connected then rely on the stored pref about such support.
- when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+ when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
BluetoothProfile.STATE_DISCONNECTED);
- when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
- when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
BluetoothProfile.STATE_CONNECTED);
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
- when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
+ when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
when(status.getCodecConfig()).thenReturn(config);
when(config.isMandatoryCodec()).thenReturn(false);
@@ -151,14 +148,14 @@
// Most tests want to simulate optional codecs being supported by the device, so do that
// by default here.
- when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
}
@Test
public void getLableCodecsNotSupported() {
setupLabelTest();
- when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
}
@@ -179,7 +176,7 @@
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
BluetoothCodecConfig[] configs = {config};
- when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
+ when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
when(config.isMandatoryCodec()).thenReturn(true);
@@ -194,7 +191,7 @@
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
BluetoothCodecConfig[] configs = {config};
- when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
+ when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
when(config.isMandatoryCodec()).thenReturn(false);
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index c0beb37..b8f057d 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -35,7 +35,6 @@
import android.net.Network;
import android.net.NetworkUtils;
import android.net.Uri;
-import android.net.dns.ResolvUtil;
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.UserHandle;
@@ -174,15 +173,6 @@
return new PrivateDnsConfig(useTls);
}
- public static PrivateDnsConfig tryBlockingResolveOf(Network network, String name) {
- try {
- final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally(network, name);
- return new PrivateDnsConfig(name, ips);
- } catch (UnknownHostException uhe) {
- return new PrivateDnsConfig(name, null);
- }
- }
-
public static Uri[] getPrivateDnsSettingsUris() {
return new Uri[]{
Settings.Global.getUriFor(PRIVATE_DNS_DEFAULT_MODE),
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 843ba2e..ca9b256 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -43,7 +43,6 @@
import android.net.Uri;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.captiveportal.CaptivePortalProbeSpec;
-import android.net.dns.ResolvUtil;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.metrics.ValidationProbeEvent;
@@ -326,7 +325,7 @@
mConnectivityServiceHandler = handler;
mDependencies = deps;
mNetworkAgentInfo = networkAgentInfo;
- mNetwork = deps.getNetwork(networkAgentInfo);
+ mNetwork = deps.getNetwork(networkAgentInfo).getPrivateDnsBypassingCopy();
mNetId = mNetwork.netId;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -800,9 +799,7 @@
private void resolveStrictModeHostname() {
try {
// Do a blocking DNS resolution using the network-assigned nameservers.
- // Do not set AI_ADDRCONFIG in ai_flags so we get all address families in advance.
- final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally(
- mNetwork, mPrivateDnsProviderHostname, 0 /* aiFlags */);
+ final InetAddress[] ips = mNetwork.getAllByName(mPrivateDnsProviderHostname);
mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig);
} catch (UnknownHostException uhe) {
@@ -860,14 +857,13 @@
// to complete, regardless of how many IP addresses a host has.
private static class OneAddressPerFamilyNetwork extends Network {
public OneAddressPerFamilyNetwork(Network network) {
- super(network);
+ // Always bypass Private DNS.
+ super(network.getPrivateDnsBypassingCopy());
}
@Override
public InetAddress[] getAllByName(String host) throws UnknownHostException {
- // Always bypass Private DNS.
- final List<InetAddress> addrs = Arrays.asList(
- ResolvUtil.blockingResolveAllLocally(this, host));
+ final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host));
// Ensure the address family of the first address is tried first.
LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>();
diff --git a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java b/services/net/java/android/net/dhcp/DhcpLeaseRepository.java
index 7e57c9f..9f77ed0 100644
--- a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java
+++ b/services/net/java/android/net/dhcp/DhcpLeaseRepository.java
@@ -29,7 +29,7 @@
import android.net.IpPrefix;
import android.net.MacAddress;
import android.net.util.SharedLog;
-import android.os.SystemClock;
+import android.net.dhcp.DhcpServer.Clock;
import android.util.ArrayMap;
import java.net.Inet4Address;
@@ -73,15 +73,6 @@
private int mNumAddresses;
private long mLeaseTimeMs;
- public static class Clock {
- /**
- * @see SystemClock#elapsedRealtime()
- */
- public long elapsedRealtime() {
- return SystemClock.elapsedRealtime();
- }
- }
-
/**
* Next timestamp when committed or declined leases should be checked for expired ones. This
* will always be lower than or equal to the time for the first lease to expire: it's OK not to
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 888821a..175e27e 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -9,6 +9,7 @@
import android.os.SystemProperties;
import android.system.OsConstants;
import android.text.TextUtils;
+
import com.android.internal.annotations.VisibleForTesting;
import java.io.UnsupportedEncodingException;
@@ -351,6 +352,14 @@
}
/**
+ * Convenience method to return the client ID if it was set explicitly, or null otherwise.
+ */
+ @Nullable
+ public byte[] getExplicitClientIdOrNull() {
+ return hasExplicitClientId() ? getClientId() : null;
+ }
+
+ /**
* Returns the client ID. If not set explicitly, this follows RFC 2132 and creates a client ID
* based on the hardware address.
*/
diff --git a/services/net/java/android/net/dhcp/DhcpPacketListener.java b/services/net/java/android/net/dhcp/DhcpPacketListener.java
index 498fd93..6f620c5 100644
--- a/services/net/java/android/net/dhcp/DhcpPacketListener.java
+++ b/services/net/java/android/net/dhcp/DhcpPacketListener.java
@@ -16,15 +16,14 @@
package android.net.dhcp;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.util.FdEventsReader;
-import android.net.util.PacketReader;
import android.os.Handler;
import android.system.Os;
import java.io.FileDescriptor;
import java.net.Inet4Address;
-import java.net.InetAddress;
import java.net.InetSocketAddress;
/**
@@ -35,19 +34,20 @@
static final class Payload {
final byte[] bytes = new byte[DhcpPacket.MAX_LENGTH];
Inet4Address srcAddr;
+ int srcPort;
}
- public DhcpPacketListener(Handler handler) {
+ public DhcpPacketListener(@NonNull Handler handler) {
super(handler, new Payload());
}
@Override
- protected int recvBufSize(Payload buffer) {
+ protected int recvBufSize(@NonNull Payload buffer) {
return buffer.bytes.length;
}
@Override
- protected final void handlePacket(Payload recvbuf, int length) {
+ protected final void handlePacket(@NonNull Payload recvbuf, int length) {
if (recvbuf.srcAddr == null) {
return;
}
@@ -55,30 +55,34 @@
try {
final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf.bytes, length,
DhcpPacket.ENCAP_BOOTP);
- onReceive(packet, recvbuf.srcAddr);
+ onReceive(packet, recvbuf.srcAddr, recvbuf.srcPort);
} catch (DhcpPacket.ParseException e) {
logParseError(recvbuf.bytes, length, e);
}
}
@Override
- protected int readPacket(FileDescriptor fd, Payload packetBuffer) throws Exception {
+ protected int readPacket(@NonNull FileDescriptor fd, @NonNull Payload packetBuffer)
+ throws Exception {
final InetSocketAddress addr = new InetSocketAddress();
final int read = Os.recvfrom(
fd, packetBuffer.bytes, 0, packetBuffer.bytes.length, 0 /* flags */, addr);
// Buffers with null srcAddr will be dropped in handlePacket()
packetBuffer.srcAddr = inet4AddrOrNull(addr);
+ packetBuffer.srcPort = addr.getPort();
return read;
}
@Nullable
- private static Inet4Address inet4AddrOrNull(InetSocketAddress addr) {
+ private static Inet4Address inet4AddrOrNull(@NonNull InetSocketAddress addr) {
return addr.getAddress() instanceof Inet4Address
? (Inet4Address) addr.getAddress()
: null;
}
- protected abstract void onReceive(DhcpPacket packet, Inet4Address srcAddr);
- protected abstract void logParseError(byte[] packet, int length, DhcpPacket.ParseException e);
+ protected abstract void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr,
+ int srcPort);
+ protected abstract void logParseError(@NonNull byte[] packet, int length,
+ @NonNull DhcpPacket.ParseException e);
}
diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/services/net/java/android/net/dhcp/DhcpServer.java
new file mode 100644
index 0000000..095a5eb72
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpServer.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2018 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 android.net.dhcp;
+
+import static android.net.NetworkUtils.getBroadcastAddress;
+import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
+import static android.net.TrafficStats.TAG_SYSTEM_DHCP_SERVER;
+import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
+import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
+import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
+import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BINDTODEVICE;
+import static android.system.OsConstants.SO_BROADCAST;
+import static android.system.OsConstants.SO_REUSEADDR;
+
+import static java.lang.Integer.toUnsignedLong;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.MacAddress;
+import android.net.NetworkUtils;
+import android.net.TrafficStats;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * A DHCPv4 server.
+ *
+ * <p>This server listens for and responds to packets on a single interface. It considers itself
+ * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of
+ * unknown hosts receive a reply instead of being ignored.
+ *
+ * <p>The server is single-threaded (including send/receive operations): all internal operations are
+ * done on the provided {@link Looper}. Public methods are thread-safe and will schedule operations
+ * on the looper asynchronously.
+ * @hide
+ */
+public class DhcpServer {
+ private static final String REPO_TAG = "Repository";
+
+ // Lease time to transmit to client instead of a negative time in case a lease expired before
+ // the server could send it (if the server process is suspended for example).
+ private static final int EXPIRED_FALLBACK_LEASE_TIME_SECS = 120;
+
+ private static final int CMD_START_DHCP_SERVER = 1;
+ private static final int CMD_STOP_DHCP_SERVER = 2;
+ private static final int CMD_UPDATE_PARAMS = 3;
+
+ @NonNull
+ private final ServerHandler mHandler;
+ @NonNull
+ private final InterfaceParams mIface;
+ @NonNull
+ private final DhcpLeaseRepository mLeaseRepo;
+ @NonNull
+ private final SharedLog mLog;
+ @NonNull
+ private final Dependencies mDeps;
+ @NonNull
+ private final Clock mClock;
+ @NonNull
+ private final DhcpPacketListener mPacketListener;
+
+ @Nullable
+ private FileDescriptor mSocket;
+ @NonNull
+ private DhcpServingParams mServingParams;
+
+ public static class Clock {
+ /**
+ * @see SystemClock#elapsedRealtime()
+ */
+ public long elapsedRealtime() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ public interface Dependencies {
+ void sendPacket(@NonNull FileDescriptor fd, @NonNull ByteBuffer buffer,
+ @NonNull InetAddress dst) throws ErrnoException, IOException;
+ DhcpLeaseRepository makeLeaseRepository(@NonNull DhcpServingParams servingParams,
+ @NonNull SharedLog log, @NonNull Clock clock);
+ DhcpPacketListener makePacketListener();
+ Clock makeClock();
+ void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
+ @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException;
+ }
+
+ private class DependenciesImpl implements Dependencies {
+ @Override
+ public void sendPacket(@NonNull FileDescriptor fd, @NonNull ByteBuffer buffer,
+ @NonNull InetAddress dst) throws ErrnoException, IOException {
+ Os.sendto(fd, buffer, 0, dst, DhcpPacket.DHCP_CLIENT);
+ }
+
+ @Override
+ public DhcpLeaseRepository makeLeaseRepository(@NonNull DhcpServingParams servingParams,
+ @NonNull SharedLog log, @NonNull Clock clock) {
+ return new DhcpLeaseRepository(
+ DhcpServingParams.makeIpPrefix(servingParams.serverAddr),
+ servingParams.excludedAddrs,
+ servingParams.dhcpLeaseTimeSecs*1000, log.forSubComponent(REPO_TAG), clock);
+ }
+
+ @Override
+ public DhcpPacketListener makePacketListener() {
+ return new PacketListener();
+ }
+
+ @Override
+ public Clock makeClock() {
+ return new Clock();
+ }
+
+ @Override
+ public void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
+ @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException {
+ NetworkUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
+ }
+ }
+
+ private static class MalformedPacketException extends Exception {
+ MalformedPacketException(String message, Throwable t) {
+ super(message, t);
+ }
+ }
+
+ public DhcpServer(@NonNull Looper looper, @NonNull InterfaceParams iface,
+ @NonNull DhcpServingParams params, @NonNull SharedLog log) {
+ this(looper, iface, params, log, null);
+ }
+
+ @VisibleForTesting
+ DhcpServer(@NonNull Looper looper, @NonNull InterfaceParams iface,
+ @NonNull DhcpServingParams params, @NonNull SharedLog log,
+ @Nullable Dependencies deps) {
+ if (deps == null) {
+ deps = new DependenciesImpl();
+ }
+ mHandler = new ServerHandler(looper);
+ mIface = iface;
+ mServingParams = params;
+ mLog = log;
+ mDeps = deps;
+ mClock = deps.makeClock();
+ mPacketListener = deps.makePacketListener();
+ mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock);
+ }
+
+ /**
+ * Start listening for and responding to packets.
+ */
+ public void start() {
+ mHandler.sendEmptyMessage(CMD_START_DHCP_SERVER);
+ }
+
+ /**
+ * Update serving parameters. All subsequently received requests will be handled with the new
+ * parameters, and current leases that are incompatible with the new parameters are dropped.
+ */
+ public void updateParams(@NonNull DhcpServingParams params) {
+ sendMessage(CMD_UPDATE_PARAMS, params);
+ }
+
+ /**
+ * Stop listening for packets.
+ *
+ * <p>As the server is stopped asynchronously, some packets may still be processed shortly after
+ * calling this method.
+ */
+ public void stop() {
+ mHandler.sendEmptyMessage(CMD_STOP_DHCP_SERVER);
+ }
+
+ private void sendMessage(int what, @Nullable Object obj) {
+ mHandler.sendMessage(mHandler.obtainMessage(what, obj));
+ }
+
+ private class ServerHandler extends Handler {
+ public ServerHandler(@NonNull Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case CMD_UPDATE_PARAMS:
+ final DhcpServingParams params = (DhcpServingParams) msg.obj;
+ mServingParams = params;
+ mLeaseRepo.updateParams(
+ DhcpServingParams.makeIpPrefix(mServingParams.serverAddr),
+ params.excludedAddrs,
+ params.dhcpLeaseTimeSecs);
+ break;
+ case CMD_START_DHCP_SERVER:
+ // This is a no-op if the listener is already started
+ mPacketListener.start();
+ break;
+ case CMD_STOP_DHCP_SERVER:
+ // This is a no-op if the listener was not started
+ mPacketListener.stop();
+ break;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void processPacket(@NonNull DhcpPacket packet, int srcPort) {
+ final String packetType = packet.getClass().getSimpleName();
+ if (srcPort != DHCP_CLIENT) {
+ mLog.logf("Ignored packet of type %s sent from client port %d", packetType, srcPort);
+ return;
+ }
+
+ mLog.log("Received packet of type " + packetType);
+ final Inet4Address sid = packet.mServerIdentifier;
+ if (sid != null && !sid.equals(mServingParams.serverAddr.getAddress())) {
+ mLog.log("Packet ignored due to wrong server identifier: " + sid);
+ return;
+ }
+
+ try {
+ if (packet instanceof DhcpDiscoverPacket) {
+ processDiscover((DhcpDiscoverPacket) packet);
+ } else if (packet instanceof DhcpRequestPacket) {
+ processRequest((DhcpRequestPacket) packet);
+ } else if (packet instanceof DhcpReleasePacket) {
+ processRelease((DhcpReleasePacket) packet);
+ } else {
+ mLog.e("Unknown packet type: " + packet.getClass().getSimpleName());
+ }
+ } catch (MalformedPacketException e) {
+ // Not an internal error: only logging exception message, not stacktrace
+ mLog.e("Ignored malformed packet: " + e.getMessage());
+ }
+ }
+
+ private void processDiscover(@NonNull DhcpDiscoverPacket packet)
+ throws MalformedPacketException {
+ final DhcpLease lease;
+ final MacAddress clientMac = getMacAddr(packet);
+ try {
+ lease = mLeaseRepo.getOffer(packet.getExplicitClientIdOrNull(), clientMac,
+ packet.mRelayIp, packet.mRequestedIp, packet.mHostName);
+ } catch (DhcpLeaseRepository.OutOfAddressesException e) {
+ transmitNak(packet, "Out of addresses to offer");
+ return;
+ } catch (DhcpLeaseRepository.InvalidAddressException e) {
+ transmitNak(packet, "Lease requested from an invalid subnet");
+ return;
+ }
+
+ transmitOffer(packet, lease, clientMac);
+ }
+
+ private void processRequest(@NonNull DhcpRequestPacket packet) throws MalformedPacketException {
+ // If set, packet SID matches with this server's ID as checked in processPacket().
+ final boolean sidSet = packet.mServerIdentifier != null;
+ final DhcpLease lease;
+ final MacAddress clientMac = getMacAddr(packet);
+ try {
+ lease = mLeaseRepo.requestLease(packet.getExplicitClientIdOrNull(), clientMac,
+ packet.mClientIp, packet.mRequestedIp, sidSet, packet.mHostName);
+ } catch (DhcpLeaseRepository.InvalidAddressException e) {
+ transmitNak(packet, "Invalid requested address");
+ return;
+ }
+
+ transmitAck(packet, lease, clientMac);
+ }
+
+ private void processRelease(@Nullable DhcpReleasePacket packet)
+ throws MalformedPacketException {
+ final byte[] clientId = packet.getExplicitClientIdOrNull();
+ final MacAddress macAddr = getMacAddr(packet);
+ // Don't care about success (there is no ACK/NAK); logging is already done in the repository
+ mLeaseRepo.releaseLease(clientId, macAddr, packet.mClientIp);
+ }
+
+ private Inet4Address getAckOrOfferDst(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
+ boolean broadcastFlag) {
+ // Unless relayed or broadcast, send to client IP if already configured on the client, or to
+ // the lease address if the client has no configured address
+ if (!isEmpty(request.mRelayIp)) {
+ return request.mRelayIp;
+ } else if (broadcastFlag) {
+ return (Inet4Address) Inet4Address.ALL;
+ } else if (!isEmpty(request.mClientIp)) {
+ return request.mClientIp;
+ } else {
+ return lease.getNetAddr();
+ }
+ }
+
+ /**
+ * Determine whether the broadcast flag should be set in the BOOTP packet flags. This does not
+ * apply to NAK responses, which should always have it set.
+ */
+ private static boolean getBroadcastFlag(@NonNull DhcpPacket request, @NonNull DhcpLease lease) {
+ // No broadcast flag if the client already has a configured IP to unicast to. RFC2131 #4.1
+ // has some contradictions regarding broadcast behavior if a client already has an IP
+ // configured and sends a request with both ciaddr (renew/rebind) and the broadcast flag
+ // set. Sending a unicast response to ciaddr matches previous behavior and is more
+ // efficient.
+ // If the client has no configured IP, broadcast if requested by the client or if the lease
+ // address cannot be used to send a unicast reply either.
+ return isEmpty(request.mClientIp) && (request.mBroadcast || isEmpty(lease.getNetAddr()));
+ }
+
+ private boolean transmitOffer(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
+ @NonNull MacAddress clientMac) {
+ final boolean broadcastFlag = getBroadcastFlag(request, lease);
+ final int timeout = getLeaseTimeout(lease);
+ final Inet4Address prefixMask =
+ getPrefixMaskAsInet4Address(mServingParams.serverAddr.getPrefixLength());
+ final Inet4Address broadcastAddr = getBroadcastAddress(
+ mServingParams.getServerInet4Addr(), mServingParams.serverAddr.getPrefixLength());
+ final ByteBuffer offerPacket = DhcpPacket.buildOfferPacket(
+ ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(),
+ lease.getNetAddr(), request.mClientMac, timeout,
+ prefixMask,
+ broadcastAddr,
+ new ArrayList<>(mServingParams.defaultRouters),
+ new ArrayList<>(mServingParams.dnsServers),
+ mServingParams.getServerInet4Addr(), null /* domainName */);
+
+ return transmitOfferOrAckPacket(offerPacket, request, lease, clientMac, broadcastFlag);
+ }
+
+ private boolean transmitAck(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
+ @NonNull MacAddress clientMac) {
+ // TODO: replace DhcpPacket's build methods with real builders and use common code with
+ // transmitOffer above
+ final boolean broadcastFlag = getBroadcastFlag(request, lease);
+ final int timeout = getLeaseTimeout(lease);
+ final ByteBuffer ackPacket = DhcpPacket.buildAckPacket(ENCAP_BOOTP, request.mTransId,
+ broadcastFlag, mServingParams.getServerInet4Addr(), lease.getNetAddr(),
+ request.mClientMac, timeout, mServingParams.getPrefixMaskAsAddress(),
+ mServingParams.getBroadcastAddress(),
+ new ArrayList<>(mServingParams.defaultRouters),
+ new ArrayList<>(mServingParams.dnsServers),
+ mServingParams.getServerInet4Addr(), null /* domainName */);
+
+ return transmitOfferOrAckPacket(ackPacket, request, lease, clientMac, broadcastFlag);
+ }
+
+ private boolean transmitNak(DhcpPacket request, String message) {
+ mLog.w("Transmitting NAK: " + message);
+ // Always set broadcast flag for NAK: client may not have a correct IP
+ final ByteBuffer nakPacket = DhcpPacket.buildNakPacket(
+ ENCAP_BOOTP, request.mTransId, mServingParams.getServerInet4Addr(),
+ request.mClientMac, true /* broadcast */, message);
+
+ final Inet4Address dst = isEmpty(request.mRelayIp)
+ ? (Inet4Address) Inet4Address.ALL
+ : request.mRelayIp;
+ return transmitPacket(nakPacket, DhcpNakPacket.class.getSimpleName(), dst);
+ }
+
+ private boolean transmitOfferOrAckPacket(@NonNull ByteBuffer buf, @NonNull DhcpPacket request,
+ @NonNull DhcpLease lease, @NonNull MacAddress clientMac, boolean broadcastFlag) {
+ mLog.logf("Transmitting %s with lease %s", request.getClass().getSimpleName(), lease);
+ // Client may not yet respond to ARP for the lease address, which may be the destination
+ // address. Add an entry to the ARP cache to save future ARP probes and make sure the
+ // packet reaches its destination.
+ if (!addArpEntry(clientMac, lease.getNetAddr())) {
+ // Logging for error already done
+ return false;
+ }
+ final Inet4Address dst = getAckOrOfferDst(request, lease, broadcastFlag);
+ return transmitPacket(buf, request.getClass().getSimpleName(), dst);
+ }
+
+ private boolean transmitPacket(@NonNull ByteBuffer buf, @NonNull String packetTypeTag,
+ @NonNull Inet4Address dst) {
+ try {
+ mDeps.sendPacket(mSocket, buf, dst);
+ } catch (ErrnoException | IOException e) {
+ mLog.e("Can't send packet " + packetTypeTag, e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean addArpEntry(@NonNull MacAddress macAddr, @NonNull Inet4Address inetAddr) {
+ try {
+ mDeps.addArpEntry(inetAddr, macAddr, mIface.name, mSocket);
+ return true;
+ } catch (IOException e) {
+ mLog.e("Error adding client to ARP table", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get the remaining lease time in seconds, starting from {@link Clock#elapsedRealtime()}.
+ *
+ * <p>This is an unsigned 32-bit integer, so it cannot be read as a standard (signed) Java int.
+ * The return value is only intended to be used to populate the lease time field in a DHCP
+ * response, considering that lease time is an unsigned 32-bit integer field in DHCP packets.
+ *
+ * <p>Lease expiration times are tracked internally with millisecond precision: this method
+ * returns a rounded down value.
+ */
+ private int getLeaseTimeout(@NonNull DhcpLease lease) {
+ final long remainingTimeSecs = (lease.getExpTime() - mClock.elapsedRealtime()) / 1000;
+ if (remainingTimeSecs < 0) {
+ mLog.e("Processing expired lease " + lease);
+ return EXPIRED_FALLBACK_LEASE_TIME_SECS;
+ }
+
+ if (remainingTimeSecs >= toUnsignedLong(INFINITE_LEASE)) {
+ return INFINITE_LEASE;
+ }
+
+ return (int) remainingTimeSecs;
+ }
+
+ /**
+ * Get the client MAC address from a packet.
+ *
+ * @throws MalformedPacketException The address in the packet uses an unsupported format.
+ */
+ @NonNull
+ private MacAddress getMacAddr(@NonNull DhcpPacket packet) throws MalformedPacketException {
+ try {
+ return MacAddress.fromBytes(packet.getClientMac());
+ } catch (IllegalArgumentException e) {
+ final String message = "Invalid MAC address in packet: "
+ + HexDump.dumpHexString(packet.getClientMac());
+ throw new MalformedPacketException(message, e);
+ }
+ }
+
+ private static boolean isEmpty(@NonNull Inet4Address address) {
+ return address == null || Inet4Address.ANY.equals(address);
+ }
+
+ private class PacketListener extends DhcpPacketListener {
+ public PacketListener() {
+ super(mHandler);
+ }
+
+ @Override
+ protected void onReceive(DhcpPacket packet, Inet4Address srcAddr, int srcPort) {
+ processPacket(packet, srcPort);
+ }
+
+ @Override
+ protected void logError(String msg, Exception e) {
+ mLog.e("Error receiving packet: " + msg, e);
+ }
+
+ @Override
+ protected void logParseError(byte[] packet, int length, DhcpPacket.ParseException e) {
+ mLog.e("Error parsing packet", e);
+ }
+
+ @Override
+ protected FileDescriptor createFd() {
+ // TODO: have and use an API to set a socket tag without going through the thread tag
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_DHCP_SERVER);
+ try {
+ mSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1);
+ // SO_BINDTODEVICE actually takes a string. This works because the first member
+ // of struct ifreq is a NULL-terminated interface name.
+ // TODO: add a setsockoptString()
+ Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIface.name);
+ Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1);
+ Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER);
+ NetworkUtils.protectFromVpn(mSocket);
+
+ return mSocket;
+ } catch (IOException | ErrnoException e) {
+ mLog.e("Error creating UDP socket", e);
+ DhcpServer.this.stop();
+ return null;
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
+ }
+ }
+}
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index b77da28..3cdef1e 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -431,6 +431,7 @@
public ProvisioningConfiguration(ProvisioningConfiguration other) {
mEnableIPv4 = other.mEnableIPv4;
mEnableIPv6 = other.mEnableIPv6;
+ mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
diff --git a/services/net/java/android/net/util/FdEventsReader.java b/services/net/java/android/net/util/FdEventsReader.java
index 575444f..8bbf449 100644
--- a/services/net/java/android/net/util/FdEventsReader.java
+++ b/services/net/java/android/net/util/FdEventsReader.java
@@ -89,7 +89,7 @@
mBuffer = buffer;
}
- public final void start() {
+ public void start() {
if (onCorrectThread()) {
createAndRegisterFd();
} else {
@@ -100,7 +100,7 @@
}
}
- public final void stop() {
+ public void stop() {
if (onCorrectThread()) {
unregisterAndDestroyFd();
} else {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fdee2f4..d37ef1a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1970,18 +1970,25 @@
* the binding intent go to.
* @hide
*/
- public static final String KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING
- = "carrier_network_service_wlan_package_override_string";
+ public static final String KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING =
+ "carrier_network_service_wlan_package_override_string";
/**
* Decides when clients try to bind to wwan (cellular) network service, which package name will
* the binding intent go to.
* @hide
*/
- public static final String KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING
- = "carrier_network_service_wwan_package_override_string";
+ public static final String KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING =
+ "carrier_network_service_wwan_package_override_string";
/**
+ * The package name of qualified networks service that telephony binds to.
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING =
+ "carrier_qualified_networks_service_package_override_string";
+ /**
* A list of 4 LTE RSCP thresholds above which a signal level is considered POOR,
* MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
*
@@ -2061,6 +2068,7 @@
sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true);
sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, "");
sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, "");
+ sDefaults.putString(KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING, "");
sDefaults.putString(KEY_CARRIER_DATA_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, "");
sDefaults.putString(KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, "");
sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING, "");
diff --git a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java b/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
index edadd6e..590bd67 100644
--- a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
+++ b/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
@@ -34,8 +34,8 @@
import android.annotation.NonNull;
import android.net.IpPrefix;
import android.net.MacAddress;
-import android.net.dhcp.DhcpLeaseRepository.Clock;
import android.net.util.SharedLog;
+import android.net.dhcp.DhcpServer.Clock;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
diff --git a/tests/net/java/android/net/dhcp/DhcpServerTest.java b/tests/net/java/android/net/dhcp/DhcpServerTest.java
new file mode 100644
index 0000000..66db5a8
--- /dev/null
+++ b/tests/net/java/android/net/dhcp/DhcpServerTest.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2018 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 android.net.dhcp;
+
+import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
+import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
+import static android.net.dhcp.DhcpPacket.INADDR_ANY;
+import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.net.InetAddress.parseNumericAddress;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkAddress;
+import android.net.MacAddress;
+import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException;
+import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException;
+import android.net.dhcp.DhcpServer.Clock;
+import android.net.dhcp.DhcpServer.Dependencies;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.test.TestLooper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DhcpServerTest {
+ private static final String PROP_DEXMAKER_SHARE_CLASSLOADER = "dexmaker.share_classloader";
+ private static final String TEST_IFACE = "testiface";
+ private static final MacAddress TEST_IFACE_MAC = MacAddress.fromString("11:22:33:44:55:66");
+ private static final InterfaceParams TEST_IFACEPARAMS =
+ new InterfaceParams(TEST_IFACE, 1, TEST_IFACE_MAC);
+
+ private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
+ private static final LinkAddress TEST_SERVER_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20);
+ private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>(
+ Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124")));
+ private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>(
+ Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127")));
+ private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
+ Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
+ private static final long TEST_LEASE_TIME_SECS = 3600L;
+ private static final int TEST_MTU = 1500;
+
+ private static final int TEST_TRANSACTION_ID = 123;
+ private static final byte[] TEST_CLIENT_MAC_BYTES = new byte [] { 1, 2, 3, 4, 5, 6 };
+ private static final MacAddress TEST_CLIENT_MAC = MacAddress.fromBytes(TEST_CLIENT_MAC_BYTES);
+ private static final Inet4Address TEST_CLIENT_ADDR = parseAddr("192.168.0.42");
+
+ private static final long TEST_CLOCK_TIME = 1234L;
+ private static final int TEST_LEASE_EXPTIME_SECS = 3600;
+ private static final DhcpLease TEST_LEASE = new DhcpLease(null, TEST_CLIENT_MAC,
+ TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS*1000L + TEST_CLOCK_TIME, null /* hostname */);
+
+ @NonNull @Mock
+ private Dependencies mDeps;
+ @NonNull @Mock
+ private DhcpLeaseRepository mRepository;
+ @NonNull @Mock
+ private Clock mClock;
+ @NonNull @Mock
+ private DhcpPacketListener mPacketListener;
+
+ @NonNull @Captor
+ private ArgumentCaptor<ByteBuffer> mSentPacketCaptor;
+ @NonNull @Captor
+ private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
+
+ @NonNull
+ private TestLooper mLooper;
+ @NonNull
+ private DhcpServer mServer;
+
+ @Nullable
+ private String mPrevShareClassloaderProp;
+
+ @Before
+ public void setUp() throws Exception {
+ // Allow mocking package-private classes
+ mPrevShareClassloaderProp = System.getProperty(PROP_DEXMAKER_SHARE_CLASSLOADER);
+ System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER, "true");
+ MockitoAnnotations.initMocks(this);
+
+ when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
+ when(mDeps.makeClock()).thenReturn(mClock);
+ when(mDeps.makePacketListener()).thenReturn(mPacketListener);
+ doNothing().when(mDeps)
+ .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
+ when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
+
+ final DhcpServingParams servingParams = new DhcpServingParams.Builder()
+ .setDefaultRouters(TEST_DEFAULT_ROUTERS)
+ .setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS)
+ .setDnsServers(TEST_DNS_SERVERS)
+ .setServerAddr(TEST_SERVER_LINKADDR)
+ .setLinkMtu(TEST_MTU)
+ .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
+ .build();
+
+ mLooper = new TestLooper();
+ mServer = new DhcpServer(mLooper.getLooper(), TEST_IFACEPARAMS, servingParams,
+ new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
+
+ mServer.start();
+ mLooper.dispatchAll();
+ }
+
+ @After
+ public void tearDown() {
+ // Calling stop() several times is not an issue
+ mServer.stop();
+ System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER,
+ (mPrevShareClassloaderProp == null ? "" : mPrevShareClassloaderProp));
+ }
+
+ @Test
+ public void testStart() throws Exception {
+ verify(mPacketListener, times(1)).start();
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ mServer.stop();
+ mLooper.dispatchAll();
+ verify(mPacketListener, times(1)).stop();
+ }
+
+ @Test
+ public void testDiscover() throws Exception {
+ // TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields
+ when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
+ eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
+ .thenReturn(TEST_LEASE);
+
+ final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
+ (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
+ false /* broadcast */, INADDR_ANY /* srcIp */);
+ mServer.processPacket(discover, DHCP_CLIENT);
+
+ assertResponseSentTo(TEST_CLIENT_ADDR);
+ final DhcpOfferPacket packet = assertOffer(getPacket());
+ assertMatchesTestLease(packet);
+ }
+
+ @Test
+ public void testDiscover_OutOfAddresses() throws Exception {
+ when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
+ eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
+ .thenThrow(new OutOfAddressesException("Test exception"));
+
+ final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
+ (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
+ false /* broadcast */, INADDR_ANY /* srcIp */);
+ mServer.processPacket(discover, DHCP_CLIENT);
+
+ assertResponseSentTo(INADDR_BROADCAST);
+ final DhcpNakPacket packet = assertNak(getPacket());
+ assertMatchesClient(packet);
+ }
+
+ private DhcpRequestPacket makeRequestSelectingPacket() {
+ final DhcpRequestPacket request = new DhcpRequestPacket(TEST_TRANSACTION_ID,
+ (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* relayIp */,
+ TEST_CLIENT_MAC_BYTES, false /* broadcast */);
+ request.mServerIdentifier = TEST_SERVER_ADDR;
+ request.mRequestedIp = TEST_CLIENT_ADDR;
+ return request;
+ }
+
+ @Test
+ public void testRequest_Selecting_Ack() throws Exception {
+ when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
+ eq(INADDR_ANY) /* clientAddr */, eq(TEST_CLIENT_ADDR) /* reqAddr */,
+ eq(true) /* sidSet */, isNull() /* hostname */))
+ .thenReturn(TEST_LEASE);
+
+ final DhcpRequestPacket request = makeRequestSelectingPacket();
+ mServer.processPacket(request, DHCP_CLIENT);
+
+ assertResponseSentTo(TEST_CLIENT_ADDR);
+ final DhcpAckPacket packet = assertAck(getPacket());
+ assertMatchesTestLease(packet);
+ }
+
+ @Test
+ public void testRequest_Selecting_Nak() throws Exception {
+ when(mRepository.requestLease(isNull(), eq(TEST_CLIENT_MAC),
+ eq(INADDR_ANY) /* clientAddr */, eq(TEST_CLIENT_ADDR) /* reqAddr */,
+ eq(true) /* sidSet */, isNull() /* hostname */))
+ .thenThrow(new InvalidAddressException("Test error"));
+
+ final DhcpRequestPacket request = makeRequestSelectingPacket();
+ mServer.processPacket(request, DHCP_CLIENT);
+
+ assertResponseSentTo(INADDR_BROADCAST);
+ final DhcpNakPacket packet = assertNak(getPacket());
+ assertMatchesClient(packet);
+ }
+
+ @Test
+ public void testRequest_Selecting_WrongClientPort() throws Exception {
+ final DhcpRequestPacket request = makeRequestSelectingPacket();
+ mServer.processPacket(request, 50000);
+
+ verify(mRepository, never()).requestLease(any(), any(), any(), any(), anyBoolean(), any());
+ verify(mDeps, never()).sendPacket(any(), any(), any());
+ }
+
+ @Test
+ public void testRelease() throws Exception {
+ final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID,
+ TEST_SERVER_ADDR, TEST_CLIENT_ADDR,
+ INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES);
+ mServer.processPacket(release, DHCP_CLIENT);
+
+ verify(mRepository, times(1))
+ .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
+ }
+
+ /* TODO: add more tests once packet construction is refactored, including:
+ * - usage of giaddr
+ * - usage of broadcast bit
+ * - other request states (init-reboot/renewing/rebinding)
+ */
+
+ private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
+ assertMatchesClient(packet);
+ assertFalse(packet.hasExplicitClientId());
+ assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier);
+ assertEquals(TEST_CLIENT_ADDR, packet.mYourIp);
+ assertNotNull(packet.mLeaseTime);
+ assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
+ assertNull(packet.mHostName);
+ }
+
+ private void assertMatchesClient(@NonNull DhcpPacket packet) {
+ assertEquals(TEST_TRANSACTION_ID, packet.mTransId);
+ assertEquals(TEST_CLIENT_MAC, MacAddress.fromBytes(packet.mClientMac));
+ }
+
+ private void assertResponseSentTo(@NonNull Inet4Address addr) {
+ assertEquals(addr, mResponseDstAddrCaptor.getValue());
+ }
+
+ private static DhcpNakPacket assertNak(@Nullable DhcpPacket packet) {
+ assertTrue(packet instanceof DhcpNakPacket);
+ return (DhcpNakPacket) packet;
+ }
+
+ private static DhcpAckPacket assertAck(@Nullable DhcpPacket packet) {
+ assertTrue(packet instanceof DhcpAckPacket);
+ return (DhcpAckPacket) packet;
+ }
+
+ private static DhcpOfferPacket assertOffer(@Nullable DhcpPacket packet) {
+ assertTrue(packet instanceof DhcpOfferPacket);
+ return (DhcpOfferPacket) packet;
+ }
+
+ private DhcpPacket getPacket() throws Exception {
+ verify(mDeps, times(1)).sendPacket(any(), any(), any());
+ return DhcpPacket.decodeFullPacket(mSentPacketCaptor.getValue(), ENCAP_BOOTP);
+ }
+
+ private static Inet4Address parseAddr(@Nullable String inet4Addr) {
+ return (Inet4Address) parseNumericAddress(inet4Addr);
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
index b017130..b399b0d 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
@@ -106,6 +106,7 @@
anyString())).thenReturn(TEST_HTTP_URL);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL),
anyString())).thenReturn(TEST_HTTPS_URL);
+ when(mNetwork.getPrivateDnsBypassingCopy()).thenReturn(mNetwork);
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);