blob: 71be5c0384b023be1aedb381afdce00dfc2bd4cd [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.appsearch;
import android.annotation.NonNull;
import android.os.Build;
import androidx.annotation.RequiresApi;
import androidx.appsearch.annotation.Document;
import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig;
import androidx.appsearch.app.GlobalSearchSession;
import com.android.adservices.LogUtil;
import com.android.internal.annotations.VisibleForTesting;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/** This class represents the data access object for the app consent data written to AppSearch. */
// TODO(b/269798827): Enable for R.
@RequiresApi(Build.VERSION_CODES.S)
@Document
class AppSearchAppConsentDao extends AppSearchDao {
/**
* Identifier of the Consent Document; must be unique within the Document's `namespace`. This is
* the row ID for consent data. It is a combination of user ID and consent type.
*/
@Document.Id private final String mId;
@Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
private final String mUserId;
/** Namespace of the Consent Document. Used to group documents during querying or deletion. */
@Document.Namespace private final String mNamespace;
/**
* Consent type for this table. Possible values are: APPS_WITH_CONSENT,
* APPS_WITH_REVOKED_CONSENT, APPS_WITH_FLEDGE_CONSENT and APPS_WITH_FLEDGE_REVOKED_CONSENT.
*/
@Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
private final String mConsentType;
/** List of apps. */
@Document.StringProperty private List<String> mApps;
// Column names used for preparing the query string, are not part of the @Document.
private static final String USER_ID_COLNAME = "userId";
private static final String CONSENT_TYPE_COLNAME = "consentType";
public static final String NAMESPACE = "appConsent";
// Consent types we store with this DAO.
public static final String APPS_WITH_CONSENT = "APPS_WITH_CONSENT";
public static final String APPS_WITH_REVOKED_CONSENT = "APPS_WITH_REVOKED_CONSENT";
/**
* Create an AppSearchConsentDao instance.
*
* @param id is a combination of the user ID and apiType
* @param userId is the user ID for this user
* @param namespace (required by AppSearch)
* @param consentType is the consentType for which we are storing consent data
* @param apps list of apps
*/
AppSearchAppConsentDao(
String id, String userId, String namespace, String consentType, List<String> apps) {
this.mId = id;
this.mUserId = userId;
this.mNamespace = namespace;
this.mConsentType = consentType;
this.mApps = apps;
}
/**
* Get the row ID for this row.
*
* @return ID
*/
public String getId() {
return mId;
}
/**
* Get the user ID for this row.
*
* @return user ID
*/
public String getUserId() {
return mUserId;
}
/**
* Get the namespace for this row.
*
* @return nameespace
*/
public String getNamespace() {
return mNamespace;
}
/**
* Get the apiType for this row.
*
* @return apiType
*/
public String getConsentType() {
return mConsentType;
}
/**
* Gets the list of apps.
*
* @return List of app package names.
*/
public List<String> getApps() {
return mApps;
}
/** Sets the apps. */
public void setApps(List<String> apps) {
mApps = apps;
}
/** Returns the row ID that should be unique for the consent namespace. */
public static String getRowId(@NonNull String uid, @NonNull String consentType) {
Objects.requireNonNull(uid);
Objects.requireNonNull(consentType);
return uid + "_" + consentType;
}
/**
* Converts the DAO to a string.
*
* @return string representing the DAO.
*/
public String toString() {
return "id="
+ mId
+ "; userId="
+ mUserId
+ "; consentType="
+ mConsentType
+ "; namespace="
+ mNamespace
+ "; apps="
+ (mApps == null ? "null" : Arrays.toString(mApps.toArray()));
}
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mNamespace, mApps, mConsentType);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AppSearchAppConsentDao)) return false;
AppSearchAppConsentDao obj = (AppSearchAppConsentDao) o;
return (Objects.equals(this.mId, obj.mId))
&& (Objects.equals(this.mUserId, obj.mUserId))
&& (Objects.equals(this.mConsentType, obj.mConsentType))
&& (Objects.equals(this.mNamespace, obj.mNamespace))
&& (Objects.equals(this.mApps, obj.mApps));
}
/**
* Read the consent data from AppSearch.
*
* @param searchSession we use GlobalSearchSession here to allow AdServices to read.
* @param executor the Executor to use.
* @param userId the user ID for the query.
* @param apiType the API type for the query.
* @return whether the row is consented for this user ID and apiType.
*/
static AppSearchAppConsentDao readConsentData(
@NonNull ListenableFuture<GlobalSearchSession> searchSession,
@NonNull Executor executor,
@NonNull String userId,
@NonNull String apiType) {
Objects.requireNonNull(searchSession);
Objects.requireNonNull(executor);
Objects.requireNonNull(userId);
Objects.requireNonNull(apiType);
String query = getQuery(userId, apiType);
AppSearchAppConsentDao dao =
AppSearchDao.readConsentData(
AppSearchAppConsentDao.class, searchSession, executor, NAMESPACE, query);
LogUtil.d("AppSearch app consent data read: " + dao + " [ query: " + query + "]");
return dao;
}
// Get the search query for AppSearch. Format specified at http://shortn/_RwVKmB74f3.
// Note: AND as an operator is not supported by AppSearch on S or T.
@VisibleForTesting
static String getQuery(String userId, String consentType) {
return USER_ID_COLNAME + ":" + userId + " " + CONSENT_TYPE_COLNAME + ":" + consentType;
}
}