1 /*
2  * Copyright (C) 2022 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 package android.adservices.appsetid;
17 
18 import static android.adservices.common.AdServicesStatusUtils.SERVICE_UNAVAILABLE_ERROR_MESSAGE;
19 
20 import android.adservices.common.AdServicesStatusUtils;
21 import android.adservices.common.CallerMetadata;
22 import android.adservices.common.SandboxedSdkContextUtils;
23 import android.annotation.CallbackExecutor;
24 import android.annotation.NonNull;
25 import android.app.sdksandbox.SandboxedSdkContext;
26 import android.content.Context;
27 import android.os.Build;
28 import android.os.OutcomeReceiver;
29 import android.os.RemoteException;
30 import android.os.SystemClock;
31 
32 import androidx.annotation.RequiresApi;
33 
34 import com.android.adservices.AdServicesCommon;
35 import com.android.adservices.LogUtil;
36 import com.android.adservices.ServiceBinder;
37 import com.android.adservices.shared.common.exception.ServiceUnavailableException;
38 
39 import java.util.Objects;
40 import java.util.concurrent.Executor;
41 
42 /**
43  * AppSetIdManager provides APIs for app and ad-SDKs to access appSetId for non-monetizing purpose.
44  */
45 @RequiresApi(Build.VERSION_CODES.S)
46 public class AppSetIdManager {
47     /**
48      * Service used for registering AppSetIdManager in the system service registry.
49      *
50      * @hide
51      */
52     public static final String APPSETID_SERVICE = "appsetid_service";
53 
54     /* When an app calls the AppSetId API directly, it sets the SDK name to empty string. */
55     static final String EMPTY_SDK = "";
56 
57     private Context mContext;
58     private ServiceBinder<IAppSetIdService> mServiceBinder;
59 
60     /**
61      * Factory method for creating an instance of AppSetIdManager.
62      *
63      * @param context The {@link Context} to use
64      * @return A {@link AppSetIdManager} instance
65      */
66     @NonNull
get(@onNull Context context)67     public static AppSetIdManager get(@NonNull Context context) {
68         // On T+, context.getSystemService() does more than just call constructor.
69         return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
70                 ? context.getSystemService(AppSetIdManager.class)
71                 : new AppSetIdManager(context);
72     }
73 
74     /**
75      * Create AppSetIdManager
76      *
77      * @hide
78      */
AppSetIdManager(Context context)79     public AppSetIdManager(Context context) {
80         // In case the AppSetIdManager is initiated from inside a sdk_sandbox process the fields
81         // will be immediately rewritten by the initialize method below.
82         initialize(context);
83     }
84 
85     /**
86      * Initializes {@link AppSetIdManager} with the given {@code context}.
87      *
88      * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
89      * For more information check the javadoc on the {@link
90      * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
91      *
92      * @hide
93      * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
94      */
initialize(Context context)95     public AppSetIdManager initialize(Context context) {
96         mContext = context;
97         mServiceBinder =
98                 ServiceBinder.getServiceBinder(
99                         context,
100                         AdServicesCommon.ACTION_APPSETID_SERVICE,
101                         IAppSetIdService.Stub::asInterface);
102         return this;
103     }
104 
105     @NonNull
getService( @allbackExecutor Executor executor, OutcomeReceiver<AppSetId, Exception> callback)106     private IAppSetIdService getService(
107             @CallbackExecutor Executor executor, OutcomeReceiver<AppSetId, Exception> callback) {
108         IAppSetIdService service = null;
109         try {
110             service = mServiceBinder.getService();
111 
112             // Throw ServiceUnavailableException and set it to the callback.
113             if (service == null) {
114                 throw new ServiceUnavailableException(SERVICE_UNAVAILABLE_ERROR_MESSAGE);
115             }
116         } catch (RuntimeException e) {
117             LogUtil.e(e, "Failed binding to AppSetId service");
118             executor.execute(() -> callback.onError(e));
119         }
120 
121         return service;
122     }
123 
124     @NonNull
getContext()125     private Context getContext() {
126         return mContext;
127     }
128 
129     /**
130      * Retrieve the AppSetId.
131      *
132      * @param executor The executor to run callback.
133      * @param callback The callback that's called after appsetid are available or an error occurs.
134      */
135     @NonNull
getAppSetId( @onNull @allbackExecutor Executor executor, @NonNull OutcomeReceiver<AppSetId, Exception> callback)136     public void getAppSetId(
137             @NonNull @CallbackExecutor Executor executor,
138             @NonNull OutcomeReceiver<AppSetId, Exception> callback) {
139         Objects.requireNonNull(executor);
140         Objects.requireNonNull(callback);
141         CallerMetadata callerMetadata =
142                 new CallerMetadata.Builder()
143                         .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
144                         .build();
145 
146         String appPackageName = "";
147         String sdkPackageName = "";
148         // First check if context is SandboxedSdkContext or not
149         Context getAppSetIdRequestContext = getContext();
150         SandboxedSdkContext requestContext =
151                 SandboxedSdkContextUtils.getAsSandboxedSdkContext(getAppSetIdRequestContext);
152         if (requestContext != null) {
153             sdkPackageName = requestContext.getSdkPackageName();
154             appPackageName = requestContext.getClientPackageName();
155         } else { // This is the case without the Sandbox.
156             appPackageName = getAppSetIdRequestContext.getPackageName();
157         }
158         try {
159             IAppSetIdService service = getService(executor, callback);
160             if (service == null) {
161                 LogUtil.d("Unable to find AppSetId service");
162                 return;
163             }
164 
165             service.getAppSetId(
166                     new GetAppSetIdParam.Builder()
167                             .setAppPackageName(appPackageName)
168                             .setSdkPackageName(sdkPackageName)
169                             .build(),
170                     callerMetadata,
171                     new IGetAppSetIdCallback.Stub() {
172                         @Override
173                         public void onResult(GetAppSetIdResult resultParcel) {
174                             executor.execute(
175                                     () -> {
176                                         if (resultParcel.isSuccess()) {
177                                             callback.onResult(
178                                                     new AppSetId(
179                                                             resultParcel.getAppSetId(),
180                                                             resultParcel.getAppSetIdScope()));
181                                         } else {
182                                             callback.onError(
183                                                     AdServicesStatusUtils.asException(
184                                                             resultParcel));
185                                         }
186                                     });
187                         }
188 
189                         @Override
190                         public void onError(int resultCode) {
191                             executor.execute(
192                                     () ->
193                                             callback.onError(
194                                                     AdServicesStatusUtils.asException(resultCode)));
195                         }
196                     });
197         } catch (RemoteException e) {
198             LogUtil.e("RemoteException", e);
199             callback.onError(e);
200         }
201     }
202 
203     /**
204      * If the service is in an APK (as opposed to the system service), unbind it from the service to
205      * allow the APK process to die.
206      *
207      * @hide
208      */
209     // TODO: change to @VisibleForTesting
unbindFromService()210     public void unbindFromService() {
211         mServiceBinder.unbindFromService();
212     }
213 }
214