1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.adservices.ondevicepersonalization;
18 
19 import android.adservices.ondevicepersonalization.aidl.IDataAccessService;
20 import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.Bundle;
24 import android.os.RemoteException;
25 
26 import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
27 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
28 
29 import java.util.Collections;
30 import java.util.HashSet;
31 import java.util.Objects;
32 import java.util.Set;
33 import java.util.concurrent.ArrayBlockingQueue;
34 import java.util.concurrent.BlockingQueue;
35 
36 /** @hide */
37 public class LocalDataImpl implements MutableKeyValueStore {
38     private static final String TAG = "LocalDataImpl";
39     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
40     @NonNull
41     IDataAccessService mDataAccessService;
42 
43     /** @hide */
LocalDataImpl(@onNull IDataAccessService binder)44     public LocalDataImpl(@NonNull IDataAccessService binder) {
45         mDataAccessService = Objects.requireNonNull(binder);
46     }
47 
48     @Override @Nullable
get(@onNull String key)49     public byte[] get(@NonNull String key) {
50         final long startTimeMillis = System.currentTimeMillis();
51         Objects.requireNonNull(key);
52         Bundle params = new Bundle();
53         params.putString(Constants.EXTRA_LOOKUP_KEYS, key);
54         return handleLookupRequest(
55                 Constants.DATA_ACCESS_OP_LOCAL_DATA_LOOKUP, params,
56                 Constants.API_NAME_LOCAL_DATA_GET, startTimeMillis);
57     }
58 
59     @Override @Nullable
put(@onNull String key, byte[] value)60     public byte[] put(@NonNull String key, byte[] value) {
61         final long startTimeMillis = System.currentTimeMillis();
62         Objects.requireNonNull(key);
63         Bundle params = new Bundle();
64         params.putString(Constants.EXTRA_LOOKUP_KEYS, key);
65         params.putParcelable(Constants.EXTRA_VALUE, new ByteArrayParceledSlice(value));
66         return handleLookupRequest(
67                 Constants.DATA_ACCESS_OP_LOCAL_DATA_PUT, params,
68                 Constants.API_NAME_LOCAL_DATA_PUT, startTimeMillis);
69     }
70 
71     @Override @Nullable
remove(@onNull String key)72     public byte[] remove(@NonNull String key) {
73         final long startTimeMillis = System.currentTimeMillis();
74         Objects.requireNonNull(key);
75         Bundle params = new Bundle();
76         params.putString(Constants.EXTRA_LOOKUP_KEYS, key);
77         return handleLookupRequest(
78                 Constants.DATA_ACCESS_OP_LOCAL_DATA_REMOVE, params,
79                 Constants.API_NAME_LOCAL_DATA_REMOVE, startTimeMillis);
80     }
81 
handleLookupRequest( int op, Bundle params, int apiName, long startTimeMillis)82     private byte[] handleLookupRequest(
83             int op, Bundle params, int apiName, long startTimeMillis) {
84         int responseCode = Constants.STATUS_SUCCESS;
85         try {
86             Bundle result = handleAsyncRequest(op, params);
87             ByteArrayParceledSlice data = result.getParcelable(
88                     Constants.EXTRA_RESULT, ByteArrayParceledSlice.class);
89             if (null == data) {
90                 return null;
91             }
92             return data.getByteArray();
93         } catch (RuntimeException e) {
94             responseCode = Constants.STATUS_INTERNAL_ERROR;
95             throw e;
96         } finally {
97             try {
98                 mDataAccessService.logApiCallStats(
99                         apiName,
100                         System.currentTimeMillis() - startTimeMillis,
101                         responseCode);
102             } catch (Exception e) {
103                 sLogger.d(e, TAG + ": failed to log metrics");
104             }
105         }
106     }
107 
108     @Override @NonNull
keySet()109     public Set<String> keySet() {
110         final long startTimeMillis = System.currentTimeMillis();
111         int responseCode = Constants.STATUS_SUCCESS;
112         try {
113             Bundle result = handleAsyncRequest(Constants.DATA_ACCESS_OP_LOCAL_DATA_KEYSET,
114                     Bundle.EMPTY);
115             HashSet<String> resultSet =
116                     result.getSerializable(Constants.EXTRA_RESULT, HashSet.class);
117             if (null == resultSet) {
118                 return Collections.emptySet();
119             }
120             return resultSet;
121         } catch (RuntimeException e) {
122             responseCode = Constants.STATUS_INTERNAL_ERROR;
123             throw e;
124         } finally {
125             try {
126                 mDataAccessService.logApiCallStats(
127                         Constants.API_NAME_LOCAL_DATA_KEYSET,
128                         System.currentTimeMillis() - startTimeMillis,
129                         responseCode);
130             } catch (Exception e) {
131                 sLogger.d(e, TAG + ": failed to log metrics");
132             }
133         }
134     }
135 
136     @Override
getTableId()137     public int getTableId() {
138         return ModelId.TABLE_ID_LOCAL_DATA;
139     }
140 
handleAsyncRequest(int op, Bundle params)141     private Bundle handleAsyncRequest(int op, Bundle params) {
142         try {
143             BlockingQueue<Bundle> asyncResult = new ArrayBlockingQueue<>(1);
144             mDataAccessService.onRequest(
145                     op,
146                     params,
147                     new IDataAccessServiceCallback.Stub() {
148                         @Override
149                         public void onSuccess(@NonNull Bundle result) {
150                             if (result != null) {
151                                 asyncResult.add(result);
152                             } else {
153                                 asyncResult.add(Bundle.EMPTY);
154                             }
155                         }
156 
157                         @Override
158                         public void onError(int errorCode) {
159                             asyncResult.add(Bundle.EMPTY);
160                         }
161                     });
162             return asyncResult.take();
163         } catch (InterruptedException | RemoteException e) {
164             sLogger.e(TAG + ": Failed to retrieve result from localData", e);
165             throw new IllegalStateException(e);
166         }
167     }
168 }
169