blob: 3782c4aa4808851851b0b1d3497a6387f52eb704 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.adservices.service.adid;
import static android.adservices.common.AdServicesStatusUtils.STATUS_BACKGROUND_CALLER;
import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_PERMISSION_NOT_REQUESTED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__ADID;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_ADID;
import android.adservices.adid.GetAdIdParam;
import android.adservices.adid.IAdIdService;
import android.adservices.adid.IGetAdIdCallback;
import android.adservices.common.AdServicesStatusUtils;
import android.adservices.common.CallerMetadata;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AllowLists;
import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException;
import com.android.adservices.service.common.PermissionHelper;
import com.android.adservices.service.common.SdkRuntimeUtil;
import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesStatsLog;
import com.android.adservices.service.stats.ApiCallStats;
import com.android.adservices.service.stats.Clock;
import java.util.concurrent.Executor;
/**
* Implementation of {@link IAdIdService}.
*
* @hide
*/
public class AdIdServiceImpl extends IAdIdService.Stub {
private final Context mContext;
private final AdIdWorker mAdIdWorker;
private static final Executor sBackgroundExecutor = AdServicesExecutors.getBackgroundExecutor();
private final AdServicesLogger mAdServicesLogger;
private final Clock mClock;
private final Flags mFlags;
private final Throttler mThrottler;
private final AppImportanceFilter mAppImportanceFilter;
public AdIdServiceImpl(
Context context,
AdIdWorker adidWorker,
AdServicesLogger adServicesLogger,
Clock clock,
Flags flags,
Throttler throttler,
AppImportanceFilter appImportanceFilter) {
mContext = context;
mAdIdWorker = adidWorker;
mAdServicesLogger = adServicesLogger;
mClock = clock;
mFlags = flags;
mThrottler = throttler;
mAppImportanceFilter = appImportanceFilter;
}
@Override
public void getAdId(
@NonNull GetAdIdParam adIdParam,
@NonNull CallerMetadata callerMetadata,
@NonNull IGetAdIdCallback callback) {
if (isThrottled(adIdParam, callback)) return;
final long startServiceTime = mClock.elapsedRealtime();
final String packageName = adIdParam.getAppPackageName();
final String sdkPackageName = adIdParam.getSdkPackageName();
// We need to save the Calling Uid before offloading to the background executor. Otherwise
// the Binder.getCallingUid will return the PPAPI process Uid. This also needs to be final
// since it's used in the lambda.
final int callingUid = Binder.getCallingUidOrThrow();
// Check the permission in the same thread since we're looking for caller's permissions.
// Note: The permission check uses sdk package name since PackageManager checks if the
// permission is declared in the manifest of that package name.
boolean hasAdIdPermission =
PermissionHelper.hasAdIdPermission(
mContext, Process.isSdkSandboxUid(callingUid), sdkPackageName);
sBackgroundExecutor.execute(
() -> {
int resultCode = STATUS_SUCCESS;
try {
if (!canCallerInvokeAdIdService(
hasAdIdPermission, adIdParam, callingUid, callback)) {
return;
}
mAdIdWorker.getAdId(packageName, callingUid, callback);
} catch (Exception e) {
LogUtil.e(e, "Unable to send result to the callback");
resultCode = STATUS_INTERNAL_ERROR;
} finally {
long binderCallStartTimeMillis = callerMetadata.getBinderElapsedTimestamp();
long serviceLatency = mClock.elapsedRealtime() - startServiceTime;
// Double it to simulate the return binder time is same to call binder time
long binderLatency = (startServiceTime - binderCallStartTimeMillis) * 2;
final int apiLatency = (int) (serviceLatency + binderLatency);
mAdServicesLogger.logApiCallStats(
new ApiCallStats.Builder()
.setCode(AdServicesStatsLog.AD_SERVICES_API_CALLED)
.setApiClass(AD_SERVICES_API_CALLED__API_CLASS__ADID)
.setApiName(AD_SERVICES_API_CALLED__API_NAME__GET_ADID)
.setAppPackageName(packageName)
.setSdkPackageName(sdkPackageName)
.setLatencyMillisecond(apiLatency)
.setResultCode(resultCode)
.build());
}
});
}
// Throttle the AdId API.
// Return true if we should throttle (don't allow the API call).
private boolean isThrottled(GetAdIdParam adIdParam, IGetAdIdCallback callback) {
boolean throttled =
!mThrottler.tryAcquire(
Throttler.ApiKey.ADID_API_APP_PACKAGE_NAME, adIdParam.getAppPackageName());
if (throttled) {
LogUtil.e("Rate Limit Reached for ADID_API");
try {
callback.onError(STATUS_RATE_LIMIT_REACHED);
} catch (RemoteException e) {
LogUtil.e(e, "Fail to call the callback on Rate Limit Reached.");
} finally {
return true;
}
}
return false;
}
// Enforce whether caller is from foreground.
private void enforceForeground(int callingUid) {
// If caller calls Topics API from Sandbox, regard it as foreground.
// Also enable a flag to force switch on/off this enforcing.
if (Process.isSdkSandboxUid(callingUid) || !mFlags.getEnforceForegroundStatusForAdId()) {
return;
}
// Call utility method in AppImportanceFilter to enforce foreground status
// Throw WrongCallingApplicationStateException if the assertion fails.
mAppImportanceFilter.assertCallerIsInForeground(
callingUid, AD_SERVICES_API_CALLED__API_NAME__GET_ADID, null);
}
/**
* Check whether caller can invoke the AdId API. The caller is not allowed to do it when one of
* the following occurs:
*
* <ul>
* <li>Permission was not requested.
* <li>Caller is not allowed - not present in the allowed list.
* </ul>
*
* @param sufficientPermission boolean which tells whether caller has sufficient permissions.
* @param adIdParam {@link GetAdIdParam} to get information about the request.
* @param callback {@link IGetAdIdCallback} to invoke when caller is not allowed.
* @return true if caller is allowed to invoke AdId API, false otherwise.
*/
private boolean canCallerInvokeAdIdService(
boolean sufficientPermission,
GetAdIdParam adIdParam,
int callingUid,
IGetAdIdCallback callback) {
// Enforce caller calls AdId API from foreground.
try {
enforceForeground(callingUid);
} catch (WrongCallingApplicationStateException backgroundCaller) {
invokeCallbackWithStatus(
callback, STATUS_BACKGROUND_CALLER, backgroundCaller.getMessage());
return false;
}
if (!sufficientPermission) {
invokeCallbackWithStatus(
callback,
STATUS_PERMISSION_NOT_REQUESTED,
"Unauthorized caller. Permission not requested.");
return false;
}
// This needs to access PhFlag which requires READ_DEVICE_CONFIG which
// is not granted for binder thread. So we have to check it with one
// of non-binder thread of the PPAPI.
boolean appCanUsePpapi =
AllowLists.isPackageAllowListed(
mFlags.getPpapiAppAllowList(), adIdParam.getAppPackageName());
if (!appCanUsePpapi) {
invokeCallbackWithStatus(
callback,
STATUS_CALLER_NOT_ALLOWED,
"Unauthorized caller. Caller is not allowed.");
return false;
}
// Check whether calling package belongs to the callingUid
int resultCode =
enforceCallingPackageBelongsToUid(adIdParam.getAppPackageName(), callingUid);
if (resultCode != STATUS_SUCCESS) {
invokeCallbackWithStatus(callback, resultCode, "Caller is not authorized.");
return false;
}
return true;
}
private void invokeCallbackWithStatus(
IGetAdIdCallback callback,
@AdServicesStatusUtils.StatusCode int statusCode,
String message) {
LogUtil.e(message);
try {
callback.onError(statusCode);
} catch (RemoteException e) {
LogUtil.e(e, String.format("Fail to call the callback. %s", message));
}
}
// Enforce that the callingPackage has the callingUid.
private int enforceCallingPackageBelongsToUid(String callingPackage, int callingUid) {
int appCallingUid = SdkRuntimeUtil.getCallingAppUid(callingUid);
int packageUid;
try {
packageUid = mContext.getPackageManager().getPackageUid(callingPackage, /* flags */ 0);
} catch (PackageManager.NameNotFoundException e) {
LogUtil.e(e, callingPackage + " not found");
return STATUS_UNAUTHORIZED;
}
if (packageUid != appCallingUid) {
LogUtil.e(callingPackage + " does not belong to uid " + callingUid);
return STATUS_UNAUTHORIZED;
}
return STATUS_SUCCESS;
}
}