| /* |
| * 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.OnDeleteStatusListener; |
| 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) { |
| if (null == listener) return; |
| 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) { |
| if (null == listener) return; |
| 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 /* l2Key */)); |
| } |
| } |
| |
| /** |
| * 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 /* response */)); |
| } |
| } |
| |
| /** |
| * 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 /* l2Key */, null /* attributes */)); |
| } |
| } |
| |
| /** |
| * 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 /* l2Key */, null /* name */, null /* blob */)); |
| } |
| } |
| |
| /** |
| * Delete a single entry. |
| * |
| * @param l2Key The L2 key of the entry to delete. |
| * @param needWipe Whether the data must be wiped from disk immediately. This makes the |
| * operation vastly more expensive as the database files will have to be copied |
| * and created again from the old files (see sqlite3 VACUUM operation for |
| * details) and makes no functional difference; only pass true if security or |
| * privacy demands this data must be removed from disk immediately. |
| * Note that this can fail for storage reasons. The passed listener will then |
| * receive an appropriate error status with the number of deleted rows. |
| * @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. |
| * returns (through the listener) A status to indicate success and the number of deleted records |
| */ |
| public void delete(@NonNull final String l2Key, final boolean needWipe, |
| @Nullable final OnDeleteStatusListener listener) { |
| try { |
| runWhenServiceReady(service -> ignoringRemoteException(() -> |
| service.delete(l2Key, needWipe, OnDeleteStatusListener.toAIDL(listener)))); |
| } catch (ExecutionException m) { |
| if (null == listener) return; |
| ignoringRemoteException("Error deleting from the memory store", |
| () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN), |
| 0 /* deletedRecords */)); |
| } |
| } |
| |
| /** |
| * Delete all entries in a cluster. |
| * |
| * This method will delete all entries in the memory store that have the cluster attribute |
| * passed as an argument. |
| * |
| * @param cluster The cluster to delete. |
| * @param needWipe Whether the data must be wiped from disk immediately. This makes the |
| * operation vastly more expensive as the database files will have to be copied |
| * and created again from the old files (see sqlite3 VACUUM operation for |
| * details) and makes no functional difference; only pass true if security or |
| * privacy demands this data must be removed from disk immediately. |
| * Note that this can fail for storage reasons. The passed listener will then |
| * receive an appropriate error status with the number of deleted rows. |
| * @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. |
| * returns (through the listener) A status to indicate success and the number of deleted records |
| */ |
| public void deleteCluster(@NonNull final String cluster, final boolean needWipe, |
| @Nullable final OnDeleteStatusListener listener) { |
| try { |
| runWhenServiceReady(service -> ignoringRemoteException( |
| () -> service.deleteCluster(cluster, needWipe, |
| OnDeleteStatusListener.toAIDL(listener)))); |
| } catch (ExecutionException m) { |
| if (null == listener) return; |
| ignoringRemoteException("Error deleting from the memory store", |
| () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN), |
| 0 /* deletedRecords */)); |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| } |