Implement the IpMemoryStore APIs that retrieve or store the network event count.

Bug: 329010113
Test: TH
Change-Id: I911129127c4467e72c30b608a91ff086e3784f1a
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 2c031e3..004ec09 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -172,7 +172,7 @@
             enabled: false,
         },
     },
-    imports: ["ipmemorystore-aidl-interfaces-V10"],
+    imports: ["ipmemorystore-aidl-interfaces-V11"],
     // TODO: have tethering depend on networkstack-client and set visibility to private
     visibility: [
         "//system/tools/aidl/build",
@@ -183,43 +183,43 @@
         // Remove old networkstack aidl interface version info that is no longer used.
         {
             version: "13",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "14",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "15",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "16",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "17",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "18",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "19",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "20",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "21",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
         {
             version: "22",
-            imports: ["ipmemorystore-aidl-interfaces-V10"],
+            imports: ["ipmemorystore-aidl-interfaces-V11"],
         },
 
     ],
@@ -231,7 +231,7 @@
     sdk_version: "system_current",
     min_sdk_version: "30",
     static_libs: [
-        "ipmemorystore-aidl-interfaces-V10-java",
+        "ipmemorystore-aidl-interfaces-V11-java",
         "networkstack-aidl-interfaces-V22-java",
     ],
     visibility: ["//packages/modules/NetworkStack:__subpackages__"],
diff --git a/common/networkstackclient/src/android/net/IpMemoryStoreClient.java b/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
index a1c5694..8b93124 100644
--- a/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
+++ b/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
@@ -25,6 +25,7 @@
 import android.net.ipmemorystore.OnDeleteStatusListener;
 import android.net.ipmemorystore.OnL2KeyResponseListener;
 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.OnNetworkEventCountRetrievedListener;
 import android.net.ipmemorystore.OnSameL3NetworkResponseListener;
 import android.net.ipmemorystore.OnStatusListener;
 import android.net.ipmemorystore.Status;
@@ -289,4 +290,64 @@
             Log.e(TAG, "Error executing factory reset", m);
         }
     }
+
+    /**
+     * Retrieve the specific network event counts for a given cluster and event type since one or
+     * more timestamps in the past.
+     *
+     * @param cluster The cluster to query.
+     * @param sinceTimes An array of timestamps in the past. The query will return an array of
+     *                   equal size. Each element in the array will contain the number of network
+     *                   events between the corresponding timestamp and the current time, e.g. query
+     *                   since the last week and/or the last day.
+     * @param eventTypes An array of network event types to query, which can be one or more of the
+     *                   above NETWORK_EVENT constants.
+     * @param listener The listener that will be invoked to return the answer.
+     * returns (through the listener) The event counts associated with the query, or an empty array
+     *                                if the query failed.
+     */
+    public void retrieveNetworkEventCount(@NonNull final String cluster,
+            @NonNull final long[] sinceTimes,
+            @NonNull final int[] eventTypes,
+            @Nullable final OnNetworkEventCountRetrievedListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.retrieveNetworkEventCount(cluster, sinceTimes, eventTypes,
+                            OnNetworkEventCountRetrievedListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error retrieving network event count",
+                    () -> listener.onNetworkEventCountRetrieved(
+                            new Status(Status.ERROR_UNKNOWN),
+                            new int[0]) /* empty counts */);
+        }
+    }
+
+    /**
+     * Store a specific network event to database for a given cluster.
+     *
+     * @param cluster The cluster representing a notion of network group (e.g., BSSIDs with the
+     *                same SSID).
+     * @param timestamp The timestamp {@link System.currentTimeMillis} when a specific network
+     *                  event occurred.
+     * @param expiry The timestamp {@link System.currentTimeMillis} when a specific network
+     *               event stored in the database expires, e.g. it might be one week from now.
+     * @param eventType One of the NETWORK_EVENT constants above.
+     * @param listener A listener that will be invoked to inform of the completion of this call.
+     * returns (through the listener) A status to indicate success or failure.
+     */
+    public void storeNetworkEvent(@NonNull final String cluster,
+            final long timestamp,
+            final long expiry,
+            final int eventType,
+            @Nullable final OnStatusListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.storeNetworkEvent(cluster, timestamp, expiry, eventType,
+                            OnStatusListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            if (null == listener) return;
+            ignoringRemoteException("Error storing network event",
+                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
+        }
+    }
 }
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/OnNetworkEventCountRetrievedListener.java b/common/networkstackclient/src/android/net/ipmemorystore/OnNetworkEventCountRetrievedListener.java
new file mode 100644
index 0000000..1c30150
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/OnNetworkEventCountRetrievedListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return specific network event counts.
+ * @hide
+ */
+public interface OnNetworkEventCountRetrievedListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+    void onNetworkEventCountRetrieved(Status status, int[] counts);
+
+    /** Converts this OnNetworkEventCountRetrievedListener to a parcelable object */
+    @NonNull
+    static IOnNetworkEventCountRetrievedListener toAIDL(
+            @NonNull final OnNetworkEventCountRetrievedListener listener) {
+        return new IOnNetworkEventCountRetrievedListener.Stub() {
+            @Override
+            public void onNetworkEventCountRetrieved(
+                    final StatusParcelable statusParcelable,
+                    final int[] counts) {
+                // NonNull, but still don't crash the system server if null
+                if (null != listener) {
+                    listener.onNetworkEventCountRetrieved(new Status(statusParcelable), counts);
+                }
+            }
+
+            @Override
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+
+            @Override
+            public String getInterfaceHash() {
+                return this.HASH;
+            }
+        };
+    }
+}
diff --git a/src/com/android/networkstack/ipmemorystore/IpMemoryStoreDatabase.java b/src/com/android/networkstack/ipmemorystore/IpMemoryStoreDatabase.java
index 9e7df82..e6dd9cd 100644
--- a/src/com/android/networkstack/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/src/com/android/networkstack/ipmemorystore/IpMemoryStoreDatabase.java
@@ -749,6 +749,19 @@
         }
     }
 
+    static int storeNetworkEvent(@NonNull final SQLiteDatabase db, @NonNull final String cluster,
+            final long timestamp, final long expiry, final int eventType) {
+        // TODO: implement this.
+        return Status.SUCCESS;
+    }
+
+    static int[] retrieveNetworkEventCount(@NonNull final SQLiteDatabase db,
+            @NonNull final String cluster, @NonNull final long[] sinceTimes,
+            @NonNull final int[] eventTypes) {
+        // TODO: implement this.
+        return new int[0];
+    }
+
     // Helper methods
     private static String getString(final Cursor cursor, final String columnName) {
         final int columnIndex = cursor.getColumnIndex(columnName);
diff --git a/src/com/android/networkstack/ipmemorystore/IpMemoryStoreService.java b/src/com/android/networkstack/ipmemorystore/IpMemoryStoreService.java
index 76ed56c..a05c5cb 100644
--- a/src/com/android/networkstack/ipmemorystore/IpMemoryStoreService.java
+++ b/src/com/android/networkstack/ipmemorystore/IpMemoryStoreService.java
@@ -32,6 +32,7 @@
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
 import android.net.ipmemorystore.IOnL2KeyResponseListener;
 import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.IOnNetworkEventCountRetrievedListener;
 import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusAndCountListener;
 import android.net.ipmemorystore.IOnStatusListener;
@@ -484,6 +485,95 @@
         });
     }
 
+    /**
+     * Retrieve the specific network event counts for a given cluster and event type since one or
+     * more timestamps in the past.
+     *
+     * @param cluster The cluster to query.
+     * @param sinceTimes An array of timestamps in the past. The query will return an array of
+     *                   equal size. Each element in the array will contain the number of network
+     *                   events between the corresponding timestamp and the current time, e.g. query
+     *                   since the last week and/or the last day.
+     * @param eventTypes An array of network event types to query, which can be one or more of the
+     *                   above NETWORK_EVENT constants.
+     * @param listener The listener that will be invoked to return the answer.
+     * returns (through the listener) The event counts associated with the query, or an empty array
+     *                                if the query failed.
+     */
+    @Override
+    public void retrieveNetworkEventCount(@NonNull final String cluster,
+            @NonNull final long[] sinceTimes,
+            @NonNull final int[] eventTypes,
+            @Nullable final IOnNetworkEventCountRetrievedListener listener) {
+        if (null == listener) return;
+        mExecutor.execute(() -> {
+            try {
+                if (null == cluster) {
+                    listener.onNetworkEventCountRetrieved(
+                            makeStatus(ERROR_ILLEGAL_ARGUMENT), new int[0] /* counts */);
+                    return;
+                }
+                if (null == mDb) {
+                    listener.onNetworkEventCountRetrieved(
+                            makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), new int[0] /* counts */);
+                    return;
+                }
+                try {
+                    final int[] counts = IpMemoryStoreDatabase.retrieveNetworkEventCount(mDb,
+                            cluster, sinceTimes, eventTypes);
+                    listener.onNetworkEventCountRetrieved(makeStatus(SUCCESS), counts);
+                } catch (final Exception e) {
+                    listener.onNetworkEventCountRetrieved(makeStatus(ERROR_GENERIC),
+                            new int[0] /* counts */);
+                }
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
+    }
+
+    /**
+     * Store a specific network event to database for a given cluster.
+     *
+     * @param cluster The cluster representing a notion of network group (e.g., BSSIDs with the
+     *                same SSID).
+     * @param timestamp The timestamp {@link System.currentTimeMillis} when a specific network
+     *                  event occurred.
+     * @param expiry The timestamp {@link System.currentTimeMillis} when a specific network
+     *               event stored in the database expires, e.g. it might be one week from now.
+     * @param eventType One of the NETWORK_EVENT constants above.
+     * @param listener A listener that will be invoked to inform of the completion of this call.
+     * returns (through the listener) A status to indicate success or failure.
+     */
+    @Override
+    public void storeNetworkEvent(@NonNull final String cluster,
+            final long timestamp,
+            final long expiry,
+            final int eventType,
+            @Nullable final IOnStatusListener listener) {
+        mExecutor.execute(() -> {
+            try {
+                if (null == cluster) {
+                    listener.onComplete(makeStatus(ERROR_ILLEGAL_ARGUMENT));
+                    return;
+                }
+                if (null == mDb) {
+                    listener.onComplete(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED));
+                    return;
+                }
+                try {
+                    final int code = IpMemoryStoreDatabase.storeNetworkEvent(mDb, cluster,
+                            timestamp, expiry, eventType);
+                    if (null != listener) listener.onComplete(makeStatus(code));
+                } catch (final Exception e) {
+                    if (null != listener) listener.onComplete(makeStatus(ERROR_GENERIC));
+                }
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
+    }
+
     /** Get db size threshold. */
     @VisibleForTesting
     protected int getDbSizeThreshold() {