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.signals; 18 19 import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_PROTECTED_SIGNALS; 20 21 import android.adservices.common.AdServicesStatusUtils; 22 import android.adservices.common.FledgeErrorResponse; 23 import android.adservices.common.SandboxedSdkContextUtils; 24 import android.annotation.CallbackExecutor; 25 import android.annotation.FlaggedApi; 26 import android.annotation.NonNull; 27 import android.annotation.RequiresApi; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SuppressLint; 30 import android.app.sdksandbox.SandboxedSdkContext; 31 import android.content.Context; 32 import android.os.Build; 33 import android.os.LimitExceededException; 34 import android.os.OutcomeReceiver; 35 import android.os.RemoteException; 36 37 import com.android.adservices.AdServicesCommon; 38 import com.android.adservices.LoggerFactory; 39 import com.android.adservices.ServiceBinder; 40 import com.android.adservices.flags.Flags; 41 42 import java.util.Objects; 43 import java.util.concurrent.Executor; 44 45 /** ProtectedSignalsManager provides APIs for apps and ad-SDKs to manage their protected signals. */ 46 @FlaggedApi(Flags.FLAG_PROTECTED_SIGNALS_ENABLED) 47 @RequiresApi(Build.VERSION_CODES.S) 48 public class ProtectedSignalsManager { 49 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 50 /** 51 * Constant that represents the service name for {@link ProtectedSignalsManager} to be used in 52 * {@link android.adservices.AdServicesFrameworkInitializer#registerServiceWrappers} 53 * 54 * @hide 55 */ 56 public static final String PROTECTED_SIGNALS_SERVICE = "protected_signals_service"; 57 58 @NonNull private Context mContext; 59 @NonNull private ServiceBinder<IProtectedSignalsService> mServiceBinder; 60 61 /** 62 * Factory method for creating an instance of ProtectedSignalsManager. 63 * 64 * @param context The {@link Context} to use 65 * @return A {@link ProtectedSignalsManager} instance 66 */ 67 @SuppressLint("ManagerLookup") 68 @NonNull 69 // TODO(b/303896680): Investigate why this lint was not triggered for similar managers get(@onNull Context context)70 public static ProtectedSignalsManager get(@NonNull Context context) { 71 // On T+, context.getSystemService() does more than just call constructor. 72 return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 73 ? context.getSystemService(ProtectedSignalsManager.class) 74 : new ProtectedSignalsManager(context); 75 } 76 77 /** 78 * Create a service binder ProtectedSignalsManager 79 * 80 * @hide 81 */ ProtectedSignalsManager(@onNull Context context)82 public ProtectedSignalsManager(@NonNull Context context) { 83 Objects.requireNonNull(context); 84 85 // In case the ProtectedSignalsManager is initiated from inside a sdk_sandbox process the 86 // fields will be immediately rewritten by the initialize method below. 87 initialize(context); 88 } 89 90 /** 91 * Initializes {@link ProtectedSignalsManager} with the given {@code context}. 92 * 93 * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context. 94 * For more information check the javadoc on the {@link 95 * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}. 96 * 97 * @hide 98 * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry 99 */ initialize(@onNull Context context)100 public ProtectedSignalsManager initialize(@NonNull Context context) { 101 Objects.requireNonNull(context); 102 103 mContext = context; 104 mServiceBinder = 105 ServiceBinder.getServiceBinder( 106 context, 107 AdServicesCommon.ACTION_PROTECTED_SIGNALS_SERVICE, 108 IProtectedSignalsService.Stub::asInterface); 109 return this; 110 } 111 112 @NonNull getService()113 IProtectedSignalsService getService() { 114 IProtectedSignalsService service = mServiceBinder.getService(); 115 if (service == null) { 116 throw new IllegalStateException("Unable to find the service"); 117 } 118 return service; 119 } 120 121 /** 122 * The updateSignals API will retrieve a JSON from the URI that describes which signals to add 123 * or remove. This API also allows registering the encoder endpoint. The endpoint is used to 124 * download an encoding logic, which enables encoding the signals. 125 * 126 * <p>The top level keys for the JSON must correspond to one of 5 commands: 127 * 128 * <p>"put" - Adds a new signal, overwriting any existing signals with the same key. The value 129 * for this is a JSON object where the keys are base 64 strings corresponding to the key to put 130 * for and the values are base 64 string corresponding to the value to put. 131 * 132 * <p>"append" - Appends a new signal/signals to a time series of signals, removing the oldest 133 * signals to make room for the new ones if the size of the series exceeds the given maximum. 134 * The value for this is a JSON object where the keys are base 64 strings corresponding to the 135 * key to append to and the values are objects with two fields: "values" and "maxSignals" . 136 * "values" is a list of base 64 strings corresponding to signal values to append to the time 137 * series. "maxSignals" is the maximum number of values that are allowed in this timeseries. If 138 * the current number of signals associated with the key exceeds maxSignals the oldest signals 139 * will be removed. Note that you can append to a key added by put. Not that appending more than 140 * the maximum number of values will cause a failure. 141 * 142 * <p>"put_if_not_present" - Adds a new signal only if there are no existing signals with the 143 * same key. The value for this is a JSON object where the keys are base 64 strings 144 * corresponding to the key to put for and the values are base 64 string corresponding to the 145 * value to put. 146 * 147 * <p>"remove" - Removes the signal for a key. The value of this is a list of base 64 strings 148 * corresponding to the keys of signals that should be deleted. 149 * 150 * <p>"update_encoder" - Provides an action to update the endpoint, and a URI which can be used 151 * to retrieve an encoding logic. The sub-key for providing an update action is "action" and the 152 * values currently supported are: 153 * 154 * <ol> 155 * <li>"REGISTER" : Registers the encoder endpoint if provided for the first time or 156 * overwrites the existing one with the newly provided endpoint. Providing the "endpoint" 157 * is required for the "REGISTER" action. 158 * </ol> 159 * 160 * <p>The sub-key for providing an encoder endpoint is "endpoint" and the value is the URI 161 * string for the endpoint. 162 * 163 * <p>On success, the onResult method of the provided OutcomeReceiver will be called with an 164 * empty Object. This Object has no significance and is used merely as a placeholder. 165 * 166 * <p>Key may only be operated on by one command per JSON. If two command attempt to operate on 167 * the same key, this method will through an {@link IllegalArgumentException} 168 * 169 * <p>This call fails with an {@link SecurityException} if 170 * 171 * <ol> 172 * <li>the {@code ownerPackageName} is not calling app's package name and/or 173 * <li>the buyer is not authorized to use the API. 174 * </ol> 175 * 176 * <p>This call fails with an {@link IllegalArgumentException} if 177 * 178 * <ol> 179 * <li>The JSON retrieved from the server is not valid. 180 * <li>The provided URI is invalid. 181 * </ol> 182 * 183 * <p>This call fails with {@link LimitExceededException} if the calling package exceeds the 184 * allowed rate limits and is throttled. 185 * 186 * <p>This call fails with an {@link IllegalStateException} if an internal service error is 187 * encountered. 188 */ 189 @RequiresPermission(ACCESS_ADSERVICES_PROTECTED_SIGNALS) updateSignals( @onNull UpdateSignalsRequest updateSignalsRequest, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Object, Exception> receiver)190 public void updateSignals( 191 @NonNull UpdateSignalsRequest updateSignalsRequest, 192 @NonNull @CallbackExecutor Executor executor, 193 @NonNull OutcomeReceiver<Object, Exception> receiver) { 194 Objects.requireNonNull(updateSignalsRequest); 195 Objects.requireNonNull(executor); 196 Objects.requireNonNull(receiver); 197 198 try { 199 final IProtectedSignalsService service = getService(); 200 201 service.updateSignals( 202 new UpdateSignalsInput.Builder( 203 updateSignalsRequest.getUpdateUri(), getCallerPackageName()) 204 .build(), 205 new UpdateSignalsCallback.Stub() { 206 @Override 207 public void onSuccess() { 208 executor.execute(() -> receiver.onResult(new Object())); 209 } 210 211 @Override 212 public void onFailure(FledgeErrorResponse failureParcel) { 213 executor.execute( 214 () -> 215 receiver.onError( 216 AdServicesStatusUtils.asException( 217 failureParcel))); 218 } 219 }); 220 } catch (RemoteException e) { 221 sLogger.e(e, "Exception"); 222 receiver.onError(new IllegalStateException("Internal Error!", e)); 223 } 224 } 225 getCallerPackageName()226 private String getCallerPackageName() { 227 SandboxedSdkContext sandboxedSdkContext = 228 SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext); 229 return sandboxedSdkContext == null 230 ? mContext.getPackageName() 231 : sandboxedSdkContext.getClientPackageName(); 232 } 233 } 234