content: /*
   * Copyright (C) 2019 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;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.Context;
  import android.net.ipmemorystore.Blob;
  import android.net.ipmemorystore.NetworkAttributes;
  import android.net.ipmemorystore.OnBlobRetrievedListener;
  import android.net.ipmemorystore.OnL2KeyResponseListener;
  import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
  import android.net.ipmemorystore.OnSameL3NetworkResponseListener;
  import android.net.ipmemorystore.OnStatusListener;
  import android.net.ipmemorystore.Status;
  import android.os.RemoteException;
  import android.util.Log;
  
  import java.util.concurrent.ExecutionException;
  import java.util.function.Consumer;
  
  /**
   * service used to communicate with the ip memory store service in network stack,
   * which is running in a separate module.
   * @hide
   */
  public abstract class IpMemoryStoreClient {
      private static final String TAG = IpMemoryStoreClient.class.getSimpleName();
      private final Context mContext;
  
      public IpMemoryStoreClient(@NonNull final Context context) {
          if (context == null) throw new IllegalArgumentException("missing context");
          mContext = context;
      }
  
      protected abstract void runWhenServiceReady(Consumer<IIpMemoryStore> cb)
              throws ExecutionException;
  
      @FunctionalInterface
      private interface ThrowingRunnable {
          void run() throws RemoteException;
      }
  
      private void ignoringRemoteException(ThrowingRunnable r) {
          ignoringRemoteException("Failed to execute remote procedure call", r);
      }
  
      private void ignoringRemoteException(String message, ThrowingRunnable r) {
          try {
              r.run();
          } catch (RemoteException e) {
              Log.e(TAG, message, e);
          }
      }
  
      /**
       * Store network attributes for a given L2 key.
       * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
       * calling findL2Key with the attributes and storing in the returned value.
       *
       * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
       *              key and only care about grouping can pass a unique ID here like the ones
       *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
       *              relevance of such a network will lead to it being evicted soon if it's not
       *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
       * @param attributes The attributes for this network.
       * @param listener A listener that will be invoked to inform of the completion of this call,
       *                 or null if the client is not interested in learning about success/failure.
       * 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.
       */
      public void storeNetworkAttributes(@NonNull final String l2Key,
              @NonNull final NetworkAttributes attributes,
              @Nullable final OnStatusListener listener) {
          try {
              runWhenServiceReady(service -> ignoringRemoteException(
                      () -> service.storeNetworkAttributes(l2Key, attributes.toParcelable(),
                              OnStatusListener.toAIDL(listener))));
          } catch (ExecutionException m) {
              ignoringRemoteException("Error storing network attributes",
                      () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
          }
      }
  
      /**
       * Store a binary blob associated with an L2 key and a name.
       *
       * @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 listener A listener to inform of the completion of this call, or null if the client
       *        is not interested in learning about success/failure.
       * Through the listener, returns a status to indicate success or failure.
       */
      public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
              @NonNull final String name, @NonNull final Blob data,
              @Nullable final OnStatusListener listener) {
          try {
              runWhenServiceReady(service -> ignoringRemoteException(
                      () -> service.storeBlob(l2Key, clientId, name, data,
                              OnStatusListener.toAIDL(listener))));
          } catch (ExecutionException m) {
              ignoringRemoteException("Error storing blob",
                      () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
          }
      }
  
      /**
       * Returns the best L2 key associated with the attributes.
       *
       * This will find a record that would be in the same group as the passed attributes. This is
       * useful to choose the key for storing a sample or private data when the L2 key is not known.
       * If multiple records are group-close to these attributes, the closest match is returned.
       * If multiple records have the same closeness, the one with the smaller (unicode codepoint
       * order) L2 key is returned.
       * If no record matches these attributes, null is returned.
       *
       * @param attributes The attributes of the network to find.
       * @param listener The listener that will be invoked to return the answer.
       * Through the listener, returns the L2 key if one matched, or null.
       */
      public void findL2Key(@NonNull final NetworkAttributes attributes,
              @NonNull final OnL2KeyResponseListener listener) {
          try {
              runWhenServiceReady(service -> ignoringRemoteException(
                      () -> service.findL2Key(attributes.toParcelable(),
                              OnL2KeyResponseListener.toAIDL(listener))));
          } catch (ExecutionException m) {
              ignoringRemoteException("Error finding L2 Key",
                      () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null));
          }
      }
  
      /**
       * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
       * to the same L3 network. Group-closeness is used to determine this.
       *
       * @param l2Key1 The key for the first network.
       * @param l2Key2 The key for the second network.
       * @param listener The listener that will be invoked to return the answer.
       * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
       */
      public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
              @NonNull final OnSameL3NetworkResponseListener listener) {
          try {
              runWhenServiceReady(service -> ignoringRemoteException(
                      () -> service.isSameNetwork(l2Key1, l2Key2,
                              OnSameL3NetworkResponseListener.toAIDL(listener))));
          } catch (ExecutionException m) {
              ignoringRemoteException("Error checking for network sameness",
                      () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null));
          }
      }
  
      /**
       * Retrieve the network attributes for a key.
       * If no record is present for this key, this will return null attributes.
       *
       * @param l2Key The key of the network to query.
       * @param listener The listener that will be invoked to return the answer.
       * Through the listener, returns the network attributes and the L2 key associated with
       *         the query.
       */
      public void retrieveNetworkAttributes(@NonNull final String l2Key,
              @NonNull final OnNetworkAttributesRetrievedListener listener) {
          try {
              runWhenServiceReady(service -> ignoringRemoteException(
                      () -> service.retrieveNetworkAttributes(l2Key,
                              OnNetworkAttributesRetrievedListener.toAIDL(listener))));
          } catch (ExecutionException m) {
              ignoringRemoteException("Error retrieving network attributes",
                      () -> listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN),
                              null, null));
          }
      }
  
      /**
       * Retrieve previously stored private data.
       * If no data was stored for this L2 key and name this will return null.
       *
       * @param l2Key The L2 key.
       * @param clientId The id of the client that stored this data.
       * @param name The name of the data.
       * @param listener The listener that will be invoked to return the answer.
       * Through the listener, returns the private data (or null), with the L2 key
       *         and the name of the data associated with the query.
       */
      public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
              @NonNull final String name, @NonNull final OnBlobRetrievedListener listener) {
          try {
              runWhenServiceReady(service -> ignoringRemoteException(
                      () -> service.retrieveBlob(l2Key, clientId, name,
                              OnBlobRetrievedListener.toAIDL(listener))));
          } catch (ExecutionException m) {
              ignoringRemoteException("Error retrieving blob",
                      () -> listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN),
                              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);
          }
      }
  }
  