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 private final IDataAccessService mDataAccessService; 41 42 /** @hide */ LocalDataImpl(@onNull IDataAccessService binder)43 public LocalDataImpl(@NonNull IDataAccessService binder) { 44 mDataAccessService = Objects.requireNonNull(binder); 45 } 46 47 @Override @Nullable get(@onNull String key)48 public byte[] get(@NonNull String key) { 49 final long startTimeMillis = System.currentTimeMillis(); 50 Objects.requireNonNull(key); 51 Bundle params = new Bundle(); 52 params.putString(Constants.EXTRA_LOOKUP_KEYS, key); 53 return handleLookupRequest( 54 Constants.DATA_ACCESS_OP_LOCAL_DATA_LOOKUP, params, 55 Constants.API_NAME_LOCAL_DATA_GET, startTimeMillis); 56 } 57 58 @Override @Nullable put(@onNull String key, byte[] value)59 public byte[] put(@NonNull String key, byte[] value) { 60 final long startTimeMillis = System.currentTimeMillis(); 61 Objects.requireNonNull(key); 62 Bundle params = new Bundle(); 63 params.putString(Constants.EXTRA_LOOKUP_KEYS, key); 64 params.putParcelable(Constants.EXTRA_VALUE, new ByteArrayParceledSlice(value)); 65 return handleLookupRequest( 66 Constants.DATA_ACCESS_OP_LOCAL_DATA_PUT, params, 67 Constants.API_NAME_LOCAL_DATA_PUT, startTimeMillis); 68 } 69 70 @Override @Nullable remove(@onNull String key)71 public byte[] remove(@NonNull String key) { 72 final long startTimeMillis = System.currentTimeMillis(); 73 Objects.requireNonNull(key); 74 Bundle params = new Bundle(); 75 params.putString(Constants.EXTRA_LOOKUP_KEYS, key); 76 return handleLookupRequest( 77 Constants.DATA_ACCESS_OP_LOCAL_DATA_REMOVE, params, 78 Constants.API_NAME_LOCAL_DATA_REMOVE, startTimeMillis); 79 } 80 handleLookupRequest( int op, Bundle params, int apiName, long startTimeMillis)81 private byte[] handleLookupRequest( 82 int op, Bundle params, int apiName, long startTimeMillis) { 83 int responseCode = Constants.STATUS_SUCCESS; 84 try { 85 CallbackResult callbackResult = handleAsyncRequest(op, params); 86 if (callbackResult.mErrorCode != 0) { 87 responseCode = callbackResult.mErrorCode; 88 return null; 89 } 90 Bundle result = callbackResult.mResult; 91 if (result == null 92 || result.getParcelable(Constants.EXTRA_RESULT, ByteArrayParceledSlice.class) 93 == null) { 94 responseCode = Constants.STATUS_SUCCESS_EMPTY_RESULT; 95 return null; 96 } 97 ByteArrayParceledSlice data = 98 result.getParcelable(Constants.EXTRA_RESULT, ByteArrayParceledSlice.class); 99 return data.getByteArray(); 100 } catch (RuntimeException e) { 101 responseCode = Constants.STATUS_INTERNAL_ERROR; 102 throw e; 103 } finally { 104 try { 105 mDataAccessService.logApiCallStats( 106 apiName, 107 System.currentTimeMillis() - startTimeMillis, 108 responseCode); 109 } catch (Exception e) { 110 sLogger.d(e, TAG + ": failed to log metrics"); 111 } 112 } 113 } 114 115 @Override @NonNull keySet()116 public Set<String> keySet() { 117 final long startTimeMillis = System.currentTimeMillis(); 118 int responseCode = Constants.STATUS_SUCCESS; 119 try { 120 CallbackResult callbackResult = 121 handleAsyncRequest(Constants.DATA_ACCESS_OP_LOCAL_DATA_KEYSET, Bundle.EMPTY); 122 if (callbackResult.mErrorCode != 0) { 123 responseCode = callbackResult.mErrorCode; 124 return Collections.emptySet(); 125 } 126 Bundle result = callbackResult.mResult; 127 if (result == null 128 || result.getSerializable(Constants.EXTRA_RESULT, HashSet.class) == null) { 129 responseCode = Constants.STATUS_SUCCESS_EMPTY_RESULT; 130 return Collections.emptySet(); 131 } 132 return result.getSerializable(Constants.EXTRA_RESULT, HashSet.class); 133 } catch (RuntimeException e) { 134 responseCode = Constants.STATUS_INTERNAL_ERROR; 135 throw e; 136 } finally { 137 try { 138 mDataAccessService.logApiCallStats( 139 Constants.API_NAME_LOCAL_DATA_KEYSET, 140 System.currentTimeMillis() - startTimeMillis, 141 responseCode); 142 } catch (Exception e) { 143 sLogger.d(e, TAG + ": failed to log metrics"); 144 } 145 } 146 } 147 148 @Override getTableId()149 public int getTableId() { 150 return ModelId.TABLE_ID_LOCAL_DATA; 151 } 152 handleAsyncRequest(int op, Bundle params)153 private CallbackResult handleAsyncRequest(int op, Bundle params) { 154 // Blocks on the calling thread and waits for the response from the data access service. 155 try { 156 BlockingQueue<CallbackResult> asyncResult = new ArrayBlockingQueue<>(1); 157 mDataAccessService.onRequest( 158 op, 159 params, 160 new IDataAccessServiceCallback.Stub() { 161 @Override 162 public void onSuccess(@NonNull Bundle result) { 163 asyncResult.add(new CallbackResult(result, /* errorCode= */ 0)); 164 } 165 166 @Override 167 public void onError(int errorCode) { 168 asyncResult.add(new CallbackResult(/* result= */ null, errorCode)); 169 } 170 }); 171 return asyncResult.take(); 172 } catch (InterruptedException | RemoteException e) { 173 sLogger.e(TAG + ": Failed to retrieve result from localData", e); 174 throw new IllegalStateException(e); 175 } 176 } 177 178 private static class CallbackResult { 179 private final Bundle mResult; 180 private final int mErrorCode; 181 CallbackResult(Bundle result, int errorCode)182 private CallbackResult(Bundle result, int errorCode) { 183 mResult = result; 184 mErrorCode = errorCode; 185 } 186 } 187 } 188