blob: f86e288bba8678e83bcaacb9a85008da32149209 [file] [log] [blame]
/*
* Copyright (C) 2023 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_INTERNAL_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
import static com.android.adservices.AdServicesCommon.ACTION_ADID_PROVIDER_SERVICE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_REMOTE_EXCEPTION;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__AD_ID;
import android.adservices.adid.AdId;
import android.adservices.adid.AdIdProviderService;
import android.adservices.adid.GetAdIdResult;
import android.adservices.adid.IAdIdProviderService;
import android.adservices.adid.IGetAdIdCallback;
import android.adservices.adid.IGetAdIdProviderCallback;
import android.adservices.common.UpdateAdIdRequest;
import android.annotation.NonNull;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.RemoteException;
import com.android.adservices.LogUtil;
import com.android.adservices.ServiceBinder;
import com.android.adservices.errorlogging.ErrorLogUtil;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.shared.common.ApplicationContextSingleton;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.concurrent.ThreadSafe;
/** The class to manage AdId Cache. */
@ThreadSafe
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class AdIdCacheManager {
@VisibleForTesting static final String SHARED_PREFS_IAPC = "adservices_PPAPI_IAPC";
private static final String SHARE_PREFS_CACHED_AD_ID_KEY = "cached_ad_id";
private static final String SHARE_PREFS_CACHED_LAT_KEY = "cached_lat";
// A default AdId to indicate the AdId is not set in the cache.
private static final AdId UNSET_AD_ID =
new AdId(/* adId */ "", /* limitAdTrackingEnabled */ false);
private static final Object SINGLETON_LOCK = new Object();
private static AdIdCacheManager sSingleton;
private final ServiceBinder<IAdIdProviderService> mServiceBinder;
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
private final Context mContext;
@VisibleForTesting
public AdIdCacheManager(Context context) {
mServiceBinder =
ServiceBinder.getServiceBinder(
context,
ACTION_ADID_PROVIDER_SERVICE,
IAdIdProviderService.Stub::asInterface);
mContext = context.getApplicationContext();
}
/** Gets a singleton instance of {@link AdIdCacheManager}. */
public static AdIdCacheManager getInstance() {
synchronized (SINGLETON_LOCK) {
if (sSingleton == null) {
sSingleton = new AdIdCacheManager(ApplicationContextSingleton.get());
}
return sSingleton;
}
}
/**
* Fetches AdId from AdId cache.
*
* <p>If the AdId cache is empty, make a call to {@link AdIdProviderService} to fetch the AdId
* from the device.
*
* @param packageName is the app package name
* @param appUid is the current UID of the calling app;
* @param callback is used to return the result.
*/
public void getAdId(String packageName, int appUid, IGetAdIdCallback callback) {
AdId adId = getAdIdInStorage();
// If the cache is not set, get the AdId from the provider.
if (adId.equals(UNSET_AD_ID)) {
getAdIdFromProvider(packageName, appUid, callback);
return;
}
LogUtil.v(
"AdIdCacheManager.getAdId from cache for %s, %d, adId = %s",
packageName, appUid, adId);
setCallbackOnResult(callback, adId, /* shouldUnbindFromProvider= */ false);
}
/**
* Updates the AdId cache.
*
* @param updateAdIdRequest the request contains new AdId to update the cache.
*/
public void updateAdId(@NonNull UpdateAdIdRequest updateAdIdRequest) {
LogUtil.v("AdIdCacheManager.updateAdId to %s", updateAdIdRequest);
mReadWriteLock.writeLock().lock();
try {
setAdIdInStorage(
new AdId(
updateAdIdRequest.getAdId(),
updateAdIdRequest.isLimitAdTrackingEnabled()));
} finally {
mReadWriteLock.writeLock().unlock();
}
}
/**
* Gets adId from {@link AdIdProviderService}.
*
* @param packageName is the app package name
* @param appUid is the current UID of the calling app;
* @param callback is used to return the result.
*/
@VisibleForTesting
void getAdIdFromProvider(String packageName, int appUid, IGetAdIdCallback callback) {
LogUtil.v("AdIdCacheManager.getAdId for %s, %d from Provider", packageName, appUid);
IAdIdProviderService service = getService();
// Unable to find adId provider service. Return default values.
if (service == null) {
setCallbackOnResult(
callback,
new AdId(AdId.ZERO_OUT, false),
/* shouldUnbindFromProvider= */ false);
return;
}
try {
// Call adId provider service method to retrieve the AdId and lat.
service.getAdIdProvider(
appUid,
packageName,
new IGetAdIdProviderCallback.Stub() {
@Override
public void onResult(GetAdIdResult resultParcel) {
AdId resultAdId =
new AdId(resultParcel.getAdId(), resultParcel.isLatEnabled());
setAdIdInStorage(resultAdId);
LogUtil.v("AdId fetched from Provider is %s", resultAdId);
setCallbackOnResult(
callback, resultAdId, /* shouldUnbindFromProvider= */ true);
}
@Override
public void onError(String errorMessage) {
try {
callback.onError(STATUS_INTERNAL_ERROR);
LogUtil.e("Get AdId Error Message from Provider: %s", errorMessage);
} catch (RemoteException e) {
logRemoteException(e);
} finally {
// Since we are sure, the provider service api has returned,
// we can safely unbind the adId provider service.
unbindFromService();
}
}
});
} catch (RemoteException e) {
logRemoteException(e);
try {
callback.onError(STATUS_INTERNAL_ERROR);
} catch (RemoteException err) {
logRemoteException(err);
} finally {
unbindFromService();
}
}
}
@VisibleForTesting
void setAdIdInStorage(@NonNull AdId adId) {
// TODO(b/300147424): Remove the flag once the feature is rolled out.
if (!FlagsFactory.getFlags().getAdIdCacheEnabled()) {
LogUtil.d("Ad Id Cache is not enabled. Set nothing.");
return;
}
Objects.requireNonNull(adId);
mReadWriteLock.writeLock().lock();
try {
SharedPreferences.Editor editor = getSharedPreferences().edit();
editor.putString(SHARE_PREFS_CACHED_AD_ID_KEY, adId.getAdId())
.putBoolean(SHARE_PREFS_CACHED_LAT_KEY, adId.isLimitAdTrackingEnabled());
if (!editor.commit()) {
// Since we are sure that the provider service api has returned, we can safely
// unbind the adId provider service.
LogUtil.e("Failed to update Ad Id Cache!");
}
} finally {
mReadWriteLock.writeLock().unlock();
}
}
@VisibleForTesting
AdId getAdIdInStorage() {
// TODO(b/300147424): Remove the flag once the feature is rolled out.
if (!FlagsFactory.getFlags().getAdIdCacheEnabled()) {
LogUtil.d("Ad Id Cache is not enabled. Return default value.");
return UNSET_AD_ID;
}
mReadWriteLock.readLock().lock();
try {
SharedPreferences sharedPreferences = getSharedPreferences();
String adIdString =
sharedPreferences.getString(
SHARE_PREFS_CACHED_AD_ID_KEY, UNSET_AD_ID.getAdId());
boolean isLatEnabled =
sharedPreferences.getBoolean(
SHARE_PREFS_CACHED_LAT_KEY, UNSET_AD_ID.isLimitAdTrackingEnabled());
return new AdId(adIdString, isLatEnabled);
} finally {
mReadWriteLock.readLock().unlock();
}
}
@NonNull
@VisibleForTesting
IAdIdProviderService getService() {
return mServiceBinder.getService();
}
@NonNull
@VisibleForTesting
SharedPreferences getSharedPreferences() {
return mContext.getSharedPreferences(SHARED_PREFS_IAPC, Context.MODE_PRIVATE);
}
@NonNull
private void unbindFromService() {
mServiceBinder.unbindFromService();
}
// Logs the RemoteException to both logcat and CEL.
private void logRemoteException(RemoteException e) {
LogUtil.e(e, "RemoteException");
ErrorLogUtil.e(
e,
AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_REMOTE_EXCEPTION,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__AD_ID);
}
private void setCallbackOnResult(
@NonNull IGetAdIdCallback callback,
@NonNull AdId resultAdId,
boolean shouldUnbindFromProvider) {
Objects.requireNonNull(callback);
Objects.requireNonNull(resultAdId);
GetAdIdResult result =
new GetAdIdResult.Builder()
.setStatusCode(STATUS_SUCCESS)
.setErrorMessage("")
.setAdId(resultAdId.getAdId())
.setLatEnabled(resultAdId.isLimitAdTrackingEnabled())
.build();
try {
callback.onResult(result);
} catch (RemoteException e) {
logRemoteException(e);
} finally {
if (shouldUnbindFromProvider) {
// Since we are sure that the provider service api has returned, we can safely
// unbind the adId provider service.
unbindFromService();
}
}
}
}