summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-01-18 18:43:41 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-01-18 18:43:41 +0000
commit5b85c1579c31520b0a84b433d509e21ca38b502f (patch)
tree284ec116592def641a902ea37ac4f259151c1a3c
parente9c0be7c8ba51447e013a34a2f3252361659326b (diff)
parentdafcc735a8240c9c1c1bca1299a9ab217457cb23 (diff)
downloadAdServices-android14-mainline-extservices-release.tar.gz
Merge cherrypicks of ['googleplex-android-review.googlesource.com/25924473', 'googleplex-android-review.googlesource.com/25854039', 'googleplex-android-review.googlesource.com/25936868'] into sparse-11243045-L95500030001370613.aml_ext_341414010android14-mainline-extservices-release
SPARSE_CHANGE: I4ad0cf0a2edea32a476fcd0bd20c97f28761f3bc SPARSE_CHANGE: I6f912308c779cedeb4969dd4e2c7ca3187d9beb7 SPARSE_CHANGE: Ieab9c249b8201253273a672556a686d6a17f0596 Change-Id: I817542592ad7a7c0adc4a4becb87e7c0fe0a1698
-rw-r--r--adservices/service-core/java/com/android/adservices/service/Flags.java24
-rw-r--r--adservices/service-core/java/com/android/adservices/service/FlagsConstants.java2
-rw-r--r--adservices/service-core/java/com/android/adservices/service/PhFlags.java20
-rw-r--r--adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java167
-rw-r--r--adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java66
-rw-r--r--adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java13
-rw-r--r--adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java3
-rw-r--r--adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchConsentWorkerTest.java545
-rw-r--r--adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java47
-rw-r--r--adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorkerTest.java36
-rw-r--r--adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java33
11 files changed, 802 insertions, 154 deletions
diff --git a/adservices/service-core/java/com/android/adservices/service/Flags.java b/adservices/service-core/java/com/android/adservices/service/Flags.java
index b181901a1..57dd4300e 100644
--- a/adservices/service-core/java/com/android/adservices/service/Flags.java
+++ b/adservices/service-core/java/com/android/adservices/service/Flags.java
@@ -4503,4 +4503,28 @@ public interface Flags extends CommonFlags {
default int getBackgroundJobSamplingLoggingRate() {
return DEFAULT_BACKGROUND_JOB_SAMPLING_LOGGING_RATE;
}
+
+ /** Default value of the timeout for AppSearch write operations */
+ int DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS = 3000;
+
+ /**
+ * Gets the value of the timeout for AppSearch write operations, in milliseconds.
+ *
+ * @return the timeout, in milliseconds, for AppSearch write operations
+ */
+ default int getAppSearchWriteTimeout() {
+ return DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS;
+ }
+
+ /** Default value of the timeout for AppSearch read operations */
+ int DEFAULT_APPSEARCH_READ_TIMEOUT_MS = 750;
+
+ /**
+ * Gets the value of the timeout for AppSearch read operations, in milliseconds.
+ *
+ * @return the timeout, in milliseconds, for AppSearch read operations
+ */
+ default int getAppSearchReadTimeout() {
+ return DEFAULT_APPSEARCH_READ_TIMEOUT_MS;
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java b/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
index fb3ec484a..d3cc839a3 100644
--- a/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
+++ b/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
@@ -813,6 +813,8 @@ public final class FlagsConstants {
public static final String KEY_PPAPI_APP_SIGNATURE_ALLOW_LIST =
"ppapi_app_signature_allow_list";
+ public static final String KEY_APPSEARCH_WRITE_TIMEOUT_MS = "appsearch_write_timeout_ms";
+ public static final String KEY_APPSEARCH_READ_TIMEOUT_MS = "appsearch_read_timeout_ms";
public static final String KEY_APPSEARCH_WRITER_ALLOW_LIST_OVERRIDE =
"appsearch_writer_allow_list_override";
diff --git a/adservices/service-core/java/com/android/adservices/service/PhFlags.java b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
index 1ddb750f4..fb3115315 100644
--- a/adservices/service-core/java/com/android/adservices/service/PhFlags.java
+++ b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
@@ -16,6 +16,8 @@
package com.android.adservices.service;
+import static com.android.adservices.service.FlagsConstants.KEY_APPSEARCH_READ_TIMEOUT_MS;
+import static com.android.adservices.service.FlagsConstants.KEY_APPSEARCH_WRITE_TIMEOUT_MS;
import static com.android.adservices.service.FlagsConstants.KEY_ENCRYPTION_KEY_JOB_PERIOD_MS;
import static com.android.adservices.service.FlagsConstants.KEY_ENCRYPTION_KEY_JOB_REQUIRED_NETWORK_TYPE;
import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_MEASUREMENT_REPORT_AND_REGISTER_EVENT_API_ENABLED;
@@ -5500,6 +5502,8 @@ public final class PhFlags extends CommonPhFlags implements Flags {
+ KEY_MEASUREMENT_ENABLE_SESSION_STABLE_KILL_SWITCHES
+ " = "
+ getMeasurementEnableSessionStableKillSwitches());
+ writer.println("\t" + KEY_APPSEARCH_WRITE_TIMEOUT_MS + " = " + getAppSearchWriteTimeout());
+ writer.println("\t" + KEY_APPSEARCH_READ_TIMEOUT_MS + " = " + getAppSearchReadTimeout());
}
@VisibleForTesting
@@ -6526,4 +6530,20 @@ public final class PhFlags extends CommonPhFlags implements Flags {
return loggingRatio;
}
+
+ @Override
+ public int getAppSearchWriteTimeout() {
+ return DeviceConfig.getInt(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* name= */ FlagsConstants.KEY_APPSEARCH_WRITE_TIMEOUT_MS,
+ /* defaultValue= */ DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS);
+ }
+
+ @Override
+ public int getAppSearchReadTimeout() {
+ return DeviceConfig.getInt(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* name= */ FlagsConstants.KEY_APPSEARCH_READ_TIMEOUT_MS,
+ /* defaultValue= */ DEFAULT_APPSEARCH_READ_TIMEOUT_MS);
+ }
}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java
index 42c5c1ed8..bda9d1744 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java
@@ -65,15 +65,11 @@ import java.util.stream.Collectors;
* source of truth for S-. When a device upgrades from S- to T+, the consent is initialized from
* AppSearch.
*/
-// TODO(b/269798827): Enable for R.
@RequiresApi(Build.VERSION_CODES.S)
class AppSearchConsentWorker {
// At the worker level, we ensure that writes do not conflict with any other writes/reads.
private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock();
- // Timeout for AppSearch write query in milliseconds.
- private static final int TIMEOUT_MS = 2000;
-
private static final String CONSENT_DATABASE_NAME = "adservices_consent";
private static final String APP_CONSENT_DATABASE_NAME = "adservices_app_consent";
private static final String NOTIFICATION_DATABASE_NAME = "adservices_notification";
@@ -82,68 +78,69 @@ class AppSearchConsentWorker {
private static final String UX_STATES_DATABASE_NAME = "adservices-ux-states";
// Required for allowing AdServices apk access to read consent written by ExtServices module.
- private String mAdservicesPackageName;
- private Context mContext;
+ private final String mAdservicesPackageName;
- private ListenableFuture<AppSearchSession> mConsentSearchSession;
- private ListenableFuture<AppSearchSession> mAppConsentSearchSession;
- private ListenableFuture<AppSearchSession> mNotificationSearchSession;
- private ListenableFuture<AppSearchSession> mInteractionsSearchSession;
- private ListenableFuture<AppSearchSession> mTopicsSearchSession;
- private ListenableFuture<AppSearchSession> mUxStatesSearchSession;
+ // Timeout for AppSearch write query in milliseconds.
+ private final int mTimeoutMs;
+
+ private final ListenableFuture<AppSearchSession> mConsentSearchSession;
+ private final ListenableFuture<AppSearchSession> mAppConsentSearchSession;
+ private final ListenableFuture<AppSearchSession> mNotificationSearchSession;
+ private final ListenableFuture<AppSearchSession> mInteractionsSearchSession;
+ private final ListenableFuture<AppSearchSession> mTopicsSearchSession;
+ private final ListenableFuture<AppSearchSession> mUxStatesSearchSession;
// When reading across APKs, a GlobalSearchSession is needed, hence we use it when reading.
- private ListenableFuture<GlobalSearchSession> mGlobalSearchSession;
- private Executor mExecutor = AdServicesExecutors.getBackgroundExecutor();
+ private final ListenableFuture<GlobalSearchSession> mGlobalSearchSession;
+ private final Executor mExecutor = AdServicesExecutors.getBackgroundExecutor();
- private List<PackageIdentifier> mPackageIdentifiers = new ArrayList<>();
+ private final List<PackageIdentifier> mPackageIdentifiers = new ArrayList<>();
// There is a single user ID for a given process, so this class would not be instantiated
// across two user IDs.
- private String mUid = getUserIdentifierFromBinderCallingUid();
+ private final String mUid = getUserIdentifierFromBinderCallingUid();
private static final String SPLITTER = ",";
private AppSearchConsentWorker(@NonNull Context context) {
Objects.requireNonNull(context);
- mContext = context;
// We write with multiple schemas, so we need to initialize sessions per db.
mConsentSearchSession =
PlatformStorage.createSearchSessionAsync(
- new PlatformStorage.SearchContext.Builder(mContext, CONSENT_DATABASE_NAME)
+ new PlatformStorage.SearchContext.Builder(context, CONSENT_DATABASE_NAME)
.build());
mAppConsentSearchSession =
PlatformStorage.createSearchSessionAsync(
new PlatformStorage.SearchContext.Builder(
- mContext, APP_CONSENT_DATABASE_NAME)
+ context, APP_CONSENT_DATABASE_NAME)
.build());
mNotificationSearchSession =
PlatformStorage.createSearchSessionAsync(
new PlatformStorage.SearchContext.Builder(
- mContext, NOTIFICATION_DATABASE_NAME)
+ context, NOTIFICATION_DATABASE_NAME)
.build());
mInteractionsSearchSession =
PlatformStorage.createSearchSessionAsync(
new PlatformStorage.SearchContext.Builder(
- mContext, INTERACTIONS_DATABASE_NAME)
+ context, INTERACTIONS_DATABASE_NAME)
.build());
mTopicsSearchSession =
PlatformStorage.createSearchSessionAsync(
- new PlatformStorage.SearchContext.Builder(mContext, TOPICS_DATABASE_NAME)
+ new PlatformStorage.SearchContext.Builder(context, TOPICS_DATABASE_NAME)
.build());
mUxStatesSearchSession =
PlatformStorage.createSearchSessionAsync(
- new PlatformStorage.SearchContext.Builder(mContext, UX_STATES_DATABASE_NAME)
+ new PlatformStorage.SearchContext.Builder(context, UX_STATES_DATABASE_NAME)
.build());
// We use global session for reads since we may perform read on T+ AdServices package to
// restore consent data post OTA.
mGlobalSearchSession =
PlatformStorage.createGlobalSearchSessionAsync(
- new PlatformStorage.GlobalSearchContext.Builder(mContext).build());
+ new PlatformStorage.GlobalSearchContext.Builder(context).build());
// The package identifier of the AdServices package on T+ should always have access to read
// data written by AdExtServices package on S-.
- mAdservicesPackageName = getAdServicesPackageName(mContext);
+ mAdservicesPackageName = getAdServicesPackageName(context);
String shaCertsFlagValue = FlagsFactory.getFlags().getAdservicesApkShaCertificate();
for (String shaCert : shaCertsFlagValue.split(SPLITTER)) {
@@ -151,6 +148,8 @@ class AppSearchConsentWorker {
new PackageIdentifier(
mAdservicesPackageName, new Signature(shaCert).toByteArray()));
}
+
+ mTimeoutMs = FlagsFactory.getFlags().getAppSearchWriteTimeout();
}
/** Get an instance of AppSearchConsentWorker. */
@@ -175,8 +174,8 @@ class AppSearchConsentWorker {
/**
* Sets the consent for this user ID for this API type in AppSearch. If we do not get
- * confirmation that the write was successful, then we throw an exception so that user does not
- * incorrectly think that the consent is updated.
+ * confirmation that the write operation was successful, then we throw an exception so that user
+ * does not incorrectly think that the consent is updated.
*/
void setConsent(@NonNull String apiType, @NonNull Boolean consented) {
Objects.requireNonNull(apiType);
@@ -193,11 +192,11 @@ class AppSearchConsentWorker {
apiType,
consented.toString());
dao.writeData(mConsentSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote consent data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -235,10 +234,10 @@ class AppSearchConsentWorker {
mExecutor,
AppSearchAppConsentDao.getRowId(mUid, consentType),
AppSearchAppConsentDao.NAMESPACE)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to delete consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to delete consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -280,11 +279,11 @@ class AppSearchConsentWorker {
dao.setApps(apps);
}
dao.writeData(mAppConsentSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote app consent data to AppSearch (add): " + dao);
return true;
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
return false;
} finally {
READ_WRITE_LOCK.writeLock().unlock();
@@ -293,8 +292,8 @@ class AppSearchConsentWorker {
/**
* Removes an app from the list of apps with this consentType for this user. If we do not get
- * confirmation that the write was successful, then we throw an exception so that user does not
- * incorrectly think that the consent is updated.
+ * confirmation that the write operation was successful, then we throw an exception so that user
+ * does not incorrectly think that the consent is updated.
*/
void removeAppWithConsent(@NonNull String consentType, @NonNull String app) {
Objects.requireNonNull(consentType);
@@ -320,11 +319,11 @@ class AppSearchConsentWorker {
.filter(filterApp -> !filterApp.equals(app))
.collect(Collectors.toList()));
dao.writeData(mAppConsentSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote app consent data to AppSearch (remove): " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.readLock().unlock();
}
@@ -367,11 +366,11 @@ class AppSearchConsentWorker {
/* wasNotificationDisplayed= */ wasNotificationDisplayed,
/* wasGaUxNotificationDisplayed= */ wasGaUxNotificationDisplayed());
dao.writeData(mNotificationSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote notification data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write notification data to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write notification data to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -389,11 +388,11 @@ class AppSearchConsentWorker {
/* wasNotificationDisplayed= */ wasNotificationDisplayed(),
/* wasGaUxNotificationDisplayed= */ wasNotificationDisplayed);
dao.writeData(mNotificationSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote notification data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write notification data to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write notification data to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -426,11 +425,11 @@ class AppSearchConsentWorker {
apiType,
currentFeatureType.ordinal());
dao.writeData(mInteractionsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote feature type data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write interactions data to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write interactions data to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -466,11 +465,11 @@ class AppSearchConsentWorker {
apiType,
interaction);
dao.writeData(mInteractionsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote interactions data to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write interactions data to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write interactions data to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -512,11 +511,11 @@ class AppSearchConsentWorker {
dao.addBlockedTopic(topic);
}
dao.writeData(mTopicsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote topics consent data to AppSearch (block): " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -539,11 +538,11 @@ class AppSearchConsentWorker {
}
dao.removeBlockedTopic(topic);
dao.writeData(mTopicsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote topics consent data to AppSearch (unblock): " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -554,7 +553,7 @@ class AppSearchConsentWorker {
READ_WRITE_LOCK.writeLock().lock();
try {
// We don't do {read, modify, write} here since the DAO has no other information besides
- // blocked topics so we can rewrite it.
+ // blocked topics, so we can rewrite it.
AppSearchTopicsConsentDao dao =
new AppSearchTopicsConsentDao(
mUid,
@@ -564,11 +563,11 @@ class AppSearchConsentWorker {
List.of(),
List.of());
dao.writeData(mTopicsSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote topics consent data to AppSearch (clear): " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write consent to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write consent to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -606,7 +605,7 @@ class AppSearchConsentWorker {
AdServicesCommon.ADEXTSERVICES_PACKAGE_NAME_SUFFIX,
AdServicesCommon.ADSERVICES_APK_PACKAGE_NAME_SUFFIX);
}
- // If we don't know the AdServices package name, we can't do a write.
+ // If we don't know the AdServices package name, we can't write.
throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
}
@@ -637,11 +636,11 @@ class AppSearchConsentWorker {
}
dao.setAdIdEnabled(isAdIdEnabled);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the isAdIdEnabled bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the isAdIdEnabled to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the isAdIdEnabled to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -674,11 +673,11 @@ class AppSearchConsentWorker {
}
dao.setU18Account(isU18Account);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the isU18Account bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the isU18Account to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the isU18Account to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -711,11 +710,11 @@ class AppSearchConsentWorker {
}
dao.setEntryPointEnabled(isEntryPointEnabled);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the isEntryPointEnabled bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the isEntryPointEnabled to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the isEntryPointEnabled to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -748,11 +747,11 @@ class AppSearchConsentWorker {
}
dao.setAdultAccount(isAdultAccount);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the isAdultAccount bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the isAdultAccount to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the isAdultAccount to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -785,11 +784,11 @@ class AppSearchConsentWorker {
}
dao.setU18NotificationDisplayed(wasU18NotificationDisplayed);
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote the wasU18NotificationDisplayed bit to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write the wasU18NotificationDisplayed to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write the wasU18NotificationDisplayed to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -822,11 +821,11 @@ class AppSearchConsentWorker {
}
dao.setUx(ux.toString());
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote PrivacySandboxUx to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write PrivacySandboxUx to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write PrivacySandboxUx to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -865,11 +864,11 @@ class AppSearchConsentWorker {
}
dao.setEnrollmentChannel(enrollmentChannel.toString());
dao.writeData(mUxStatesSearchSession, mPackageIdentifiers, mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote PrivacySandboxUx to AppSearch: " + dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
- LogUtil.e("Failed to write PrivacySandboxUx to AppSearch ", e);
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ LogUtil.e(e, "Failed to write PrivacySandboxUx to AppSearch");
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
index 4fdf36f67..48d27d1c5 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
@@ -16,6 +16,8 @@
package com.android.adservices.service.appsearch;
+import static com.android.adservices.service.consent.ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE;
+
import android.annotation.NonNull;
import android.os.Build;
@@ -31,6 +33,7 @@ import androidx.appsearch.app.RemoveByDocumentIdRequest;
import androidx.appsearch.app.SearchResults;
import androidx.appsearch.app.SearchSpec;
import androidx.appsearch.app.SetSchemaRequest;
+import androidx.appsearch.app.SetSchemaResponse.MigrationFailure;
import androidx.appsearch.exceptions.AppSearchException;
import com.android.adservices.AdServicesCommon;
@@ -38,7 +41,6 @@ import com.android.adservices.LogUtil;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.AllowLists;
-import com.android.adservices.service.consent.ConsentConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.google.common.util.concurrent.FluentFuture;
@@ -58,12 +60,8 @@ import java.util.function.BiFunction;
* Base class for all data access objects for AppSearch. This class handles the common logic for
* reading from and writing to AppSearch.
*/
-// TODO(b/269798827): Enable for R.
@RequiresApi(Build.VERSION_CODES.S)
class AppSearchDao {
- // Timeout for AppSearch search query in milliseconds.
- private static final int TIMEOUT_MS = 500;
-
/**
* Iterate over the search results returned for the search query by AppSearch.
*
@@ -88,7 +86,7 @@ class AppSearchDao {
// Converts GenericDocument object to the type of object passed in cls.
documentResult = genericDocument.toDocumentClass(cls);
} catch (AppSearchException e) {
- LogUtil.e("Failed to convert GenericDocument to " + cls.getName(), e);
+ LogUtil.e(e, "Failed to convert GenericDocument to %s", cls.getName());
}
}
@@ -203,9 +201,11 @@ class AppSearchDao {
results -> iterateSearchResults(cls, results, executor),
executor)
.transform(result -> ((T) result), executor);
- return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ int timeout = FlagsFactory.getFlags().getAppSearchReadTimeout();
+ return future.get(timeout, TimeUnit.MILLISECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
- LogUtil.e("getConsent() Appsearch lookup failed with: ", e);
+ LogUtil.e(e, "getConsent() AppSearch lookup failed");
}
return null;
}
@@ -216,7 +216,7 @@ class AppSearchDao {
* we specify the packageIdentifier as that of the T+ AdServices APK, which after OTA, needs
* access to the data written before OTA. What is written is the subclass type of DAO.
*
- * @return the result of the write.
+ * @return the result of the write operation.
*/
FluentFuture<AppSearchBatchResult<String, Void>> writeData(
@NonNull ListenableFuture<AppSearchSession> appSearchSession,
@@ -245,14 +245,17 @@ class AppSearchDao {
// If we get failures in schemaResponse then we cannot try
// to write.
if (!setSchemaResponse.getMigrationFailures().isEmpty()) {
+ MigrationFailure failure =
+ setSchemaResponse.getMigrationFailures().get(0);
LogUtil.e(
"SetSchemaResponse migration failure: "
- + setSchemaResponse
- .getMigrationFailures()
- .get(0));
- throw new RuntimeException(
- ConsentConstants
- .ERROR_MESSAGE_APPSEARCH_FAILURE);
+ + failure);
+ String message =
+ String.format(
+ "%s Migration failure: %s",
+ ERROR_MESSAGE_APPSEARCH_FAILURE,
+ failure.getAppSearchResult());
+ throw new RuntimeException(message);
}
// The database knows about this schemaType and write can
// occur.
@@ -264,17 +267,17 @@ class AppSearchDao {
executor);
return putFuture;
} catch (AppSearchException e) {
- LogUtil.e("Cannot instantiate AppSearch database: " + e.getMessage());
+ LogUtil.e(e, "Cannot instantiate AppSearch database");
+ return FluentFuture.from(
+ Futures.immediateFailedFuture(
+ new RuntimeException(ERROR_MESSAGE_APPSEARCH_FAILURE, e)));
}
- return FluentFuture.from(
- Futures.immediateFailedFuture(
- new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE)));
}
/**
* Delete a row from the database.
*
- * @return the result of the delete.
+ * @return the result of the delete operation.
*/
protected static <T> FluentFuture<AppSearchBatchResult<String, Void>> deleteData(
@NonNull Class<T> cls,
@@ -302,14 +305,17 @@ class AppSearchDao {
// If we get failures in schemaResponse then we cannot try
// to write.
if (!setSchemaResponse.getMigrationFailures().isEmpty()) {
+ MigrationFailure failure =
+ setSchemaResponse.getMigrationFailures().get(0);
LogUtil.e(
"SetSchemaResponse migration failure: "
- + setSchemaResponse
- .getMigrationFailures()
- .get(0));
- throw new RuntimeException(
- ConsentConstants
- .ERROR_MESSAGE_APPSEARCH_FAILURE);
+ + failure);
+ String message =
+ String.format(
+ "%s Migration failure: %s",
+ ERROR_MESSAGE_APPSEARCH_FAILURE,
+ failure.getAppSearchResult());
+ throw new RuntimeException(message);
}
// The database knows about this schemaType and write can
// occur.
@@ -321,10 +327,10 @@ class AppSearchDao {
executor);
return deleteFuture;
} catch (AppSearchException e) {
- LogUtil.e("Cannot instantiate AppSearch database: " + e.getMessage());
+ LogUtil.e(e, "Cannot instantiate AppSearch database");
+ return FluentFuture.from(
+ Futures.immediateFailedFuture(
+ new RuntimeException(ERROR_MESSAGE_APPSEARCH_FAILURE, e)));
}
- return FluentFuture.from(
- Futures.immediateFailedFuture(
- new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE)));
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java
index f81566cc2..85e7baf07 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java
@@ -28,6 +28,7 @@ import androidx.appsearch.platformstorage.PlatformStorage;
import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.compat.FileCompatUtils;
import com.android.adservices.service.consent.ConsentConstants;
import com.android.adservices.service.measurement.rollback.MeasurementRollbackWorker;
@@ -59,7 +60,7 @@ public final class AppSearchMeasurementRollbackWorker implements MeasurementRoll
private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock();
// Timeout for AppSearch write query in milliseconds.
- private static final int TIMEOUT_MS = 2000;
+ private final int mTimeoutMs;
private final String mUserId;
private final String mAdServicesPackageName;
@@ -75,6 +76,8 @@ public final class AppSearchMeasurementRollbackWorker implements MeasurementRoll
mSearchSession =
PlatformStorage.createSearchSessionAsync(
new PlatformStorage.SearchContext.Builder(context, DATABASE_NAME).build());
+
+ mTimeoutMs = FlagsFactory.getFlags().getAppSearchWriteTimeout();
}
/** Return an instance of {@link AppSearchMeasurementRollbackWorker} */
@@ -98,11 +101,11 @@ public final class AppSearchMeasurementRollbackWorker implements MeasurementRoll
// don't need to share it with the T package. Thus, we can send an empty list for the
// packageIdentifiers parameter.
dao.writeData(mSearchSession, List.of(), mExecutor)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Wrote measurement rollback data to AppSearch: %s", dao);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
LogUtil.e(e, "Failed to write measurement rollback to AppSearch");
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
@@ -130,11 +133,11 @@ public final class AppSearchMeasurementRollbackWorker implements MeasurementRoll
mExecutor,
storageIdentifier,
AppSearchMeasurementRollbackDao.NAMESPACE)
- .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ .get(mTimeoutMs, TimeUnit.MILLISECONDS);
LogUtil.d("Deleted MeasurementRollback data from AppSearch for: %s", storageIdentifier);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
LogUtil.e(e, "Failed to delete MeasurementRollback data in AppSearch");
- throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE, e);
} finally {
READ_WRITE_LOCK.writeLock().unlock();
}
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java b/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
index 5b77deccb..10140e56f 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
@@ -2185,7 +2185,8 @@ public class ConsentManager {
if (errorLogger != null) {
errorLogger.apply(e);
}
- throw new RuntimeException(getClass().getSimpleName() + " failed. " + e.getMessage());
+ throw new RuntimeException(
+ getClass().getSimpleName() + " failed. " + e.getMessage(), e);
} finally {
mReadWriteLock.writeLock().unlock();
}
diff --git a/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchConsentWorkerTest.java b/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchConsentWorkerTest.java
index 1114e11d2..23da8f97d 100644
--- a/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchConsentWorkerTest.java
+++ b/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchConsentWorkerTest.java
@@ -62,6 +62,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
@@ -72,9 +74,12 @@ import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
@SmallTest
public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
@@ -88,17 +93,20 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
private static final Topic TOPIC1 = Topic.create(0, 1, 11);
private static final Topic TOPIC2 = Topic.create(12, 2, 22);
private static final Topic TOPIC3 = Topic.create(123, 3, 33);
- private List<Topic> mTopics = new ArrayList<>();
+
private MockitoSession mMockitoSession;
- @Mock
- Flags mMockFlags;
+ private static final int APPSEARCH_TIMEOUT_MS = 1000;
@Rule(order = 0)
public final SdkLevelSupportRule sdkLevelRule = SdkLevelSupportRule.forAtLeastS();
+ private final List<Topic> mTopics = Arrays.asList(TOPIC1, TOPIC2, TOPIC3);
+ private final ListeningExecutorService mExecutorService =
+ MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
+ @Mock private Flags mMockFlags;
+
@Before
public void setup() {
- mTopics.addAll(List.of(TOPIC1, TOPIC2, TOPIC3));
mMockitoSession =
ExtendedMockito.mockitoSession()
.mockStatic(FlagsFactory.class)
@@ -109,6 +117,9 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
when(mMockFlags.getAdservicesApkShaCertificate())
.thenReturn(Flags.ADSERVICES_APK_SHA_CERTIFICATE);
when(mMockFlags.getAppsearchWriterAllowListOverride()).thenReturn("");
+ // Reduce AppSearch write timeout to speed up the tests.
+ when(mMockFlags.getAppSearchWriteTimeout()).thenReturn(APPSEARCH_TIMEOUT_MS);
+ when(mMockFlags.getAppSearchReadTimeout()).thenReturn(APPSEARCH_TIMEOUT_MS);
}
@After
@@ -185,6 +196,34 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void testSetConsent_failure_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ initTimeoutResponse();
+ RuntimeException e =
+ assertThrows(
+ RuntimeException.class,
+ () ->
+ AppSearchConsentWorker.getInstance()
+ .setConsent(API_TYPE, CONSENTED));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void testSetConsent() {
MockitoSession staticMockSessionLocal = null;
try {
@@ -379,6 +418,8 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
RuntimeException.class,
() -> appSearchConsentWorker.clearAppsWithConsent(TEST));
assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(ExecutionException.class);
} finally {
if (staticMockSessionLocal != null) {
staticMockSessionLocal.finishMocking();
@@ -561,6 +602,8 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
RuntimeException.class,
() -> appSearchConsentWorker.removeAppWithConsent(consentType, TEST));
assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(ExecutionException.class);
} finally {
if (staticMockSessionLocal != null) {
staticMockSessionLocal.finishMocking();
@@ -677,7 +720,7 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
- public void testRecordGaUxNotificationDisplayed_faiure() {
+ public void testRecordGaUxNotificationDisplayed_failure() {
runRecordNotificationDisplayedTestFailure(/* isBetaUx= */ false);
}
@@ -715,6 +758,52 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void testRecordNotificationDisplayed_failure_timeout() {
+ runRecordNotificationDisplayedTestFailureTimeout(/* isBetaUx= */ true);
+ }
+
+ @Test
+ public void testRecordGaUxNotificationDisplayed_failure_timeout() {
+ runRecordNotificationDisplayedTestFailureTimeout(/* isBetaUx= */ false);
+ }
+
+ private void runRecordNotificationDisplayedTestFailureTimeout(boolean isBetaUx) {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ initTimeoutResponse();
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+
+ RuntimeException e;
+ if (isBetaUx) {
+ e =
+ assertThrows(
+ RuntimeException.class,
+ () -> worker.recordNotificationDisplayed(true));
+ } else {
+ e =
+ assertThrows(
+ RuntimeException.class,
+ () -> worker.recordGaUxNotificationDisplayed(true));
+ }
+
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void testRecordNotificationDisplayed() {
runRecordNotificationDisplayed(/* isBetaUx= */ true);
}
@@ -808,6 +897,36 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void testSetCurrentPrivacySandboxFeature_failure_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+
+ initTimeoutResponse();
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(
+ RuntimeException.class,
+ () ->
+ worker.setCurrentPrivacySandboxFeature(
+ PrivacySandboxFeatureType.PRIVACY_SANDBOX_RECONSENT));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void testSetCurrentPrivacySandboxFeature() {
MockitoSession staticMockSessionLocal = null;
try {
@@ -882,6 +1001,40 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void testRecordUserManualInteractionWithConsent_failure_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .mockStatic(AppSearchInteractionsDao.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+
+ initTimeoutResponse();
+
+ when(AppSearchInteractionsDao.getRowId(any(), any())).thenReturn("" + UID);
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ int interactions = ConsentManager.MANUAL_INTERACTIONS_RECORDED;
+
+ RuntimeException e =
+ assertThrows(
+ RuntimeException.class,
+ () -> worker.recordUserManualInteractionWithConsent(interactions));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void testRecordUserManualInteractionWithConsent() {
MockitoSession staticMockSessionLocal = null;
try {
@@ -954,6 +1107,38 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void testRecordBlockedTopic_failure_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .spyStatic(UserHandle.class)
+ .spyStatic(AppSearchTopicsConsentDao.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ initTimeoutResponse();
+
+ String query = "" + UID;
+ ExtendedMockito.doReturn(query).when(() -> AppSearchTopicsConsentDao.getQuery(any()));
+ ExtendedMockito.doReturn(null)
+ .when(() -> AppSearchTopicsConsentDao.readConsentData(any(), any(), any(), any()));
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(RuntimeException.class, () -> worker.recordBlockedTopic(TOPIC1));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void testRecordBlockedTopics_new() {
MockitoSession staticMockSessionLocal = null;
try {
@@ -973,7 +1158,6 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
() ->
AppSearchTopicsConsentDao.readConsentData(
any(), any(), any(), any()));
-
AppSearchConsentWorker appSearchConsentWorker = AppSearchConsentWorker.getInstance();
appSearchConsentWorker.recordBlockedTopic(TOPIC1);
} finally {
@@ -1046,6 +1230,15 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
verify(dao).removeBlockedTopic(TOPIC1);
verify(dao).writeData(any(), any(), any());
assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+
+
+ Throwable cause = e.getCause();
+ assertThat(cause).isNotNull();
+ assertThat(cause).isInstanceOf(ExecutionException.class);
+
+ Throwable rootCause = cause.getCause();
+ assertThat(rootCause).isNotNull();
+ assertThat(rootCause).isInstanceOf(InterruptedException.class);
} finally {
if (staticMockSessionLocal != null) {
staticMockSessionLocal.finishMocking();
@@ -1141,6 +1334,32 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void testClearBlockedTopics_failure_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ initTimeoutResponse();
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(RuntimeException.class, () -> worker.clearBlockedTopics());
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void testClearBlockedTopics() {
MockitoSession staticMockSessionLocal = null;
try {
@@ -1180,6 +1399,26 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
when(mockResponse.getMigrationFailures()).thenReturn(List.of());
}
+ private void initTimeoutResponse() {
+ AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
+ UserHandle mockUserHandle = Mockito.mock(UserHandle.class);
+ Mockito.when(UserHandle.getUserHandleForUid(Binder.getCallingUid()))
+ .thenReturn(mockUserHandle);
+ Mockito.when(mockUserHandle.getIdentifier()).thenReturn(UID);
+ ExtendedMockito.doReturn(Futures.immediateFuture(mockSession))
+ .when(() -> PlatformStorage.createSearchSessionAsync(any()));
+ verify(mockSession, atMost(1)).setSchemaAsync(any(SetSchemaRequest.class));
+
+ SetSchemaResponse mockResponse = Mockito.mock(SetSchemaResponse.class);
+ when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
+ .thenReturn(Futures.immediateFuture(mockResponse));
+ AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+ when(mockSession.putAsync(any())).thenReturn(getLongRunningOperation(result));
+
+ verify(mockResponse, atMost(1)).getMigrationFailures();
+ when(mockResponse.getMigrationFailures()).thenReturn(List.of());
+ }
+
private void initFailureResponse() {
AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
ExtendedMockito.doReturn(Futures.immediateFuture(mockSession))
@@ -1190,7 +1429,8 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
.thenReturn(Futures.immediateFuture(mockResponse));
- AppSearchResult mockResult = Mockito.mock(AppSearchResult.class);
+ AppSearchResult<String> mockResult =
+ AppSearchResult.newFailedResult(AppSearchResult.RESULT_INVALID_ARGUMENT, "test");
SetSchemaResponse.MigrationFailure failure =
new SetSchemaResponse.MigrationFailure(
/* namespace= */ TEST,
@@ -1200,6 +1440,15 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
when(mockResponse.getMigrationFailures()).thenReturn(List.of(failure));
}
+ private <T> ListenableFuture<T> getLongRunningOperation(T result) {
+ // Wait for a time that's longer than the AppSearch write timeout, then return the result.
+ return mExecutorService.submit(
+ () -> {
+ TimeUnit.MILLISECONDS.sleep(APPSEARCH_TIMEOUT_MS + 1000);
+ return result;
+ });
+ }
+
@Test
public void isAdIdEnabledTest_trueBit() {
isAdIdEnabledTest(true);
@@ -1221,6 +1470,7 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
.startMocking();
ExtendedMockito.doReturn(isAdIdEnabled)
.when(() -> AppSearchUxStatesDao.readIsAdIdEnabled(any(), any(), any(), any()));
+
AppSearchConsentWorker appSearchConsentWorker = AppSearchConsentWorker.getInstance();
assertThat(appSearchConsentWorker.isAdIdEnabled()).isEqualTo(isAdIdEnabled);
} finally {
@@ -1242,7 +1492,6 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
.strictness(Strictness.WARN)
.initMocks(this)
.startMocking();
- initSuccessResponse();
String query = "" + UID;
ExtendedMockito.doReturn(query).when(() -> AppSearchUxStatesDao.getQuery(any()));
@@ -1265,16 +1514,16 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
- public void setAdIdEnabledTest_trueBit() {
- setAdIdEnabledTest(true);
+ public void setAdIdEnabledTest_failure_trueBit() {
+ setAdIdEnabledTestFailure(true);
}
@Test
- public void setAdIdEnabledTest_falseBit() {
- setAdIdEnabledTest(false);
+ public void setAdIdEnabledTest_failure_falseBit() {
+ setAdIdEnabledTestFailure(false);
}
- private void setAdIdEnabledTest(boolean isAdIdEnabled) {
+ private void setAdIdEnabledTestFailure(boolean isAdIdEnabled) {
MockitoSession staticMockSessionLocal = null;
try {
staticMockSessionLocal =
@@ -1297,6 +1546,40 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void setAdIdEnabledTest_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .spyStatic(UserHandle.class)
+ .mockStatic(AppSearchUxStatesDao.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+
+ String query = "" + UID;
+ ExtendedMockito.doReturn(query).when(() -> AppSearchUxStatesDao.getQuery(any()));
+ AppSearchUxStatesDao dao = Mockito.mock(AppSearchUxStatesDao.class);
+ ExtendedMockito.doReturn(dao)
+ .when(() -> AppSearchUxStatesDao.readData(any(), any(), any(), any()));
+ when(dao.writeData(any(), any(), any()))
+ .thenReturn(FluentFuture.from(getLongRunningOperation(null)));
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(RuntimeException.class, () -> worker.setAdIdEnabled(true));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void isU18AccountTest_trueBit() {
isU18AccountTest(true);
}
@@ -1392,6 +1675,41 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void setU18AccountTest_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .mockStatic(AppSearchUxStatesDao.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+
+ String query = "" + UID;
+ ExtendedMockito.doReturn(query).when(() -> AppSearchUxStatesDao.getQuery(any()));
+ AppSearchUxStatesDao dao = Mockito.mock(AppSearchUxStatesDao.class);
+ ExtendedMockito.doReturn(dao)
+ .when(() -> AppSearchUxStatesDao.readData(any(), any(), any(), any()));
+ AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+ when(dao.writeData(any(), any(), any()))
+ .thenReturn(FluentFuture.from(getLongRunningOperation(result)));
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(RuntimeException.class, () -> worker.setU18Account(false));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void isEntryPointEnabledTest_trueBit() {
isEntryPointEnabledTest(true);
}
@@ -1492,6 +1810,43 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void setEntryPointEnabledTest_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .mockStatic(AppSearchUxStatesDao.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+
+ initSuccessResponse();
+
+ String query = "" + UID;
+ ExtendedMockito.doReturn(query).when(() -> AppSearchUxStatesDao.getQuery(any()));
+ AppSearchUxStatesDao dao = Mockito.mock(AppSearchUxStatesDao.class);
+ ExtendedMockito.doReturn(dao)
+ .when(() -> AppSearchUxStatesDao.readData(any(), any(), any(), any()));
+ AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+ when(dao.writeData(any(), any(), any()))
+ .thenReturn(FluentFuture.from(getLongRunningOperation(result)));
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(RuntimeException.class, () -> worker.setEntryPointEnabled(true));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void isAdultAccountTest_trueBit() {
isAdultAccountTest(true);
}
@@ -1591,6 +1946,42 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void setAdultAccountTest_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .mockStatic(AppSearchUxStatesDao.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ initSuccessResponse();
+
+ String query = "" + UID;
+ ExtendedMockito.doReturn(query).when(() -> AppSearchUxStatesDao.getQuery(any()));
+ AppSearchUxStatesDao dao = Mockito.mock(AppSearchUxStatesDao.class);
+ ExtendedMockito.doReturn(dao)
+ .when(() -> AppSearchUxStatesDao.readData(any(), any(), any(), any()));
+ AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+ when(dao.writeData(any(), any(), any()))
+ .thenReturn(FluentFuture.from(getLongRunningOperation(result)));
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(RuntimeException.class, () -> worker.setAdultAccount(true));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void wasU18NotificationDisplayedTest_trueBit() {
wasU18NotificationDisplayedTest(true);
}
@@ -1692,6 +2083,43 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void setU18NotificationDisplayedTest_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .mockStatic(AppSearchUxStatesDao.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ initSuccessResponse();
+
+ String query = "" + UID;
+ ExtendedMockito.doReturn(query).when(() -> AppSearchUxStatesDao.getQuery(any()));
+ AppSearchUxStatesDao dao = Mockito.mock(AppSearchUxStatesDao.class);
+ ExtendedMockito.doReturn(dao)
+ .when(() -> AppSearchUxStatesDao.readData(any(), any(), any(), any()));
+ AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+ when(dao.writeData(any(), any(), any()))
+ .thenReturn(FluentFuture.from(getLongRunningOperation(result)));
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(
+ RuntimeException.class, () -> worker.setU18NotificationDisplayed(true));
+ assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void getUxTest_allUxs() {
MockitoSession staticMockSessionLocal = null;
try {
@@ -1740,7 +2168,7 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
}
}
-
+
@Test
public void setUxTest_allUxsSuccess() {
MockitoSession staticMockSessionLocal = null;
@@ -1761,7 +2189,7 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
AppSearchUxStatesDao dao = Mockito.mock(AppSearchUxStatesDao.class);
ExtendedMockito.doReturn(dao)
.when(() -> AppSearchUxStatesDao.readData(any(), any(), any(), any()));
- AppSearchBatchResult<String, Void> result =
+ AppSearchBatchResult<String, Void> result =
Mockito.mock(AppSearchBatchResult.class);
when(dao.writeData(any(), any(), any()))
.thenReturn(FluentFuture.from(Futures.immediateFuture(result)));
@@ -1780,6 +2208,45 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
@Test
+ public void setUxTest_allUxs_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .mockStatic(AppSearchUxStatesDao.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+ initSuccessResponse();
+
+ for (PrivacySandboxUxCollection ux : PrivacySandboxUxCollection.values()) {
+ String query = "" + UID;
+ ExtendedMockito.doReturn(query).when(() -> AppSearchUxStatesDao.getQuery(any()));
+ AppSearchUxStatesDao dao = Mockito.mock(AppSearchUxStatesDao.class);
+ ExtendedMockito.doReturn(dao)
+ .when(() -> AppSearchUxStatesDao.readData(any(), any(), any(), any()));
+ AppSearchBatchResult<String, Void> result = Mockito.mock(
+ AppSearchBatchResult.class);
+ when(dao.writeData(any(), any(), any()))
+ .thenReturn(FluentFuture.from(getLongRunningOperation(result)));
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e = assertThrows(RuntimeException.class, () -> worker.setUx(ux));
+ assertThat(e.getMessage()).isEqualTo(
+ ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ }
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
+
+ @Test
public void getEnrollmentChannelTest_allUxsAllEnrollmentChannels() {
MockitoSession staticMockSessionLocal = null;
try {
@@ -1882,4 +2349,50 @@ public final class AppSearchConsentWorkerTest extends AdServicesUnitTestCase {
}
}
}
+
+ @Test
+
+ public void setEnrollmentChannelTest_allUxsAllEnrollmentChannels_timeout() {
+ MockitoSession staticMockSessionLocal = null;
+ try {
+ staticMockSessionLocal =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(PlatformStorage.class)
+ .mockStatic(AppSearchUxStatesDao.class)
+ .spyStatic(UserHandle.class)
+ .strictness(Strictness.WARN)
+ .initMocks(this)
+ .startMocking();
+
+ for (PrivacySandboxUxCollection ux : PrivacySandboxUxCollection.values()) {
+ for (PrivacySandboxEnrollmentChannelCollection channel :
+ ux.getEnrollmentChannelCollection()) {
+ String query = "" + UID;
+ ExtendedMockito.doReturn(query)
+ .when(() -> AppSearchUxStatesDao.getQuery(any()));
+ AppSearchUxStatesDao dao = Mockito.mock(AppSearchUxStatesDao.class);
+ ExtendedMockito.doReturn(dao)
+ .when(() -> AppSearchUxStatesDao.readData(any(), any(), any(), any()));
+ AppSearchBatchResult<String, Void> result =
+ Mockito.mock(AppSearchBatchResult.class);
+ when(dao.writeData(any(), any(), any()))
+ .thenReturn(FluentFuture.from(getLongRunningOperation(result)));
+
+ AppSearchConsentWorker worker = AppSearchConsentWorker.getInstance();
+ RuntimeException e =
+ assertThrows(
+ RuntimeException.class,
+ () -> worker.setEnrollmentChannel(ux, channel));
+ assertThat(e.getMessage())
+ .isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ }
+ }
+ } finally {
+ if (staticMockSessionLocal != null) {
+ staticMockSessionLocal.finishMocking();
+ }
+ }
+ }
}
diff --git a/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java b/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java
index 47bc18645..5627ff9fb 100644
--- a/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java
+++ b/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java
@@ -52,6 +52,8 @@ import com.android.adservices.service.consent.ConsentConstants;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
import org.junit.Rule;
@@ -63,6 +65,8 @@ import org.mockito.MockitoAnnotations;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
@SmallTest
public class AppSearchDaoTest {
@@ -87,6 +91,8 @@ public class AppSearchDaoTest {
new PackageIdentifier(
/* packageName= */ TEST, /* sha256= */ new Signature(SHA).toByteArray());
+ private static final int APPSEARCH_READ_TIMEOUT_MS = 500;
+
@Rule
public final AdServicesExtendedMockitoRule adServicesExtendedMockitoRule =
new AdServicesExtendedMockitoRule.Builder(this).mockStatic(FlagsFactory.class).build();
@@ -95,6 +101,7 @@ public class AppSearchDaoTest {
public void before() {
MockitoAnnotations.initMocks(this);
when(mFlags.getAppsearchWriterAllowListOverride()).thenReturn("");
+ when(mFlags.getAppSearchReadTimeout()).thenReturn(APPSEARCH_READ_TIMEOUT_MS);
doReturn(mFlags).when(FlagsFactory::getFlags);
}
@@ -205,6 +212,30 @@ public class AppSearchDaoTest {
}
@Test
+ public void testReadConsentData_timeout() {
+ AppSearchDao result =
+ AppSearchDao.readConsentData(
+ AppSearchConsentDao.class,
+ getLongRunningOperation(mGlobalSearchSession),
+ mExecutor,
+ NAMESPACE,
+ TEST,
+ mAdServicesPackageName);
+ assertThat(result).isNull();
+ }
+
+ private <T> ListenableFuture<T> getLongRunningOperation(T result) {
+ // Wait for a time that's longer than the AppSearch read timeout, then return the result.
+ ListeningExecutorService ls =
+ MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
+ return ls.submit(
+ () -> {
+ TimeUnit.MILLISECONDS.sleep(APPSEARCH_READ_TIMEOUT_MS + 500);
+ return result;
+ });
+ }
+
+ @Test
public void testReadAppSearchData_emptyQuery() {
AppSearchDao dao =
AppSearchDao.readAppSearchSessionData(
@@ -262,7 +293,8 @@ public class AppSearchDaoTest {
when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
.thenReturn(Futures.immediateFuture(mockResponse));
- AppSearchResult mockResult = Mockito.mock(AppSearchResult.class);
+ AppSearchResult<Void> mockResult =
+ AppSearchResult.newFailedResult(AppSearchResult.RESULT_INVALID_ARGUMENT, "test");
SetSchemaResponse.MigrationFailure failure =
new SetSchemaResponse.MigrationFailure(
/* namespace= */ TEST,
@@ -278,11 +310,12 @@ public class AppSearchDaoTest {
Futures.immediateFuture(mockSession),
List.of(PACKAGE_IDENTIFIER),
mExecutor);
- ExecutionException e = assertThrows(ExecutionException.class, () -> result.get());
+ ExecutionException e = assertThrows(ExecutionException.class, result::get);
assertThat(e.getMessage())
.isEqualTo(
"java.lang.RuntimeException: "
- + ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ + ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE
+ + " Migration failure: [FAILURE(3)]: test");
}
@Test
@@ -320,7 +353,8 @@ public class AppSearchDaoTest {
when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
.thenReturn(Futures.immediateFuture(mockResponse));
- AppSearchResult mockResult = Mockito.mock(AppSearchResult.class);
+ AppSearchResult<String> mockResult =
+ AppSearchResult.newFailedResult(AppSearchResult.RESULT_INVALID_ARGUMENT, "test");
SetSchemaResponse.MigrationFailure failure =
new SetSchemaResponse.MigrationFailure(
/* namespace= */ TEST,
@@ -337,11 +371,12 @@ public class AppSearchDaoTest {
mExecutor,
TEST,
NAMESPACE);
- ExecutionException e = assertThrows(ExecutionException.class, () -> result.get());
+ ExecutionException e = assertThrows(ExecutionException.class, result::get);
assertThat(e.getMessage())
.isEqualTo(
"java.lang.RuntimeException: "
- + ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ + ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE
+ + " Migration failure: [FAILURE(3)]: test");
}
@Test
diff --git a/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorkerTest.java b/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorkerTest.java
index d9bccb54e..e8137c71c 100644
--- a/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorkerTest.java
+++ b/adservices/tests/unittest/service-core/appsearch/src/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorkerTest.java
@@ -40,6 +40,8 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.compat.FileCompatUtils;
import com.android.adservices.service.consent.ConsentConstants;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -60,6 +62,7 @@ import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
@SmallTest
public class AppSearchMeasurementRollbackWorkerTest {
@@ -67,7 +70,7 @@ public class AppSearchMeasurementRollbackWorkerTest {
FileCompatUtils.getAdservicesFilename("measurement_rollback");
private static final String USERID = "user1";
private static final long APEX_VERSION = 100L;
- private static final int FUTURE_TIMEOUT_MILLISECONDS = 3000;
+ private static final int APPSEARCH_WRITE_TIMEOUT_MS = 1000;
private final Context mContext = ApplicationProvider.getApplicationContext();
private final String mAdServicesPackageName =
@@ -75,8 +78,8 @@ public class AppSearchMeasurementRollbackWorkerTest {
private final Executor mExecutor = AdServicesExecutors.getBackgroundExecutor();
private AppSearchMeasurementRollbackWorker mWorker;
private MockitoSession mMockitoSession;
-
@Mock private ListenableFuture<AppSearchSession> mAppSearchSession;
+ @Mock private Flags mMockFlags;
@Before
public void setup() {
@@ -84,10 +87,14 @@ public class AppSearchMeasurementRollbackWorkerTest {
ExtendedMockito.mockitoSession()
.mockStatic(PlatformStorage.class)
.mockStatic(AppSearchDao.class)
+ .mockStatic(FlagsFactory.class)
.strictness(Strictness.LENIENT)
.initMocks(this)
.startMocking();
+ doReturn(mMockFlags).when(FlagsFactory::getFlags);
+ doReturn(APPSEARCH_WRITE_TIMEOUT_MS).when(mMockFlags).getAppSearchWriteTimeout();
+
ArgumentCaptor<PlatformStorage.SearchContext> cap =
ArgumentCaptor.forClass(PlatformStorage.SearchContext.class);
doReturn(mAppSearchSession)
@@ -115,7 +122,7 @@ public class AppSearchMeasurementRollbackWorkerTest {
@SuppressWarnings("FutureReturnValueIgnored")
@Test
public void testClearAdServicesDeletionOccurred() {
- FluentFuture mockResult =
+ FluentFuture<AppSearchBatchResult<String, Void>> mockResult =
FluentFuture.from(
Futures.immediateFuture(
new AppSearchBatchResult.Builder<String, Void>().build()));
@@ -136,13 +143,14 @@ public class AppSearchMeasurementRollbackWorkerTest {
@Test
public void testClearAdServicesDeletionOccurred_throwsChecked() {
- Callable<Void> callable =
+ Callable<AppSearchBatchResult<String, Void>> callable =
() -> {
- TimeUnit.MILLISECONDS.sleep(FUTURE_TIMEOUT_MILLISECONDS);
+ TimeUnit.MILLISECONDS.sleep(APPSEARCH_WRITE_TIMEOUT_MS + 500);
return null;
};
- FluentFuture mockResult = FluentFuture.from(Futures.submit(callable, mExecutor));
+ FluentFuture<AppSearchBatchResult<String, Void>> mockResult =
+ FluentFuture.from(Futures.submit(callable, mExecutor));
doReturn(mockResult).when(() -> AppSearchDao.deleteData(any(), any(), any(), any(), any()));
RuntimeException e =
@@ -150,6 +158,8 @@ public class AppSearchMeasurementRollbackWorkerTest {
RuntimeException.class,
() -> mWorker.clearAdServicesDeletionOccurred("mock_row_id"));
assertThat(e).hasMessageThat().contains(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e).hasCauseThat().isNotNull();
+ assertThat(e).hasCauseThat().isInstanceOf(TimeoutException.class);
}
@Test
@@ -242,17 +252,17 @@ public class AppSearchMeasurementRollbackWorkerTest {
@Test
public void testRecordAdServicesDeletionOccurred_throwsChecked() {
- // The manager class waits for 2 seconds on the future.get() call before timing out. So
- // creating a future that takes longer than 2 sec to resolve, in order to create a
+ // The manager class waits for a few seconds on the future.get() call before timing out. So
+ // creating a future that takes longer than the timeout to resolve, in order to create a
// TimeoutException.
- Callable<Void> callable =
+ Callable<AppSearchBatchResult<String, Void>> callable =
() -> {
- TimeUnit.MILLISECONDS.sleep(FUTURE_TIMEOUT_MILLISECONDS);
+ TimeUnit.MILLISECONDS.sleep(APPSEARCH_WRITE_TIMEOUT_MS + 500);
return null;
};
- ListenableFuture<Void> future = Futures.submit(callable, mExecutor);
- FluentFuture mockFuture = FluentFuture.from(future);
+ FluentFuture<AppSearchBatchResult<String, Void>> mockFuture =
+ FluentFuture.from(Futures.submit(callable, mExecutor));
AppSearchMeasurementRollbackDao dao = mock(AppSearchMeasurementRollbackDao.class);
doReturn(mockFuture).when(dao).writeData(any(), any(), any());
@@ -269,6 +279,8 @@ public class AppSearchMeasurementRollbackWorkerTest {
spyWorker.recordAdServicesDeletionOccurred(
AdServicesManager.MEASUREMENT_DELETION, APEX_VERSION));
assertThat(e).hasMessageThat().contains(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+ assertThat(e).hasCauseThat().isNotNull();
+ assertThat(e).hasCauseThat().isInstanceOf(TimeoutException.class);
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
index 3e29468e4..673405ad4 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
@@ -50,6 +50,8 @@ import static com.android.adservices.service.Flags.DEFAULT_ADSERVICES_CONSENT_MI
import static com.android.adservices.service.Flags.DEFAULT_ADSERVICES_ENABLEMENT_CHECK_ENABLED;
import static com.android.adservices.service.Flags.DEFAULT_ADSERVICES_VERSION_MAPPINGS;
import static com.android.adservices.service.Flags.DEFAULT_AD_ID_FETCHER_TIMEOUT_MS;
+import static com.android.adservices.service.Flags.DEFAULT_APPSEARCH_READ_TIMEOUT_MS;
+import static com.android.adservices.service.Flags.DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS;
import static com.android.adservices.service.Flags.DEFAULT_AUCTION_SERVER_AD_ID_FETCHER_TIMEOUT_MS;
import static com.android.adservices.service.Flags.DEFAULT_BACKGROUND_JOB_SAMPLING_LOGGING_RATE;
import static com.android.adservices.service.Flags.DEFAULT_BLOCKED_TOPICS_SOURCE_OF_TRUTH;
@@ -10111,6 +10113,37 @@ public class PhFlagsTest {
assertThrows(IllegalArgumentException.class, mPhFlags::getBackgroundJobSamplingLoggingRate);
}
+ @Test
+ public void testGetAppSearchWriteTimeout() {
+ // Without any overriding, the value is the hard coded constant.
+ assertThat(mPhFlags.getAppSearchWriteTimeout())
+ .isEqualTo(DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS);
+
+ int phOverridingValue = DEFAULT_APPSEARCH_WRITE_TIMEOUT_MS + 1000;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ FlagsConstants.KEY_APPSEARCH_WRITE_TIMEOUT_MS,
+ Integer.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ assertThat(mPhFlags.getAppSearchWriteTimeout()).isEqualTo(phOverridingValue);
+ }
+
+ @Test
+ public void testGetAppSearchReadTimeout() {
+ // Without any overriding, the value is the hard coded constant.
+ assertThat(mPhFlags.getAppSearchReadTimeout()).isEqualTo(DEFAULT_APPSEARCH_READ_TIMEOUT_MS);
+
+ int phOverridingValue = DEFAULT_APPSEARCH_READ_TIMEOUT_MS + 500;
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ FlagsConstants.KEY_APPSEARCH_READ_TIMEOUT_MS,
+ Integer.toString(phOverridingValue),
+ /* makeDefault */ false);
+
+ assertThat(mPhFlags.getAppSearchReadTimeout()).isEqualTo(phOverridingValue);
+ }
+
private void overrideBackgroundJobSamplingLoggingRate(int phOverridingValue) {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_ADSERVICES,