Merge "Wipe the data in IpMemoryStore database upon network factory reset." into qt-dev
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index 764e2d0..a538a5b 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -410,6 +410,7 @@
     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) {
@@ -432,6 +433,57 @@
     }
 
     /**
+     * Wipe all data in tables when network factory reset occurs.
+     */
+    static void wipeDataUponNetworkReset(@NonNull final SQLiteDatabase db) {
+        for (int remainingRetries = 3; remainingRetries > 0; --remainingRetries) {
+            db.beginTransaction();
+            try {
+                db.delete(NetworkAttributesContract.TABLENAME, null, null);
+                db.delete(PrivateDataContract.TABLENAME, null, null);
+                final Cursor cursorNetworkAttributes = db.query(
+                        // table name
+                        NetworkAttributesContract.TABLENAME,
+                        // column name
+                        new String[] { NetworkAttributesContract.COLNAME_L2KEY },
+                        null, // selection
+                        null, // selectionArgs
+                        null, // groupBy
+                        null, // having
+                        null, // orderBy
+                        "1"); // limit
+                if (0 != cursorNetworkAttributes.getCount()) {
+                    cursorNetworkAttributes.close();
+                    continue;
+                }
+                cursorNetworkAttributes.close();
+                final Cursor cursorPrivateData = db.query(
+                        // table name
+                        PrivateDataContract.TABLENAME,
+                        // column name
+                        new String[] { PrivateDataContract.COLNAME_L2KEY },
+                        null, // selection
+                        null, // selectionArgs
+                        null, // groupBy
+                        null, // having
+                        null, // orderBy
+                        "1"); // limit
+                if (0 != cursorPrivateData.getCount()) {
+                    cursorPrivateData.close();
+                    continue;
+                }
+                cursorPrivateData.close();
+                db.setTransactionSuccessful();
+                return;
+            } catch (SQLiteException e) {
+                Log.e(TAG, "Could not wipe the data in database", e);
+            } finally {
+                db.endTransaction();
+            }
+        }
+    }
+
+    /**
      * The following is a horrible hack that is necessary because the Android SQLite API does not
      * have a way to query a binary blob. This, almost certainly, is an overlook.
      *
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index 8312dfe..ad2bae8 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -410,8 +410,12 @@
         });
     }
 
+    /**
+     * Wipe the data in IpMemoryStore database upon network factory reset.
+     */
     @Override
     public void factoryReset() {
+        mExecutor.execute(() -> IpMemoryStoreDatabase.wipeDataUponNetworkReset(mDb));
     }
 
     /** Get db size threshold. */
diff --git a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index 87346e5..c1d6a05 100644
--- a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -62,6 +62,7 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -77,7 +78,11 @@
     private static final int DEFAULT_TIMEOUT_MS = 5000;
     private static final int LONG_TIMEOUT_MS = 30000;
     private static final int FAKE_KEY_COUNT = 20;
+    private static final long LEASE_EXPIRY_NULL = -1L;
+    private static final int MTU_NULL = -1;
     private static final String[] FAKE_KEYS;
+    private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
+            -128, 0, 89, 112, 91, -34 };
     static {
         FAKE_KEYS = new String[FAKE_KEY_COUNT];
         for (int i = 0; i < FAKE_KEYS.length; ++i) {
@@ -124,6 +129,29 @@
         mDbFile.delete();
     }
 
+    /** Helper method to build test network attributes */
+    private static NetworkAttributes.Builder buildTestNetworkAttributes(
+            final Inet4Address ipAddress, final long expiry, final String hint,
+            final List<InetAddress> dnsServers, final int mtu) {
+        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+        if (null != ipAddress) {
+            na.setAssignedV4Address(ipAddress);
+        }
+        if (LEASE_EXPIRY_NULL != expiry) {
+            na.setAssignedV4AddressExpiry(expiry);
+        }
+        if (null != hint) {
+            na.setGroupHint(hint);
+        }
+        if (null != dnsServers) {
+            na.setDnsAddresses(dnsServers);
+        }
+        if (MTU_NULL != mtu) {
+            na.setMtu(mtu);
+        }
+        return na;
+    }
+
     /** Helper method to make a vanilla IOnStatusListener */
     private IOnStatusListener onStatus(Consumer<Status> functor) {
         return new IOnStatusListener() {
@@ -265,7 +293,7 @@
         }
     }
 
-    // Helper methods to factorize more boilerplate
+    // Helper method to store network attributes to database
     private void storeAttributes(final String l2Key, final NetworkAttributes na) {
         storeAttributes("Did not complete storing attributes", l2Key, na);
     }
@@ -278,15 +306,28 @@
                 })));
     }
 
+    // Helper method to store blob data to database
+    private void storeBlobOrFail(final String l2Key, final Blob b, final byte[] data) {
+        storeBlobOrFail("Did not complete storing private data", l2Key, b, data);
+    }
+    private void storeBlobOrFail(final String timeoutMessage, final String l2Key, final Blob b,
+            final byte[] data) {
+        b.data = data;
+        doLatched(timeoutMessage, latch -> mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME,
+                b, onStatus(status -> {
+                    assertTrue("Store status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    latch.countDown();
+                })));
+    }
+
     /** Insert large data that db size will be over threshold for maintenance test usage. */
     private void insertFakeDataAndOverThreshold() {
         try {
-            final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-            na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-            na.setGroupHint("hint1");
-            na.setMtu(219);
-            na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
-            final byte[] data = new byte[]{-3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34};
+            final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+                    (Inet4Address) Inet4Address.getByName("1.2.3.4"), LEASE_EXPIRY_NULL,
+                    "hint1", Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")),
+                    219);
             final long time = System.currentTimeMillis() - 1;
             for (int i = 0; i < 1000; i++) {
                 int errorCode = IpMemoryStoreDatabase.storeNetworkAttributes(
@@ -298,7 +339,8 @@
                 assertEquals(errorCode, Status.SUCCESS);
 
                 errorCode = IpMemoryStoreDatabase.storeBlob(
-                        mService.mDb, "fakeKey" + i, TEST_CLIENT_ID, TEST_DATA_NAME, data);
+                        mService.mDb, "fakeKey" + i, TEST_CLIENT_ID, TEST_DATA_NAME,
+                        TEST_BLOB_DATA);
                 assertEquals(errorCode, Status.SUCCESS);
             }
 
@@ -320,12 +362,10 @@
 
     @Test
     public void testNetworkAttributes() throws UnknownHostException {
-        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-        na.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
-        na.setGroupHint("hint1");
-        na.setMtu(219);
         final String l2Key = FAKE_KEYS[0];
+        final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+                (Inet4Address) Inet4Address.getByName("1.2.3.4"),
+                System.currentTimeMillis() + 7_200_000, "hint1", null, 219);
         NetworkAttributes attributes = na.build();
         storeAttributes(l2Key, attributes);
 
@@ -420,16 +460,9 @@
 
     @Test
     public void testPrivateData() {
-        final Blob b = new Blob();
-        b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
         final String l2Key = FAKE_KEYS[0];
-        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();
-                        })));
+        final Blob b = new Blob();
+        storeBlobOrFail(l2Key, b, TEST_BLOB_DATA);
 
         doLatched("Did not complete retrieving private data", latch ->
                 mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
@@ -564,11 +597,10 @@
 
     @Test
     public void testIsSameNetwork() throws UnknownHostException {
-        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-        na.setGroupHint("hint1");
-        na.setMtu(219);
-        na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
+        final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+                (Inet4Address) Inet4Address.getByName("1.2.3.4"), LEASE_EXPIRY_NULL,
+                "hint1", Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")),
+                219);
 
         storeAttributes(FAKE_KEYS[0], na.build());
         // 0 and 1 have identical attributes
@@ -601,7 +633,6 @@
                 })));
     }
 
-
     @Test
     public void testFullMaintenance() {
         insertFakeDataAndOverThreshold();
@@ -660,4 +691,45 @@
         // still be over the threshold.
         assertTrue(mService.isDbSizeOverThreshold());
     }
+
+    @Test
+    public void testFactoryReset() throws UnknownHostException {
+        final String l2Key = FAKE_KEYS[0];
+
+        // store network attributes
+        final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+                (Inet4Address) Inet4Address.getByName("1.2.3.4"),
+                System.currentTimeMillis() + 7_200_000, "hint1", null, 219);
+        storeAttributes(l2Key, na.build());
+
+        // store private data blob
+        final Blob b = new Blob();
+        storeBlobOrFail(l2Key, b, TEST_BLOB_DATA);
+
+        // wipe all data in Database
+        mService.factoryReset();
+
+        // retrieved network attributes should be null
+        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);
+                            assertNull(attr);
+                            latch.countDown();
+                        })));
+
+        // retrieved private data blob should be null
+        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);
+                            assertNull(data);
+                            latch.countDown();
+                        })));
+    }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1816681..0a1dbff 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -77,6 +77,7 @@
 import android.net.ISocketKeepaliveCallback;
 import android.net.ITetheringEventCallback;
 import android.net.InetAddresses;
+import android.net.IpMemoryStore;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.LinkProperties.CompareResult;
@@ -6888,6 +6889,9 @@
 
         final int userId = UserHandle.getCallingUserId();
 
+        final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
+        ipMemoryStore.factoryReset();
+
         // Turn airplane mode off
         setAirplaneMode(false);
 
diff --git a/services/net/java/android/net/IpMemoryStoreClient.java b/services/net/java/android/net/IpMemoryStoreClient.java
index 3d56202..014b528 100644
--- a/services/net/java/android/net/IpMemoryStoreClient.java
+++ b/services/net/java/android/net/IpMemoryStoreClient.java
@@ -212,4 +212,16 @@
                             null, null, null));
         }
     }
+
+    /**
+     * Wipe the data in the database upon network factory reset.
+     */
+    public void factoryReset() {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.factoryReset()));
+        } catch (ExecutionException m) {
+            Log.e(TAG, "Error executing factory reset", m);
+        }
+    }
 }
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
index 6e69b34..b81ca36 100644
--- a/tests/net/java/android/net/IpMemoryStoreTest.java
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -321,4 +321,11 @@
                 eq(TEST_OTHER_DATA_NAME), any());
         assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
     }
+
+    @Test
+    public void testFactoryReset() throws RemoteException {
+        startIpMemoryStore(true /* supplyService */);
+        mStore.factoryReset();
+        verify(mMockService, times(1)).factoryReset();
+    }
 }