Merge changes I5a6174a4,Idfbfdf54
* changes:
Acquire lock to write NetworkStackConnector
Allow Bluetooth to bind to NetworkStack
diff --git a/Android.mk b/Android.mk
index 9f7bf99..e405345 100644
--- a/Android.mk
+++ b/Android.mk
@@ -87,14 +87,11 @@
frameworks/base/config/hiddenapi-greylist-max-p.txt \
frameworks/base/config/hiddenapi-greylist-max-o.txt \
frameworks/base/config/hiddenapi-force-blacklist.txt \
- $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \
- $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \
+ $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) \
$(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \
$(SOONG_HIDDENAPI_FLAGS)
frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \
- --public $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \
- --private $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \
- --csv $(PRIVATE_FLAGS_INPUTS) \
+ --csv $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) $(PRIVATE_FLAGS_INPUTS) \
--greylist frameworks/base/config/hiddenapi-greylist.txt \
--greylist-ignore-conflicts $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \
--greylist-max-p frameworks/base/config/hiddenapi-greylist-max-p.txt \
diff --git a/api/current.txt b/api/current.txt
index a7cc380..bd769e0 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -27553,6 +27553,7 @@
method public android.net.IpPrefix getDestination();
method public java.net.InetAddress getGateway();
method public String getInterface();
+ method public boolean hasGateway();
method public boolean isDefaultRoute();
method public boolean matches(java.net.InetAddress);
method public void writeToParcel(android.os.Parcel, int);
@@ -40455,6 +40456,8 @@
method public static String gaiName(int);
field public static final int AF_INET;
field public static final int AF_INET6;
+ field public static final int AF_NETLINK;
+ field public static final int AF_PACKET;
field public static final int AF_UNIX;
field public static final int AF_UNSPEC;
field public static final int AI_ADDRCONFIG;
@@ -40464,6 +40467,7 @@
field public static final int AI_NUMERICSERV;
field public static final int AI_PASSIVE;
field public static final int AI_V4MAPPED;
+ field public static final int ARPHRD_ETHER;
field public static final int CAP_AUDIT_CONTROL;
field public static final int CAP_AUDIT_WRITE;
field public static final int CAP_BLOCK_SUSPEND;
@@ -40587,6 +40591,10 @@
field public static final int ESPIPE;
field public static final int ESRCH;
field public static final int ESTALE;
+ field public static final int ETH_P_ALL;
+ field public static final int ETH_P_ARP;
+ field public static final int ETH_P_IP;
+ field public static final int ETH_P_IPV6;
field public static final int ETIME;
field public static final int ETIMEDOUT;
field public static final int ETXTBSY;
@@ -40684,6 +40692,8 @@
field public static final int MS_ASYNC;
field public static final int MS_INVALIDATE;
field public static final int MS_SYNC;
+ field public static final int NETLINK_INET_DIAG;
+ field public static final int NETLINK_ROUTE;
field public static final int NI_DGRAM;
field public static final int NI_NAMEREQD;
field public static final int NI_NOFQDN;
@@ -40720,6 +40730,7 @@
field public static final int PR_GET_DUMPABLE;
field public static final int PR_SET_DUMPABLE;
field public static final int PR_SET_NO_NEW_PRIVS;
+ field public static final int RTMGRP_NEIGH;
field public static final int RT_SCOPE_HOST;
field public static final int RT_SCOPE_LINK;
field public static final int RT_SCOPE_NOWHERE;
diff --git a/api/system-current.txt b/api/system-current.txt
index ab45a22..2a68d0e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3044,6 +3044,10 @@
public class LinkAddress implements android.os.Parcelable {
ctor public LinkAddress(java.net.InetAddress, int);
ctor public LinkAddress(String);
+ method public boolean isGlobalPreferred();
+ method public boolean isIPv4();
+ method public boolean isIPv6();
+ method public boolean isSameAddressAs(android.net.LinkAddress);
}
public final class LinkProperties implements android.os.Parcelable {
@@ -3058,6 +3062,10 @@
method public void setMtu(int);
}
+ public class Network implements android.os.Parcelable {
+ method public android.net.Network getPrivateDnsBypassingCopy();
+ }
+
public final class NetworkCapabilities implements android.os.Parcelable {
method public int getSignalStrength();
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
@@ -3099,6 +3107,13 @@
field public static final String EXTRA_PACKAGE_NAME = "packageName";
}
+ public final class RouteInfo implements android.os.Parcelable {
+ method public int getType();
+ field public static final int RTN_THROW = 9; // 0x9
+ field public static final int RTN_UNICAST = 1; // 0x1
+ field public static final int RTN_UNREACHABLE = 7; // 0x7
+ }
+
public class RssiCurve implements android.os.Parcelable {
ctor public RssiCurve(int, int, byte[]);
ctor public RssiCurve(int, int, byte[], int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 5e7ce65..9574520 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -602,11 +602,29 @@
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
+ public class LinkAddress implements android.os.Parcelable {
+ method public boolean isGlobalPreferred();
+ method public boolean isIPv4();
+ method public boolean isIPv6();
+ method public boolean isSameAddressAs(android.net.LinkAddress);
+ }
+
+ public class Network implements android.os.Parcelable {
+ method public android.net.Network getPrivateDnsBypassingCopy();
+ }
+
public final class NetworkCapabilities implements android.os.Parcelable {
method public int[] getCapabilities();
method public int[] getTransportTypes();
}
+ public final class RouteInfo implements android.os.Parcelable {
+ method public int getType();
+ field public static final int RTN_THROW = 9; // 0x9
+ field public static final int RTN_UNICAST = 1; // 0x1
+ field public static final int RTN_UNREACHABLE = 7; // 0x7
+ }
+
public class TrafficStats {
method public static long getLoopbackRxBytes();
method public static long getLoopbackRxPackets();
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index b40f15a..a536d08 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -26,6 +26,7 @@
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -117,7 +118,8 @@
* @return true if the address is IPv6.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isIPv6() {
return address instanceof Inet6Address;
}
@@ -126,6 +128,8 @@
* @return true if the address is IPv4 or is a mapped IPv4 address.
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean isIPv4() {
return address instanceof Inet4Address;
}
@@ -263,7 +267,8 @@
* otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isSameAddressAs(LinkAddress other) {
return address.equals(other.address) && prefixLength == other.prefixLength;
}
@@ -310,6 +315,8 @@
* Returns true if this {@code LinkAddress} is global scope and preferred.
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean isGlobalPreferred() {
/**
* Note that addresses flagged as IFA_F_OPTIMISTIC are
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 058cb94..c2b7d2c 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -52,6 +52,8 @@
/**
* The MacAddress zero MAC address.
+ *
+ * <p>Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index bf2344d..2c831de 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -157,6 +159,8 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
public Network getPrivateDnsBypassingCopy() {
return new Network(netId, true);
}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 37ab9ff..6bf2c67 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -16,16 +16,17 @@
package android.net;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import java.net.UnknownHostException;
-import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
-
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Objects;
@@ -67,12 +68,18 @@
/** Unicast route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_UNICAST = 1;
/** Unreachable route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_UNREACHABLE = 7;
/** Throw route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_THROW = 9;
/**
@@ -317,6 +324,8 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
public int getType() {
return mType;
}
@@ -362,9 +371,7 @@
* ({@code false}).
*
* @return {@code true} if a gateway is specified
- * @hide
*/
- @UnsupportedAppUsage
public boolean hasGateway() {
return mHasGateway;
}
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
index 95e5042..cacd42d 100644
--- a/core/java/android/net/ipmemorystore/Status.java
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* A parcelable status representing the result of an operation.
* Parcels as StatusParceled.
@@ -26,7 +28,10 @@
public class Status {
public static final int SUCCESS = 0;
- public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1;
+ public static final int ERROR_GENERIC = -1;
+ public static final int ERROR_ILLEGAL_ARGUMENT = -2;
+ public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -3;
+ public static final int ERROR_STORAGE = -4;
public final int resultCode;
@@ -34,7 +39,8 @@
this.resultCode = resultCode;
}
- Status(@NonNull final StatusParcelable parcelable) {
+ @VisibleForTesting
+ public Status(@NonNull final StatusParcelable parcelable) {
this(parcelable.resultCode);
}
@@ -55,7 +61,12 @@
public String toString() {
switch (resultCode) {
case SUCCESS: return "SUCCESS";
+ case ERROR_GENERIC: return "GENERIC ERROR";
+ case ERROR_ILLEGAL_ARGUMENT: return "ILLEGAL ARGUMENT";
case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
+ // "DB storage error" is not very helpful but SQLite does not provide specific error
+ // codes upon store failure. Thus this indicates SQLite returned some error upon store
+ case ERROR_STORAGE: return "DATABASE STORAGE ERROR";
default: return "Unknown value ?!";
}
}
diff --git a/core/java/android/net/ipmemorystore/Utils.java b/core/java/android/net/ipmemorystore/Utils.java
index 73d8c83..b361aca 100644
--- a/core/java/android/net/ipmemorystore/Utils.java
+++ b/core/java/android/net/ipmemorystore/Utils.java
@@ -17,18 +17,25 @@
package android.net.ipmemorystore;
import android.annotation.NonNull;
+import android.annotation.Nullable;
/** {@hide} */
public class Utils {
/** Pretty print */
- public static String blobToString(final Blob blob) {
- final StringBuilder sb = new StringBuilder("Blob : [");
- if (blob.data.length <= 24) {
- appendByteArray(sb, blob.data, 0, blob.data.length);
+ public static String blobToString(@Nullable final Blob blob) {
+ return "Blob : " + byteArrayToString(null == blob ? null : blob.data);
+ }
+
+ /** Pretty print */
+ public static String byteArrayToString(@Nullable final byte[] data) {
+ if (null == data) return "null";
+ final StringBuilder sb = new StringBuilder("[");
+ if (data.length <= 24) {
+ appendByteArray(sb, data, 0, data.length);
} else {
- appendByteArray(sb, blob.data, 0, 16);
+ appendByteArray(sb, data, 0, 16);
sb.append("...");
- appendByteArray(sb, blob.data, blob.data.length - 8, blob.data.length);
+ appendByteArray(sb, data, data.length - 8, data.length);
}
sb.append("]");
return sb.toString();
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 31e799d..0985ac3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -378,8 +378,8 @@
Values must be from NetworkCapabilities#NET_CAPABILITIES_* constants.
[IP config] Optional. If empty or not specified - DHCP will be used, otherwise
use the following format to specify static IP configuration:
- ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
- domains=<comma-sep-domains>
+ ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
+ domains=<comma-sep-domains>
-->
<string-array translatable="false" name="config_ethernet_interfaces">
<!--
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 0f8fc17..8a3cdca 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -185,13 +185,13 @@
}
void start(int slot) {
+ mSlot = slot;
int error = isValid();
if (error == SUCCESS) {
- mSlot = slot;
Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name());
mNai.asyncChannel.sendMessage(CMD_START_PACKET_KEEPALIVE, slot, mInterval, mPacket);
} else {
- notifyMessenger(NO_KEEPALIVE, error);
+ handleStopKeepalive(mNai, mSlot, error);
return;
}
}
@@ -277,6 +277,7 @@
return;
}
ki.stop(reason);
+ Log.d(TAG, "Stopped keepalive " + slot + " on " + networkName);
networkKeepalives.remove(slot);
if (networkKeepalives.isEmpty()) {
mKeepalives.remove(nai);
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index b0adf95..2e7cbc6 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -955,12 +955,64 @@
@Override
public long getIfaceStats(String iface, int type) {
- return nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
+ long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
+ if (nativeIfaceStats == -1) {
+ return nativeIfaceStats;
+ } else {
+ // When tethering offload is in use, nativeIfaceStats does not contain usage from
+ // offload, add it back here.
+ // When tethering offload is not in use, nativeIfaceStats contains tethering usage.
+ // this does not cause double-counting of tethering traffic, because
+ // NetdTetheringStatsProvider returns zero NetworkStats
+ // when called with STATS_PER_IFACE.
+ return nativeIfaceStats + getTetherStats(iface, type);
+ }
}
@Override
public long getTotalStats(int type) {
- return nativeGetTotalStat(type, checkBpfStatsEnable());
+ long nativeTotalStats = nativeGetTotalStat(type, checkBpfStatsEnable());
+ if (nativeTotalStats == -1) {
+ return nativeTotalStats;
+ } else {
+ // Refer to comment in getIfaceStats
+ return nativeTotalStats + getTetherStats(IFACE_ALL, type);
+ }
+ }
+
+ private long getTetherStats(String iface, int type) {
+ final NetworkStats tetherSnapshot;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error get TetherStats: " + e);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ HashSet<String> limitIfaces;
+ if (iface == IFACE_ALL) {
+ limitIfaces = null;
+ } else {
+ limitIfaces = new HashSet<String>();
+ limitIfaces.add(iface);
+ }
+ NetworkStats.Entry entry = tetherSnapshot.getTotal(null, limitIfaces);
+ if (LOGD) Slog.d(TAG, "TetherStats: iface=" + iface + " type=" + type +
+ " entry=" + entry);
+ switch (type) {
+ case 0: // TYPE_RX_BYTES
+ return entry.rxBytes;
+ case 1: // TYPE_RX_PACKETS
+ return entry.rxPackets;
+ case 2: // TYPE_TX_BYTES
+ return entry.txBytes;
+ case 3: // TYPE_TX_PACKETS
+ return entry.txPackets;
+ default:
+ return 0;
+ }
}
private boolean checkBpfStatsEnable() {
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
index eaab650..238f077 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -17,9 +17,24 @@
package com.android.server.net.ipmemorystore;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentValues;
import android.content.Context;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
+import android.net.NetworkUtils;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.Status;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
/**
* Encapsulating class for using the SQLite database backing the memory store.
@@ -30,6 +45,8 @@
* @hide
*/
public class IpMemoryStoreDatabase {
+ private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
+
/**
* Contract class for the Network Attributes table.
*/
@@ -57,7 +74,7 @@
public static final String COLTYPE_DNSADDRESSES = "BLOB";
public static final String COLNAME_MTU = "mtu";
- public static final String COLTYPE_MTU = "INTEGER";
+ public static final String COLTYPE_MTU = "INTEGER DEFAULT -1";
public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
+ TABLENAME + " ("
@@ -108,7 +125,7 @@
/** The SQLite DB helper */
public static class DbHelper extends SQLiteOpenHelper {
// Update this whenever changing the schema.
- private static final int SCHEMA_VERSION = 1;
+ private static final int SCHEMA_VERSION = 2;
private static final String DATABASE_FILENAME = "IpMemoryStore.db";
public DbHelper(@NonNull final Context context) {
@@ -140,4 +157,216 @@
onCreate(db);
}
}
+
+ @NonNull
+ private static byte[] encodeAddressList(@NonNull final List<InetAddress> addresses) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ for (final InetAddress address : addresses) {
+ final byte[] b = address.getAddress();
+ os.write(b.length);
+ os.write(b, 0, b.length);
+ }
+ return os.toByteArray();
+ }
+
+ @NonNull
+ private static ArrayList<InetAddress> decodeAddressList(@NonNull final byte[] encoded) {
+ final ByteArrayInputStream is = new ByteArrayInputStream(encoded);
+ final ArrayList<InetAddress> addresses = new ArrayList<>();
+ int d = -1;
+ while ((d = is.read()) != -1) {
+ final byte[] bytes = new byte[d];
+ is.read(bytes, 0, d);
+ try {
+ addresses.add(InetAddress.getByAddress(bytes));
+ } catch (UnknownHostException e) { /* Hopefully impossible */ }
+ }
+ return addresses;
+ }
+
+ // Convert a NetworkAttributes object to content values to store them in a table compliant
+ // with the contract defined in NetworkAttributesContract.
+ @NonNull
+ private static ContentValues toContentValues(@NonNull final String key,
+ @Nullable final NetworkAttributes attributes, final long expiry) {
+ final ContentValues values = new ContentValues();
+ values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
+ values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
+ if (null != attributes) {
+ if (null != attributes.assignedV4Address) {
+ values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
+ NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
+ }
+ if (null != attributes.groupHint) {
+ values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
+ }
+ if (null != attributes.dnsAddresses) {
+ values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
+ encodeAddressList(attributes.dnsAddresses));
+ }
+ if (null != attributes.mtu) {
+ values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
+ }
+ }
+ return values;
+ }
+
+ // Convert a byte array into content values to store it in a table compliant with the
+ // contract defined in PrivateDataContract.
+ @NonNull
+ private static ContentValues toContentValues(@NonNull final String key,
+ @NonNull final String clientId, @NonNull final String name,
+ @NonNull final byte[] data) {
+ final ContentValues values = new ContentValues();
+ values.put(PrivateDataContract.COLNAME_L2KEY, key);
+ values.put(PrivateDataContract.COLNAME_CLIENT, clientId);
+ values.put(PrivateDataContract.COLNAME_DATANAME, name);
+ values.put(PrivateDataContract.COLNAME_DATA, data);
+ return values;
+ }
+
+ private static final String[] EXPIRY_COLUMN = new String[] {
+ NetworkAttributesContract.COLNAME_EXPIRYDATE
+ };
+ static final int EXPIRY_ERROR = -1; // Legal values for expiry are positive
+
+ static final String SELECT_L2KEY = NetworkAttributesContract.COLNAME_L2KEY + " = ?";
+
+ // Returns the expiry date of the specified row, or one of the error codes above if the
+ // row is not found or some other error
+ static long getExpiry(@NonNull final SQLiteDatabase db, @NonNull final String key) {
+ final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+ EXPIRY_COLUMN, // columns
+ SELECT_L2KEY, // selection
+ new String[] { key }, // selectionArgs
+ null, // groupBy
+ null, // having
+ null // orderBy
+ );
+ // L2KEY is the primary key ; it should not be possible to get more than one
+ // result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return EXPIRY_ERROR;
+ cursor.moveToFirst();
+ return cursor.getLong(0); // index in the EXPIRY_COLUMN array
+ }
+
+ static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
+
+ // Returns the relevance of the specified row, or one of the error codes above if the
+ // row is not found or some other error
+ static int getRelevance(@NonNull final SQLiteDatabase db, @NonNull final String key) {
+ final long expiry = getExpiry(db, key);
+ return expiry < 0 ? (int) expiry : RelevanceUtils.computeRelevanceForNow(expiry);
+ }
+
+ // If the attributes are null, this will only write the expiry.
+ // Returns an int out of Status.{SUCCESS,ERROR_*}
+ static int storeNetworkAttributes(@NonNull final SQLiteDatabase db, @NonNull final String key,
+ final long expiry, @Nullable final NetworkAttributes attributes) {
+ final ContentValues cv = toContentValues(key, attributes, expiry);
+ db.beginTransaction();
+ try {
+ // Unfortunately SQLite does not have any way to do INSERT OR UPDATE. Options are
+ // to either insert with on conflict ignore then update (like done here), or to
+ // construct a custom SQL INSERT statement with nested select.
+ final long resultId = db.insertWithOnConflict(NetworkAttributesContract.TABLENAME,
+ null, cv, SQLiteDatabase.CONFLICT_IGNORE);
+ if (resultId < 0) {
+ db.update(NetworkAttributesContract.TABLENAME, cv, SELECT_L2KEY, new String[]{key});
+ }
+ db.setTransactionSuccessful();
+ return Status.SUCCESS;
+ } catch (SQLiteException e) {
+ // No space left on disk or something
+ Log.e(TAG, "Could not write to the memory store", e);
+ } finally {
+ db.endTransaction();
+ }
+ return Status.ERROR_STORAGE;
+ }
+
+ // Returns an int out of Status.{SUCCESS,ERROR_*}
+ static int storeBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
+ @NonNull final String clientId, @NonNull final String name,
+ @NonNull final byte[] data) {
+ final long res = db.insertWithOnConflict(PrivateDataContract.TABLENAME, null,
+ toContentValues(key, clientId, name, data), SQLiteDatabase.CONFLICT_REPLACE);
+ return (res == -1) ? Status.ERROR_STORAGE : Status.SUCCESS;
+ }
+
+ @Nullable
+ static NetworkAttributes retrieveNetworkAttributes(@NonNull final SQLiteDatabase db,
+ @NonNull final String key) {
+ final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+ null, // columns, null means everything
+ NetworkAttributesContract.COLNAME_L2KEY + " = ?", // selection
+ new String[] { key }, // selectionArgs
+ null, // groupBy
+ null, // having
+ null); // orderBy
+ // L2KEY is the primary key ; it should not be possible to get more than one
+ // result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return null;
+ cursor.moveToFirst();
+
+ // Make sure the data hasn't expired
+ final long expiry = cursor.getLong(
+ cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE));
+ if (expiry < System.currentTimeMillis()) return null;
+
+ final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+ final int assignedV4AddressInt = getInt(cursor,
+ NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+ final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
+ final byte[] dnsAddressesBlob =
+ getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
+ final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+ if (0 != assignedV4AddressInt) {
+ builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
+ }
+ builder.setGroupHint(groupHint);
+ if (null != dnsAddressesBlob) {
+ builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
+ }
+ if (mtu >= 0) {
+ builder.setMtu(mtu);
+ }
+ return builder.build();
+ }
+
+ private static final String[] DATA_COLUMN = new String[] {
+ PrivateDataContract.COLNAME_DATA
+ };
+ @Nullable
+ static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
+ @NonNull final String clientId, @NonNull final String name) {
+ final Cursor cursor = db.query(PrivateDataContract.TABLENAME,
+ DATA_COLUMN, // columns
+ PrivateDataContract.COLNAME_L2KEY + " = ? AND " // selection
+ + PrivateDataContract.COLNAME_CLIENT + " = ? AND "
+ + PrivateDataContract.COLNAME_DATANAME + " = ?",
+ new String[] { key, clientId, name }, // selectionArgs
+ null, // groupBy
+ null, // having
+ null); // orderBy
+ // The query above is querying by (composite) primary key, so it should not be possible to
+ // get more than one result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return null;
+ cursor.moveToFirst();
+ return cursor.getBlob(0); // index in the DATA_COLUMN array
+ }
+
+ // Helper methods
+ static String getString(final Cursor cursor, final String columnName) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return (columnIndex >= 0) ? cursor.getString(columnIndex) : null;
+ }
+ static byte[] getBlob(final Cursor cursor, final String columnName) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null;
+ }
+ static int getInt(final Cursor cursor, final String columnName, final int defaultValue) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue;
+ }
}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index 55a72190..444b299 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -16,6 +16,13 @@
package com.android.server.net.ipmemorystore;
+import static android.net.ipmemorystore.Status.ERROR_DATABASE_CANNOT_BE_OPENED;
+import static android.net.ipmemorystore.Status.ERROR_GENERIC;
+import static android.net.ipmemorystore.Status.ERROR_ILLEGAL_ARGUMENT;
+import static android.net.ipmemorystore.Status.SUCCESS;
+
+import static com.android.server.net.ipmemorystore.IpMemoryStoreDatabase.EXPIRY_ERROR;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -28,7 +35,12 @@
import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
import android.net.ipmemorystore.IOnSameNetworkResponseListener;
import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.Status;
+import android.net.ipmemorystore.StatusParcelable;
+import android.net.ipmemorystore.Utils;
+import android.os.RemoteException;
import android.util.Log;
import java.util.concurrent.ExecutorService;
@@ -45,6 +57,7 @@
public class IpMemoryStoreService extends IIpMemoryStore.Stub {
private static final String TAG = IpMemoryStoreService.class.getSimpleName();
private static final int MAX_CONCURRENT_THREADS = 4;
+ private static final boolean DBG = true;
@NonNull
final Context mContext;
@@ -114,6 +127,11 @@
if (mDb != null) mDb.close();
}
+ /** Helper function to make a status object */
+ private StatusParcelable makeStatus(final int code) {
+ return new Status(code).toParcelable();
+ }
+
/**
* Store network attributes for a given L2 key.
*
@@ -128,11 +146,27 @@
* Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
* If the call failed, the L2 key will be null.
*/
+ // Note that while l2Key and attributes are non-null in spirit, they are received from
+ // another process. If the remote process decides to ignore everything and send null, this
+ // process should still not crash.
@Override
- public void storeNetworkAttributes(@NonNull final String l2Key,
- @NonNull final NetworkAttributesParcelable attributes,
+ public void storeNetworkAttributes(@Nullable final String l2Key,
+ @Nullable final NetworkAttributesParcelable attributes,
@Nullable final IOnStatusListener listener) {
- // TODO : implement this.
+ // Because the parcelable is 100% mutable, the thread may not see its members initialized.
+ // Therefore either an immutable object is created on this same thread before it's passed
+ // to the executor, or there need to be a write barrier here and a read barrier in the
+ // remote thread.
+ final NetworkAttributes na = null == attributes ? null : new NetworkAttributes(attributes);
+ mExecutor.execute(() -> {
+ try {
+ final int code = storeNetworkAttributesAndBlobSync(l2Key, na,
+ null /* clientId */, null /* name */, null /* data */);
+ if (null != listener) listener.onComplete(makeStatus(code));
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -141,16 +175,63 @@
* @param l2Key The L2 key for this network.
* @param clientId The ID of the client.
* @param name The name of this data.
- * @param data The data to store.
+ * @param blob The data to store.
* @param listener The listener that will be invoked to return the answer, or null if the
* is not interested in learning about success/failure.
* Through the listener, returns a status to indicate success or failure.
*/
@Override
- public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
- @NonNull final String name, @NonNull final Blob data,
+ public void storeBlob(@Nullable final String l2Key, @Nullable final String clientId,
+ @Nullable final String name, @Nullable final Blob blob,
@Nullable final IOnStatusListener listener) {
- // TODO : implement this.
+ final byte[] data = null == blob ? null : blob.data;
+ mExecutor.execute(() -> {
+ try {
+ final int code = storeNetworkAttributesAndBlobSync(l2Key,
+ null /* NetworkAttributes */, clientId, name, data);
+ if (null != listener) listener.onComplete(makeStatus(code));
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
+ }
+
+ /**
+ * Helper method for storeNetworkAttributes and storeBlob.
+ *
+ * Either attributes or none of clientId, name and data may be null. This will write the
+ * passed data if non-null, and will write attributes if non-null, but in any case it will
+ * bump the relevance up.
+ * Returns a success code from Status.
+ */
+ private int storeNetworkAttributesAndBlobSync(@Nullable final String l2Key,
+ @Nullable final NetworkAttributes attributes,
+ @Nullable final String clientId,
+ @Nullable final String name, @Nullable final byte[] data) {
+ if (null == l2Key) return ERROR_ILLEGAL_ARGUMENT;
+ if (null == attributes && null == data) return ERROR_ILLEGAL_ARGUMENT;
+ if (null != data && (null == clientId || null == name)) return ERROR_ILLEGAL_ARGUMENT;
+ if (null == mDb) return ERROR_DATABASE_CANNOT_BE_OPENED;
+ try {
+ final long oldExpiry = IpMemoryStoreDatabase.getExpiry(mDb, l2Key);
+ final long newExpiry = RelevanceUtils.bumpExpiryDate(
+ oldExpiry == EXPIRY_ERROR ? System.currentTimeMillis() : oldExpiry);
+ final int errorCode =
+ IpMemoryStoreDatabase.storeNetworkAttributes(mDb, l2Key, newExpiry, attributes);
+ // If no blob to store, the client is interested in the result of storing the attributes
+ if (null == data) return errorCode;
+ // Otherwise it's interested in the result of storing the blob
+ return IpMemoryStoreDatabase.storeBlob(mDb, l2Key, clientId, name, data);
+ } catch (Exception e) {
+ if (DBG) {
+ Log.e(TAG, "Exception while storing for key {" + l2Key
+ + "} ; NetworkAttributes {" + (null == attributes ? "null" : attributes)
+ + "} ; clientId {" + (null == clientId ? "null" : clientId)
+ + "} ; name {" + (null == name ? "null" : name)
+ + "} ; data {" + Utils.byteArrayToString(data) + "}", e);
+ }
+ }
+ return ERROR_GENERIC;
}
/**
@@ -198,9 +279,32 @@
* the query.
*/
@Override
- public void retrieveNetworkAttributes(@NonNull final String l2Key,
- @NonNull final IOnNetworkAttributesRetrieved listener) {
- // TODO : implement this.
+ public void retrieveNetworkAttributes(@Nullable final String l2Key,
+ @Nullable final IOnNetworkAttributesRetrieved listener) {
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == l2Key) {
+ listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onL2KeyResponse(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
+ null);
+ return;
+ }
+ try {
+ final NetworkAttributes attributes =
+ IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key);
+ listener.onL2KeyResponse(makeStatus(SUCCESS), l2Key,
+ null == attributes ? null : attributes.toParcelable());
+ } catch (final Exception e) {
+ listener.onL2KeyResponse(makeStatus(ERROR_GENERIC), l2Key, null);
+ }
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -217,6 +321,28 @@
@Override
public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
@NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
- // TODO : implement this.
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == l2Key) {
+ listener.onBlobRetrieved(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, name, null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onBlobRetrieved(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
+ name, null);
+ return;
+ }
+ try {
+ final Blob b = new Blob();
+ b.data = IpMemoryStoreDatabase.retrieveBlob(mDb, l2Key, clientId, name);
+ listener.onBlobRetrieved(makeStatus(SUCCESS), l2Key, name, b);
+ } catch (final Exception e) {
+ listener.onBlobRetrieved(makeStatus(ERROR_GENERIC), l2Key, name, null);
+ }
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
}
diff --git a/services/net/java/android/net/netlink/ConntrackMessage.java b/services/net/java/android/net/netlink/ConntrackMessage.java
index 605c46b..4ee6432 100644
--- a/services/net/java/android/net/netlink/ConntrackMessage.java
+++ b/services/net/java/android/net/netlink/ConntrackMessage.java
@@ -28,7 +28,6 @@
import android.system.OsConstants;
import android.util.Log;
-import libcore.io.SizeOf;
import java.net.Inet4Address;
import java.net.Inet6Address;
diff --git a/services/net/java/android/net/netlink/StructNfGenMsg.java b/services/net/java/android/net/netlink/StructNfGenMsg.java
index 99695e2..8155977 100644
--- a/services/net/java/android/net/netlink/StructNfGenMsg.java
+++ b/services/net/java/android/net/netlink/StructNfGenMsg.java
@@ -16,8 +16,6 @@
package android.net.netlink;
-import libcore.io.SizeOf;
-
import java.nio.ByteBuffer;
@@ -29,7 +27,7 @@
* @hide
*/
public class StructNfGenMsg {
- public static final int STRUCT_SIZE = 2 + SizeOf.SHORT;
+ public static final int STRUCT_SIZE = 2 + Short.BYTES;
public static final int NFNETLINK_V0 = 0;
diff --git a/services/net/java/android/net/netlink/StructNlAttr.java b/services/net/java/android/net/netlink/StructNlAttr.java
index 811bdbb..28a4e88 100644
--- a/services/net/java/android/net/netlink/StructNlAttr.java
+++ b/services/net/java/android/net/netlink/StructNlAttr.java
@@ -17,7 +17,6 @@
package android.net.netlink;
import android.net.netlink.NetlinkConstants;
-import libcore.io.SizeOf;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -117,7 +116,7 @@
public StructNlAttr(short type, short value, ByteOrder order) {
this(order);
nla_type = type;
- setValue(new byte[SizeOf.SHORT]);
+ setValue(new byte[Short.BYTES]);
getValueAsByteBuffer().putShort(value);
}
@@ -128,7 +127,7 @@
public StructNlAttr(short type, int value, ByteOrder order) {
this(order);
nla_type = type;
- setValue(new byte[SizeOf.INT]);
+ setValue(new byte[Integer.BYTES]);
getValueAsByteBuffer().putInt(value);
}
@@ -164,7 +163,7 @@
public int getValueAsInt(int defaultValue) {
final ByteBuffer byteBuffer = getValueAsByteBuffer();
- if (byteBuffer == null || byteBuffer.remaining() != SizeOf.INT) {
+ if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
return defaultValue;
}
return getValueAsByteBuffer().getInt();
diff --git a/services/net/java/android/net/netlink/StructNlMsgErr.java b/services/net/java/android/net/netlink/StructNlMsgErr.java
index f095af4..6fcc6e69d 100644
--- a/services/net/java/android/net/netlink/StructNlMsgErr.java
+++ b/services/net/java/android/net/netlink/StructNlMsgErr.java
@@ -18,7 +18,6 @@
import android.net.netlink.NetlinkConstants;
import android.net.netlink.StructNlMsgHdr;
-import libcore.io.SizeOf;
import java.nio.ByteBuffer;
@@ -31,7 +30,7 @@
* @hide
*/
public class StructNlMsgErr {
- public static final int STRUCT_SIZE = SizeOf.INT + StructNlMsgHdr.STRUCT_SIZE;
+ public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE;
public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java
index a4b2fbb..7b060da 100644
--- a/services/net/java/android/net/util/InterfaceParams.java
+++ b/services/net/java/android/net/util/InterfaceParams.java
@@ -16,9 +16,9 @@
package android.net.util;
-import static android.net.MacAddress.ALL_ZEROS_ADDRESS;
import static android.net.util.NetworkConstants.ETHER_MTU;
import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
+
import static com.android.internal.util.Preconditions.checkArgument;
import android.net.MacAddress;
@@ -67,7 +67,8 @@
checkArgument((index > 0), "invalid interface index");
this.name = name;
this.index = index;
- this.macAddr = (macAddr != null) ? macAddr : ALL_ZEROS_ADDRESS;
+ this.macAddr = (macAddr != null) ? macAddr : MacAddress.fromBytes(new byte[] {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 });
this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU;
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c9d0eb1..6724c034 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1173,17 +1173,33 @@
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
- List<SubscriptionInfo> result = null;
+ return getActiveSubscriptionInfoList(false);
+ }
+
+ /**
+ * This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly
+ * is true, it will filter out the hidden subscriptions.
+ *
+ * @hide
+ */
+ public List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
+ List<SubscriptionInfo> activeList = null;
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
- result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
+ activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
}
} catch (RemoteException ex) {
// ignore it
}
- return result;
+
+ if (!userVisibleOnly || activeList == null) {
+ return activeList;
+ } else {
+ return activeList.stream().filter(subInfo -> !shouldHideSubscription(subInfo))
+ .collect(Collectors.toList());
+ }
}
/**
@@ -2717,8 +2733,7 @@
if (availableList == null) {
return null;
} else {
- return getAvailableSubscriptionInfoList().stream()
- .filter(subInfo -> !shouldHideSubscription(subInfo))
+ return availableList.stream().filter(subInfo -> !shouldHideSubscription(subInfo))
.collect(Collectors.toList());
}
}
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
index a9f9758..1fc67a8 100644
--- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -27,6 +27,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.reflect.Modifier;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Arrays;
@@ -60,6 +61,12 @@
builder.setMtu(null);
in = builder.build();
assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ // Verify that this test does not miss any new field added later.
+ // If any field is added to NetworkAttributes it must be tested here for parceling
+ // roundtrip.
+ assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+ .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
}
@Test
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index e63c3b0..94bcd28 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -16,13 +16,30 @@
package com.android.server.net.ipmemorystore;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.Status;
+import android.net.ipmemorystore.StatusParcelable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
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;
@@ -30,41 +47,267 @@
import org.mockito.MockitoAnnotations;
import java.io.File;
+import java.lang.reflect.Modifier;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
-/** Unit tests for {@link IpMemoryStoreServiceTest}. */
+/** Unit tests for {@link IpMemoryStoreService}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class IpMemoryStoreServiceTest {
+ private static final String TEST_CLIENT_ID = "testClientId";
+ private static final String TEST_DATA_NAME = "testData";
+
@Mock
- Context mMockContext;
+ private Context mMockContext;
+ private File mDbFile;
+
+ private IpMemoryStoreService mService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString());
+ final Context context = InstrumentationRegistry.getContext();
+ final File dir = context.getFilesDir();
+ mDbFile = new File(dir, "test.db");
+ doReturn(mDbFile).when(mMockContext).getDatabasePath(anyString());
+ mService = new IpMemoryStoreService(mMockContext);
+ }
+
+ @After
+ public void tearDown() {
+ mService.shutdown();
+ mDbFile.delete();
+ }
+
+ /** Helper method to make a vanilla IOnStatusListener */
+ private IOnStatusListener onStatus(Consumer<Status> functor) {
+ return new IOnStatusListener() {
+ @Override
+ public void onComplete(final StatusParcelable statusParcelable) throws RemoteException {
+ functor.accept(new Status(statusParcelable));
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ /** Helper method to make an IOnBlobRetrievedListener */
+ private interface OnBlobRetrievedListener {
+ void onBlobRetrieved(Status status, String l2Key, String name, byte[] data);
+ }
+ private IOnBlobRetrievedListener onBlobRetrieved(final OnBlobRetrievedListener functor) {
+ return new IOnBlobRetrievedListener() {
+ @Override
+ public void onBlobRetrieved(final StatusParcelable statusParcelable,
+ final String l2Key, final String name, final Blob blob) throws RemoteException {
+ functor.onBlobRetrieved(new Status(statusParcelable), l2Key, name,
+ null == blob ? null : blob.data);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ /** Helper method to make an IOnNetworkAttributesRetrievedListener */
+ private interface OnNetworkAttributesRetrievedListener {
+ void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attr);
+ }
+ private IOnNetworkAttributesRetrieved onNetworkAttributesRetrieved(
+ final OnNetworkAttributesRetrievedListener functor) {
+ return new IOnNetworkAttributesRetrieved() {
+ @Override
+ public void onL2KeyResponse(final StatusParcelable status, final String l2Key,
+ final NetworkAttributesParcelable attributes)
+ throws RemoteException {
+ functor.onNetworkAttributesRetrieved(new Status(status), l2Key,
+ null == attributes ? null : new NetworkAttributes(attributes));
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ // Helper method to factorize some boilerplate
+ private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ functor.accept(latch);
+ try {
+ latch.await(5000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail(timeoutMessage);
+ }
}
@Test
public void testNetworkAttributes() {
- final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
- // TODO : implement this
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+ try {
+ na.setAssignedV4Address(
+ (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
+ } catch (UnknownHostException e) { /* Can't happen */ }
+ na.setGroupHint("hint1");
+ na.setMtu(219);
+ final String l2Key = UUID.randomUUID().toString();
+ NetworkAttributes attributes = na.build();
+ doLatched("Did not complete storing attributes", latch ->
+ mService.storeNetworkAttributes(l2Key, attributes.toParcelable(),
+ onStatus(status -> {
+ assertTrue("Store status not successful : " + status.resultCode,
+ status.isSuccess());
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete retrieving attributes", latch ->
+ mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(attributes, attr);
+ latch.countDown();
+ })));
+
+ final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder();
+ try {
+ na.setDnsAddresses(Arrays.asList(
+ new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
+ } catch (UnknownHostException e) { /* Still can't happen */ }
+ final NetworkAttributes attributes2 = na2.build();
+ doLatched("Did not complete storing attributes 2", latch ->
+ mService.storeNetworkAttributes(l2Key, attributes2.toParcelable(),
+ onStatus(status -> latch.countDown())));
+
+ doLatched("Did not complete retrieving attributes 2", latch ->
+ mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(attributes.assignedV4Address, attr.assignedV4Address);
+ assertEquals(attributes.groupHint, attr.groupHint);
+ assertEquals(attributes.mtu, attr.mtu);
+ assertEquals(attributes2.dnsAddresses, attr.dnsAddresses);
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete retrieving attributes 3", latch ->
+ mService.retrieveNetworkAttributes(l2Key + "nonexistent",
+ onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key + "nonexistent", key);
+ assertNull("Retrieved data not stored", attr);
+ latch.countDown();
+ }
+ )));
+
+ // Verify that this test does not miss any new field added later.
+ // If any field is added to NetworkAttributes it must be tested here for storing
+ // and retrieving.
+ assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+ .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
+ }
+
+ @Test
+ public void testInvalidAttributes() {
+ doLatched("Did not complete storing bad attributes", latch ->
+ mService.storeNetworkAttributes("key", null, onStatus(status -> {
+ assertFalse("Success storing on a null key",
+ status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ latch.countDown();
+ })));
+
+ final NetworkAttributes na = new NetworkAttributes.Builder().setMtu(2).build();
+ doLatched("Did not complete storing bad attributes", latch ->
+ mService.storeNetworkAttributes(null, na.toParcelable(), onStatus(status -> {
+ assertFalse("Success storing null attributes on a null key",
+ status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete storing bad attributes", latch ->
+ mService.storeNetworkAttributes(null, null, onStatus(status -> {
+ assertFalse("Success storing null attributes on a null key",
+ status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete retrieving bad attributes", latch ->
+ mService.retrieveNetworkAttributes(null, onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertFalse("Success retrieving attributes for a null key",
+ status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ assertNull(key);
+ assertNull(attr);
+ })));
}
@Test
public void testPrivateData() {
- final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
- // TODO : implement this
+ final Blob b = new Blob();
+ b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
+ final String l2Key = UUID.randomUUID().toString();
+ doLatched("Did not complete storing private data", latch ->
+ mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ onStatus(status -> {
+ assertTrue("Store status not successful : " + status.resultCode,
+ status.isSuccess());
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete retrieving private data", latch ->
+ mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ Arrays.equals(b.data, data);
+ latch.countDown();
+ })));
+
+ // Most puzzling error message ever
+ doLatched("Did not complete retrieving nothing", latch ->
+ mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME + "2", onBlobRetrieved(
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME + "2");
+ assertNull(data);
+ latch.countDown();
+ })));
}
@Test
public void testFindL2Key() {
- final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
// TODO : implement this
}
@Test
public void testIsSameNetwork() {
- final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
// TODO : implement this
}
}
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 01728fa1..2a8f695 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -17,6 +17,7 @@
Generate API lists for non-SDK API enforcement.
"""
import argparse
+from collections import defaultdict
import os
import sys
import re
@@ -27,16 +28,20 @@
FLAG_BLACKLIST = "blacklist"
FLAG_GREYLIST_MAX_O = "greylist-max-o"
FLAG_GREYLIST_MAX_P = "greylist-max-p"
+FLAG_CORE_PLATFORM_API = "core-platform-api"
# List of all known flags.
-FLAGS = [
+FLAGS_API_LIST = [
FLAG_WHITELIST,
FLAG_GREYLIST,
FLAG_BLACKLIST,
FLAG_GREYLIST_MAX_O,
FLAG_GREYLIST_MAX_P,
]
-FLAGS_SET = set(FLAGS)
+ALL_FLAGS = FLAGS_API_LIST + [ FLAG_CORE_PLATFORM_API ]
+
+FLAGS_API_LIST_SET = set(FLAGS_API_LIST)
+ALL_FLAGS_SET = set(ALL_FLAGS)
# Suffix used in command line args to express that only known and
# otherwise unassigned entries should be assign the given flag.
@@ -62,7 +67,7 @@
SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$')
# Predicates to be used with filter_apis.
-IS_UNASSIGNED = lambda api, flags: not flags
+HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags)
IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
def get_args():
@@ -73,12 +78,10 @@
"""
parser = argparse.ArgumentParser()
parser.add_argument('--output', required=True)
- parser.add_argument('--public', required=True, help='list of all public entries')
- parser.add_argument('--private', required=True, help='list of all private entries')
parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE',
help='CSV files to be merged into output')
- for flag in FLAGS:
+ for flag in ALL_FLAGS:
ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX
parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE',
help='lists of entries with flag "' + flag + '"')
@@ -118,26 +121,9 @@
f.writelines(lines)
class FlagsDict:
- def __init__(self, public_api, private_api):
- # Bootstrap the entries dictionary.
-
- # Check that the two sets do not overlap.
- public_api_set = set(public_api)
- private_api_set = set(private_api)
- assert public_api_set.isdisjoint(private_api_set), (
- "Lists of public and private API overlap. " +
- "This suggests an issue with the `hiddenapi` build tool.")
-
- # Compute the whole key set
- self._dict_keyset = public_api_set.union(private_api_set)
-
- # Create a dict that creates entries for both public and private API,
- # and assigns public API to the whitelist.
- self._dict = {}
- for api in public_api:
- self._dict[api] = set([ FLAG_WHITELIST ])
- for api in private_api:
- self._dict[api] = set()
+ def __init__(self):
+ self._dict_keyset = set()
+ self._dict = defaultdict(set)
def _check_entries_set(self, keys_subset, source):
assert isinstance(keys_subset, set)
@@ -150,12 +136,12 @@
def _check_flags_set(self, flags_subset, source):
assert isinstance(flags_subset, set)
- assert flags_subset.issubset(FLAGS_SET), (
+ assert flags_subset.issubset(ALL_FLAGS_SET), (
"Error processing: {}\n"
"The following flags were not recognized: \n"
"{}\n"
"Please visit go/hiddenapi for more information.").format(
- source, "\n".join(flags_subset - FLAGS_SET))
+ source, "\n".join(flags_subset - ALL_FLAGS_SET))
def filter_apis(self, filter_fn):
"""Returns APIs which match a given predicate.
@@ -173,7 +159,7 @@
def get_valid_subset_of_unassigned_apis(self, api_subset):
"""Sanitizes a key set input to only include keys which exist in the dictionary
- and have not been assigned any flags.
+ and have not been assigned any API list flags.
Args:
entries_subset (set/list): Key set to be sanitized.
@@ -182,7 +168,7 @@
Sanitized key set.
"""
assert isinstance(api_subset, set)
- return api_subset.intersection(self.filter_apis(IS_UNASSIGNED))
+ return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED))
def generate_csv(self):
"""Constructs CSV entries from a dictionary.
@@ -203,14 +189,13 @@
source (string): Origin of `csv_lines`. Will be printed in error messages.
Throws:
- AssertionError if parsed API signatures of flags are invalid.
+ AssertionError if parsed flags are invalid.
"""
# Split CSV lines into arrays of values.
csv_values = [ line.split(',') for line in csv_lines ]
- # Check that all entries exist in the dict.
- csv_keys = set([ csv[0] for csv in csv_values ])
- self._check_entries_set(csv_keys, source)
+ # Update the full set of API signatures.
+ self._dict_keyset.update([ csv[0] for csv in csv_values ])
# Check that all flags are known.
csv_flags = set(reduce(lambda x, y: set(x).union(y), [ csv[1:] for csv in csv_values ], []))
@@ -224,7 +209,7 @@
"""Assigns a flag to given subset of entries.
Args:
- flag (string): One of FLAGS.
+ flag (string): One of ALL_FLAGS.
apis (set): Subset of APIs to recieve the flag.
source (string): Origin of `entries_subset`. Will be printed in error messages.
@@ -245,18 +230,23 @@
# Parse arguments.
args = vars(get_args())
- flags = FlagsDict(read_lines(args["public"]), read_lines(args["private"]))
+ # Initialize API->flags dictionary.
+ flags = FlagsDict()
+
+ # Merge input CSV files into the dictionary.
+ # Do this first because CSV files produced by parsing API stubs will
+ # contain the full set of APIs. Subsequent additions from text files
+ # will be able to detect invalid entries, and/or filter all as-yet
+ # unassigned entries.
+ for filename in args["csv"]:
+ flags.parse_and_merge_csv(read_lines(filename), filename)
# Combine inputs which do not require any particular order.
# (1) Assign serialization API to whitelist.
flags.assign_flag(FLAG_WHITELIST, flags.filter_apis(IS_SERIALIZATION))
- # (2) Merge input CSV files into the dictionary.
- for filename in args["csv"]:
- flags.parse_and_merge_csv(read_lines(filename), filename)
-
- # (3) Merge text files with a known flag into the dictionary.
- for flag in FLAGS:
+ # (2) Merge text files with a known flag into the dictionary.
+ for flag in ALL_FLAGS:
for filename in args[flag]:
flags.assign_flag(flag, read_lines(filename), filename)
@@ -265,13 +255,13 @@
# (a) the entry exists, and
# (b) it has not been assigned any other flag.
# Because of (b), this must run after all strict assignments have been performed.
- for flag in FLAGS:
+ for flag in ALL_FLAGS:
for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]:
valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename))
flags.assign_flag(flag, valid_entries, filename)
# Assign all remaining entries to the blacklist.
- flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(IS_UNASSIGNED))
+ flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))
# Write output.
write_lines(args["output"], flags.generate_csv())