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