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