summaryrefslogtreecommitdiff
path: root/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java')
-rw-r--r--adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java464
1 files changed, 352 insertions, 112 deletions
diff --git a/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java b/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java
index 22eb558dab..2fc29f2914 100644
--- a/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/topics/AppUpdateManager.java
@@ -20,10 +20,12 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Pair;
import com.android.adservices.LogUtil;
+import com.android.adservices.data.DbHelper;
import com.android.adservices.data.topics.Topic;
import com.android.adservices.data.topics.TopicsDao;
import com.android.adservices.data.topics.TopicsTables;
@@ -31,9 +33,8 @@ import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.internal.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -51,8 +52,8 @@ import java.util.stream.Collectors;
*
* <p>See go/rb-topics-app-update for details.
*/
-// TODO(b/239553255): Use transaction for methods have both read and write to the database.
public class AppUpdateManager {
+ private static final String EMPTY_SDK = "";
private static AppUpdateManager sSingleton;
// Tables that needs to be wiped out for application data
@@ -77,11 +78,17 @@ public class AppUpdateManager {
TopicsTables.AppUsageHistoryContract.APP)
};
+ private final DbHelper mDbHelper;
private final TopicsDao mTopicsDao;
private final Random mRandom;
private final Flags mFlags;
- AppUpdateManager(@NonNull TopicsDao topicsDao, @NonNull Random random, @NonNull Flags flags) {
+ AppUpdateManager(
+ @NonNull DbHelper dbHelper,
+ @NonNull TopicsDao topicsDao,
+ @NonNull Random random,
+ @NonNull Flags flags) {
+ mDbHelper = dbHelper;
mTopicsDao = topicsDao;
mRandom = random;
mFlags = flags;
@@ -99,6 +106,7 @@ public class AppUpdateManager {
if (sSingleton == null) {
sSingleton =
new AppUpdateManager(
+ DbHelper.getInstance(context),
TopicsDao.getInstance(context),
new Random(),
FlagsFactory.getFlags());
@@ -109,30 +117,77 @@ public class AppUpdateManager {
}
/**
- * Delete application data for a specific application.
+ * Handle application uninstallation for Topics API.
*
- * <p>This method allows other usages besides daily maintenance job, such as real-time data
- * wiping for an app uninstallation.
+ * <ul>
+ * <li>Delete all derived data for an uninstalled app.
+ * <li>When the feature is enabled, remove a topic if it has the uninstalled app as the only
+ * contributor in an epoch.
+ * </ul>
*
- * @param apps a {@link List} of applications to wipe data for
+ * @param packageUri The {@link Uri} got from Broadcast Intent
+ * @param currentEpochId the epoch id of current Epoch
*/
- public void deleteAppDataFromTableByApps(@NonNull List<String> apps) {
- for (Pair<String, String> tableColumnNamePair : TABLE_INFO_TO_ERASE_APP_DATA) {
- mTopicsDao.deleteAppFromTable(
- tableColumnNamePair.first, tableColumnNamePair.second, apps);
+ public void handleAppUninstallationInRealTime(@NonNull Uri packageUri, long currentEpochId) {
+ String packageName = convertUriToAppName(packageUri);
+
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ LogUtil.e(
+ "Database is not available, Stop processing app uninstallation for %s!",
+ packageName);
+ return;
}
- LogUtil.v("Have deleted data for application " + apps);
+ // This cross db and java boundaries multiple times, so we need to have a db transaction.
+ db.beginTransaction();
+
+ try {
+ if (supportsTopicContributorFeature()) {
+ handleTopTopicsWithoutContributors(currentEpochId, packageName);
+ }
+
+ deleteAppDataFromTableByApps(List.of(packageName));
+
+ // Mark the transaction successful.
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ LogUtil.d("End of processing app uninstallation for %s", packageName);
+ }
}
/**
- * Delete application data for a specific application.
+ * Handle application installation for Topics API.
+ *
+ * <p>Assign topics to past epochs for the installed app.
*
* @param packageUri The {@link Uri} got from Broadcast Intent
+ * @param currentEpochId the epoch id of current Epoch
*/
- public void deleteAppDataByUri(@NonNull Uri packageUri) {
- String appName = convertUriToAppName(packageUri);
- deleteAppDataFromTableByApps(List.of(appName));
+ public void handleAppInstallationInRealTime(@NonNull Uri packageUri, long currentEpochId) {
+ String packageName = convertUriToAppName(packageUri);
+
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ LogUtil.e(
+ "Database is not available, Stop processing app installation for %s",
+ packageName);
+ return;
+ }
+
+ // This cross db and java boundaries multiple times, so we need to have a db transaction.
+ db.beginTransaction();
+
+ try {
+ assignTopicsToNewlyInstalledApps(packageName, currentEpochId);
+
+ // Mark the transaction successful.
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ LogUtil.d("End of processing app installation for %s", packageName);
+ }
}
/**
@@ -149,8 +204,9 @@ public class AppUpdateManager {
* </ul>
*
* @param context the context
+ * @param currentEpochId epoch ID of current epoch
*/
- public void reconcileUninstalledApps(@NonNull Context context) {
+ public void reconcileUninstalledApps(@NonNull Context context, long currentEpochId) {
Set<String> currentInstalledApps = getCurrentInstalledApps(context);
Set<String> unhandledUninstalledApps = getUnhandledUninstalledApps(currentInstalledApps);
if (unhandledUninstalledApps.isEmpty()) {
@@ -160,8 +216,25 @@ public class AppUpdateManager {
LogUtil.v(
"Detect below unhandled mismatched applications: %s",
unhandledUninstalledApps.toString());
- handleUninstalledApps(unhandledUninstalledApps);
- LogUtil.v("App uninstallation reconciliation is finished!");
+
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ LogUtil.e("Database is not available, Stop reconciling app uninstallation in Topics!");
+ return;
+ }
+
+ // This cross db and java boundaries multiple times, so we need to have a db transaction.
+ db.beginTransaction();
+
+ try {
+ handleUninstalledAppsInReconciliation(unhandledUninstalledApps, currentEpochId);
+
+ // Mark the transaction successful.
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ LogUtil.v("App uninstallation reconciliation in Topics is finished!");
+ }
}
/**
@@ -191,25 +264,30 @@ public class AppUpdateManager {
LogUtil.v(
"Detect below unhandled installed applications: %s",
unhandledInstalledApps.toString());
- handleInstalledApps(unhandledInstalledApps, currentEpochId);
- LogUtil.v("App installation reconciliation is finished!");
- }
- /**
- * An overloading method to allow passing in Uri instead of app name in string format.
- *
- * <p>For newly installed app, to allow it get topics in current epoch, one of top topics in
- * past epochs will be assigned to this app.
- *
- * <p>See more details in go/rb-topics-app-update
- *
- * @param packageUri the Uri of newly installed application
- * @param currentEpochId current epoch id
- */
- public void assignTopicsToNewlyInstalledApps(@NonNull Uri packageUri, long currentEpochId) {
- assignTopicsToNewlyInstalledApps(convertUriToAppName(packageUri), currentEpochId);
+ SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
+ if (db == null) {
+ LogUtil.e("Database is not available, Stop reconciling app installation in Topics!");
+ return;
+ }
+
+ // This cross db and java boundaries multiple times, so we need to have a db transaction.
+ db.beginTransaction();
+
+ try {
+ handleInstalledAppsInReconciliation(unhandledInstalledApps, currentEpochId);
+
+ // Mark the transaction successful.
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ LogUtil.v("App installation reconciliation in Topics is finished!");
+ }
}
+ // TODO(b/256703300): Currently we handled app-sdk topic assignments in serving flow. Move the
+ // logic back to app installation after we can get all SDKs when an app is
+ // installed.
/**
* For a newly installed app, in case SDKs that this app uses are not known when the app is
* installed, the returned topic for an SDK can only be assigned when user calls getTopic().
@@ -231,7 +309,7 @@ public class AppUpdateManager {
}
int numberOfLookBackEpochs = mFlags.getTopicsNumberOfLookBackEpochs();
- Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
+ Pair<String, String> appOnlyCaller = Pair.create(app, EMPTY_SDK);
Pair<String, String> appSdkCaller = Pair.create(app, sdk);
// Get ReturnedTopics and CallerCanLearnTopics for past epochs in
@@ -286,32 +364,220 @@ public class AppUpdateManager {
/**
* Generating a random topic from given top topic list
*
- * @param topTopics a {@link List} of top topics in current epoch
- * @param numberOfTopTopics the number of regular top topics
- * @param numberOfRandomTopics the number of random top topics
+ * @param regularTopics a {@link List} of non-random topics in current epoch, excluding those
+ * which have no contributors
+ * @param randomTopics a {@link List} of random top topics
* @param percentageForRandomTopic the probability to select random object
* @return a selected {@link Topic} to be assigned to newly installed app
*/
+ @VisibleForTesting
@NonNull
- public Topic selectAssignedTopicFromTopTopics(
- @NonNull List<Topic> topTopics,
- int numberOfTopTopics,
- int numberOfRandomTopics,
+ Topic selectAssignedTopicFromTopTopics(
+ @NonNull List<Topic> regularTopics,
+ @NonNull List<Topic> randomTopics,
int percentageForRandomTopic) {
- // Validate the Top Topics are combined with correct number of topics and random topics
- Preconditions.checkArgument(numberOfTopTopics + numberOfRandomTopics == topTopics.size());
-
// If random number is in [0, randomPercentage - 1], a random topic will be selected.
boolean shouldSelectRandomTopic = mRandom.nextInt(100) < percentageForRandomTopic;
- if (shouldSelectRandomTopic) {
- // Generate a random number to pick one of random topics.
- // Random topics' index starts from numberOfTopTopics
- return topTopics.get(numberOfTopTopics + mRandom.nextInt(numberOfRandomTopics));
+ return shouldSelectRandomTopic
+ ? randomTopics.get(mRandom.nextInt(randomTopics.size()))
+ : regularTopics.get(mRandom.nextInt(regularTopics.size()));
+ }
+
+ /**
+ * Delete application data for a specific application.
+ *
+ * <p>This method allows other usages besides daily maintenance job, such as real-time data
+ * wiping for an app uninstallation.
+ *
+ * @param apps a {@link List} of applications to wipe data for
+ */
+ @VisibleForTesting
+ void deleteAppDataFromTableByApps(@NonNull List<String> apps) {
+ List<Pair<String, String>> tableToEraseData =
+ Arrays.stream(TABLE_INFO_TO_ERASE_APP_DATA).collect(Collectors.toList());
+ if (supportsTopicContributorFeature()) {
+ tableToEraseData.add(
+ Pair.create(
+ TopicsTables.TopicContributorsContract.TABLE,
+ TopicsTables.TopicContributorsContract.APP));
+ }
+
+ mTopicsDao.deleteFromTableByColumn(
+ /* tableNamesAndColumnNamePairs */ tableToEraseData, /* valuesToDelete */ apps);
+
+ LogUtil.v("Have deleted data for application " + apps);
+ }
+
+ /**
+ * Assign a top Topic for the newly installed app. This allows SDKs in the newly installed app
+ * to get the past 3 epochs' topics if they did observe the topic in the past.
+ *
+ * <p>See more details in go/rb-topics-app-update
+ *
+ * @param app the app package name of newly installed application
+ * @param currentEpochId current epoch id
+ */
+ @VisibleForTesting
+ void assignTopicsToNewlyInstalledApps(@NonNull String app, long currentEpochId) {
+ Objects.requireNonNull(app);
+
+ final int numberOfEpochsToAssignTopics = mFlags.getTopicsNumberOfLookBackEpochs();
+ final int numberOfTopTopics = mFlags.getTopicsNumberOfTopTopics();
+ final int topicsPercentageForRandomTopic = mFlags.getTopicsPercentageForRandomTopic();
+
+ Pair<String, String> appOnlyCaller = Pair.create(app, EMPTY_SDK);
+
+ // For each past epoch, assign a random topic to this newly installed app.
+ // The assigned topic should align the probability with rule to generate top topics.
+ for (long epochId = currentEpochId - 1;
+ epochId >= currentEpochId - numberOfEpochsToAssignTopics && epochId >= 0;
+ epochId--) {
+ List<Topic> topTopics = mTopicsDao.retrieveTopTopics(epochId);
+
+ if (topTopics.isEmpty()) {
+ LogUtil.v(
+ "Empty top topic list in Epoch %d, do not assign topic to App %s.",
+ epochId, app);
+ continue;
+ }
+
+ // Regular Topics are placed at the beginning of top topic list.
+ List<Topic> regularTopics = topTopics.subList(0, numberOfTopTopics);
+ // If enabled, filter out topics without contributors.
+ if (supportsTopicContributorFeature()) {
+ regularTopics = filterRegularTopicsWithoutContributors(regularTopics, epochId);
+ }
+ List<Topic> randomTopics = topTopics.subList(numberOfTopTopics, topTopics.size());
+
+ if (regularTopics.isEmpty() && randomTopics.isEmpty()) {
+ LogUtil.v(
+ "No topic is available to assign in Epoch %d, do not assign topic to App"
+ + " %s.",
+ epochId, app);
+ continue;
+ }
+
+ Topic assignedTopic =
+ selectAssignedTopicFromTopTopics(
+ regularTopics, randomTopics, topicsPercentageForRandomTopic);
+
+ // Persist this topic to database as returned topic in this epoch
+ mTopicsDao.persistReturnedAppTopicsMap(epochId, Map.of(appOnlyCaller, assignedTopic));
+
+ LogUtil.v(
+ "Topic %s has been assigned to newly installed App %s in Epoch %d",
+ assignedTopic.getTopic(), app, epochId);
}
+ }
- // Regular top topics start from index 0
- return topTopics.get(mRandom.nextInt(numberOfTopTopics));
+ /**
+ * When an app is uninstalled, we need to check whether any of its classified topics has no
+ * contributors on epoch basis for past epochs to look back. Note in an epoch, an app is a
+ * contributor to a topic if the app has called Topics API in this epoch and is classified to
+ * the topic.
+ *
+ * <p>If such topic exists, remove this topic from ReturnedTopicsTable in the epoch. This method
+ * is invoked before {@code deleteAppDataFromTableByApps}, so the uninstalled app will be
+ * cleared in TopicContributors Table there.
+ *
+ * <p>NOTE: We are only interested in the epochs which will be used for getTopics(), i.e. past
+ * numberOfLookBackEpochs epochs.
+ *
+ * @param currentEpochId the id of epoch when the method gets invoked
+ * @param uninstalledApp the newly uninstalled app
+ */
+ @VisibleForTesting
+ void handleTopTopicsWithoutContributors(long currentEpochId, @NonNull String uninstalledApp) {
+ // This check is on epoch basis for past epochs to look back
+ for (long epochId = currentEpochId - 1;
+ epochId >= currentEpochId - mFlags.getTopicsNumberOfLookBackEpochs()
+ && epochId >= 0;
+ epochId--) {
+ Map<String, List<Topic>> appClassificationTopics =
+ mTopicsDao.retrieveAppClassificationTopics(epochId);
+ List<Topic> topTopics = mTopicsDao.retrieveTopTopics(epochId);
+ Map<Integer, Set<String>> topTopicsToContributorsMap =
+ mTopicsDao.retrieveTopicToContributorsMap(epochId);
+
+ List<Topic> classifiedTopics =
+ appClassificationTopics.getOrDefault(uninstalledApp, new ArrayList<>());
+ // Collect all top topics to delete to make only one Db Update
+ List<String> topTopicsToDelete =
+ classifiedTopics.stream()
+ .filter(
+ classifiedTopic ->
+ topTopics.contains(classifiedTopic)
+ && topTopicsToContributorsMap.containsKey(
+ classifiedTopic.getTopic())
+ // Filter out the topic that has ONLY
+ // the uninstalled app as a contributor
+ && topTopicsToContributorsMap
+ .get(classifiedTopic.getTopic())
+ .size()
+ == 1
+ && topTopicsToContributorsMap
+ .get(classifiedTopic.getTopic())
+ .contains(uninstalledApp))
+ .map(Topic::getTopic)
+ .map(String::valueOf)
+ .collect(Collectors.toList());
+
+ if (!topTopicsToDelete.isEmpty()) {
+ LogUtil.v(
+ "Topics %s will not have contributors at epoch %d. Delete them in"
+ + " epoch %d",
+ topTopicsToDelete, epochId, epochId);
+ }
+
+ mTopicsDao.deleteEntriesFromTableByColumnWithEqualCondition(
+ List.of(
+ Pair.create(
+ TopicsTables.ReturnedTopicContract.TABLE,
+ TopicsTables.ReturnedTopicContract.TOPIC)),
+ topTopicsToDelete,
+ TopicsTables.ReturnedTopicContract.EPOCH_ID,
+ String.valueOf(epochId),
+ /* isStringEqualConditionColumnValue */ false);
+ }
+ }
+
+ /**
+ * Filter out regular topics without any contributors. Note in an epoch, an app is a contributor
+ * to a topic if the app has called Topics API in this epoch and is classified to the topic.
+ *
+ * <p>For padded Topics (Classifier randomly pads top topics if they are not enough), as we put
+ * {@link EpochManager#PADDED_TOP_TOPICS_STRING} into TopicContributors Map, padded topics
+ * actually have "contributor" PADDED_TOP_TOPICS_STRING. Therefore, they won't be filtered out.
+ *
+ * @param regularTopics non-random top topics
+ * @param epochId epochId of current epoch
+ * @return the filtered regular topics
+ */
+ @NonNull
+ @VisibleForTesting
+ List<Topic> filterRegularTopicsWithoutContributors(
+ @NonNull List<Topic> regularTopics, long epochId) {
+ Map<Integer, Set<String>> topicToContributorMap =
+ mTopicsDao.retrieveTopicToContributorsMap(epochId);
+ return regularTopics.stream()
+ .filter(
+ regularTopic ->
+ topicToContributorMap.containsKey(regularTopic.getTopic())
+ && !topicToContributorMap
+ .get(regularTopic.getTopic())
+ .isEmpty())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Check whether TopContributors Feature is enabled. It's enabled only when TopicContributors
+ * table is supported and the feature flag is on.
+ */
+ @VisibleForTesting
+ boolean supportsTopicContributorFeature() {
+ return mFlags.getEnableTopicContributorsCheck()
+ && mDbHelper.supportsTopicContributorsTable();
}
// An app will be regarded as an unhandled uninstalled app if it has an entry in any epoch of
@@ -368,7 +634,8 @@ public class AppUpdateManager {
// Get current installed applications from package manager
@NonNull
- private Set<String> getCurrentInstalledApps(Context context) {
+ @VisibleForTesting
+ Set<String> getCurrentInstalledApps(Context context) {
PackageManager packageManager = context.getPackageManager();
List<ApplicationInfo> appInfoList =
packageManager.getInstalledApplications(
@@ -377,73 +644,46 @@ public class AppUpdateManager {
return appInfoList.stream().map(appInfo -> appInfo.packageName).collect(Collectors.toSet());
}
+ /**
+ * Get App Package Name from a Uri.
+ *
+ * <p>Across PPAPI, package Uri is in the form of "package:com.example.adservices.sampleapp".
+ * "package" is a scheme of Uri and "com.example.adservices.sampleapp" is the app package name.
+ * Topics API persists app package name into database so this method extracts it from a Uri.
+ *
+ * @param packageUri the {@link Uri} of a package
+ * @return the app package name
+ */
+ @VisibleForTesting
+ @NonNull
+ String convertUriToAppName(@NonNull Uri packageUri) {
+ return packageUri.getSchemeSpecificPart();
+ }
+
// Handle Uninstalled applications that still have derived data in database
//
- // Currently, simply wipe out these data in the database for an app. i.e. Deleting all
- // derived data from all tables that are related to app (has app column)
- private void handleUninstalledApps(@NonNull Set<String> newlyUninstalledApps) {
- deleteAppDataFromTableByApps(new ArrayList<>(newlyUninstalledApps));
+ // 1) Delete all derived data for an uninstalled app.
+ // 2) Remove a topic if it has the uninstalled app as the only contributor in an epoch. In an
+ // epoch, an app is a contributor to a topic if the app has called Topics API in this epoch and
+ // is classified to the topic.
+ private void handleUninstalledAppsInReconciliation(
+ @NonNull Set<String> newlyUninstalledApps, long currentEpochId) {
+ for (String app : newlyUninstalledApps) {
+ if (supportsTopicContributorFeature()) {
+ handleTopTopicsWithoutContributors(currentEpochId, app);
+ }
+
+ deleteAppDataFromTableByApps(List.of(app));
+ }
}
// Handle newly installed applications
//
// Assign topics as real-time service to the app only, if the app isn't assigned with topics.
- private void handleInstalledApps(@NonNull Set<String> newlyInstalledApps, long currentEpochId) {
+ private void handleInstalledAppsInReconciliation(
+ @NonNull Set<String> newlyInstalledApps, long currentEpochId) {
for (String newlyInstalledApp : newlyInstalledApps) {
assignTopicsToNewlyInstalledApps(newlyInstalledApp, currentEpochId);
}
}
-
- //
- // For newly installed app, to allow it get topics in current epoch, one of top topics in past
- // epochs will be assigned to this app.
- //
- // See more details in go/rb-topics-app-update
- private void assignTopicsToNewlyInstalledApps(@NonNull String app, long currentEpochId) {
- Objects.requireNonNull(app);
-
- // Read topics related configurations from Flags
- final int numberOfEpochsToAssignTopics = mFlags.getTopicsNumberOfLookBackEpochs();
- final int topicsNumberOfTopTopics = mFlags.getTopicsNumberOfTopTopics();
- final int topicsNumberOfRandomTopics = mFlags.getTopicsNumberOfRandomTopics();
- final int topicsPercentageForRandomTopic = mFlags.getTopicsPercentageForRandomTopic();
-
- Pair<String, String> appOnlyCaller = Pair.create(app, /* sdk */ "");
-
- // For each past epoch, assign a random topic to this newly installed app.
- // The assigned topic should align the probability with rule to generate top topics.
- for (long epochId = currentEpochId - 1;
- epochId >= currentEpochId - numberOfEpochsToAssignTopics && epochId >= 0;
- epochId--) {
- List<Topic> topTopics = mTopicsDao.retrieveTopTopics(epochId);
-
- if (topTopics.isEmpty()) {
- LogUtil.v(
- "Empty top topic list in Epoch %d, do not assign topic to App %s in Epoch"
- + "%d.",
- epochId, app, epochId);
- continue;
- }
-
- Topic assignedTopic =
- selectAssignedTopicFromTopTopics(
- topTopics,
- topicsNumberOfTopTopics,
- topicsNumberOfRandomTopics,
- topicsPercentageForRandomTopic);
-
- // Persist this topic to database as returned topic in this epoch
- mTopicsDao.persistReturnedAppTopicsMap(epochId, Map.of(appOnlyCaller, assignedTopic));
-
- LogUtil.v(
- "Topic %s has been assigned to newly installed App %s in Epoch %d",
- assignedTopic.getTopic(), app, epochId);
- }
- }
-
- // packageUri.toString() has only app name, without "package:" in the front, i.e. it'll be like
- // "com.example.adservices.sampleapp".
- private String convertUriToAppName(@NonNull Uri packageUri) {
- return packageUri.getSchemeSpecificPart();
- }
}