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())