1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.adservices.data.topics;
18 
19 import com.android.internal.annotations.VisibleForTesting;
20 
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.List;
24 
25 /** Container class for Topics API table definitions and constants. */
26 public final class TopicsTables {
27 
28     static final String TOPICS_TABLE_PREFIX = "topics_";
29 
30     /** Topics Taxonomy Table. */
31     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
32     public interface TaxonomyContract {
33         String TABLE = TOPICS_TABLE_PREFIX + "taxonomy";
34         String ID = "_id";
35         String TAXONOMY_VERSION = "taxonomy_version";
36         String MODEL_VERSION = "model_version";
37         String TOPIC = "topic";
38     }
39 
40     /** Table Create Statement for the Topics Epoch table */
41     @VisibleForTesting
42     public static final String CREATE_TABLE_TOPICS_TAXONOMY =
43             "CREATE TABLE "
44                     + TaxonomyContract.TABLE
45                     + "("
46                     + TaxonomyContract.ID
47                     + " INTEGER PRIMARY KEY, "
48                     + TaxonomyContract.TAXONOMY_VERSION
49                     + " INTEGER NOT NULL, "
50                     + TaxonomyContract.MODEL_VERSION
51                     + " INTEGER NOT NULL, "
52                     + TaxonomyContract.TOPIC
53                     + " INTEGER NOT NULL"
54                     + ")";
55 
56     /**
57      * This table has apps' classification Topics generated by the ML Classifier. In each epoch
58      * computation, the ML Classifier will generate topics for each app that uses the Topics API in
59      * the epoch.
60      */
61     public interface AppClassificationTopicsContract {
62         String TABLE = TOPICS_TABLE_PREFIX + "app_classification_topics";
63         String ID = "_id";
64         String EPOCH_ID = "epoch_id";
65         String APP = "app";
66         String TAXONOMY_VERSION = "taxonomy_version";
67         String MODEL_VERSION = "model_version";
68         String TOPIC = "topic";
69     }
70 
71     /** Create Statement for the returned Topics table */
72     @VisibleForTesting
73     public static final String CREATE_TABLE_APP_CLASSIFICATION_TOPICS =
74             "CREATE TABLE "
75                     + AppClassificationTopicsContract.TABLE
76                     + "("
77                     + AppClassificationTopicsContract.ID
78                     + " INTEGER PRIMARY KEY, "
79                     + AppClassificationTopicsContract.EPOCH_ID
80                     + " INTEGER NOT NULL, "
81                     + AppClassificationTopicsContract.APP
82                     + " TEXT NOT NULL, "
83                     + AppClassificationTopicsContract.TAXONOMY_VERSION
84                     + " INTEGER NOT NULL, "
85                     + AppClassificationTopicsContract.MODEL_VERSION
86                     + " INTEGER NOT NULL, "
87                     + AppClassificationTopicsContract.TOPIC
88                     + " INTEGER NOT NULL"
89                     + ")";
90 
91     /**
92      * This table has callers and which topics they can learn. Caller can be either (1) app in case
93      * the app called the Topics API directly. (2) sdk in case the sdk called the Topics API.
94      */
95     public interface CallerCanLearnTopicsContract {
96         String TABLE = TOPICS_TABLE_PREFIX + "caller_can_learn_topic";
97         String ID = "_id";
98         String EPOCH_ID = "epoch_id";
99         String CALLER = "caller";
100         String TOPIC = "topic";
101         String TAXONOMY_VERSION = "taxonomy_version";
102         String MODEL_VERSION = "model_version";
103     }
104 
105     /** Create Statement for the Caller Learned Topic table. */
106     @VisibleForTesting
107     public static final String CREATE_TABLE_CALLER_CAN_LEARN_TOPICS =
108             "CREATE TABLE "
109                     + CallerCanLearnTopicsContract.TABLE
110                     + "("
111                     + CallerCanLearnTopicsContract.ID
112                     + " INTEGER PRIMARY KEY, "
113                     + CallerCanLearnTopicsContract.EPOCH_ID
114                     + " INTEGER NOT NULL, "
115                     + CallerCanLearnTopicsContract.CALLER
116                     + " TEXT NOT NULL, "
117                     + CallerCanLearnTopicsContract.TOPIC
118                     + " INTEGER NOT NULL, "
119                     + CallerCanLearnTopicsContract.TAXONOMY_VERSION
120                     + " INTEGER NOT NULL, "
121                     + CallerCanLearnTopicsContract.MODEL_VERSION
122                     + " INTEGER NOT NULL"
123                     + ")";
124 
125     // TODO(b/223446202): Make this table to configurable numbers of top topics.
126 
127     /**
128      * Top Topics Table. There are top 5 topics and 1 random topic. In case there is not enough
129      * usage to generate top 5 topics, random ones will be generated.
130      */
131     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
132     public interface TopTopicsContract {
133         String TABLE = TOPICS_TABLE_PREFIX + "top_topics";
134         String ID = "_id";
135         String EPOCH_ID = "epoch_id";
136         String TOPIC1 = "topic1";
137         String TOPIC2 = "topic2";
138         String TOPIC3 = "topic3";
139         String TOPIC4 = "topic4";
140         String TOPIC5 = "topic5";
141         String RANDOM_TOPIC = "random_topic";
142         String TAXONOMY_VERSION = "taxonomy_version";
143         String MODEL_VERSION = "model_version";
144     }
145 
146     /** Table Create Statement for the Top Topics table */
147     @VisibleForTesting
148     public static final String CREATE_TABLE_TOP_TOPICS =
149             "CREATE TABLE "
150                     + TopTopicsContract.TABLE
151                     + "("
152                     + TopTopicsContract.ID
153                     + " INTEGER PRIMARY KEY, "
154                     + TopTopicsContract.EPOCH_ID
155                     + " INTEGER NOT NULL, "
156                     + TopTopicsContract.TOPIC1
157                     + " INTEGER NOT NULL, "
158                     + TopTopicsContract.TOPIC2
159                     + " INTEGER NOT NULL, "
160                     + TopTopicsContract.TOPIC3
161                     + " INTEGER NOT NULL, "
162                     + TopTopicsContract.TOPIC4
163                     + " INTEGER NOT NULL, "
164                     + TopTopicsContract.TOPIC5
165                     + " INTEGER NOT NULL, "
166                     + TopTopicsContract.RANDOM_TOPIC
167                     + " INTEGER NOT NULL, "
168                     + TopTopicsContract.TAXONOMY_VERSION
169                     + " INTEGER NOT NULL, "
170                     + TopTopicsContract.MODEL_VERSION
171                     + " INTEGER NOT NULL"
172                     + ")";
173 
174     /**
175      * The returned topic for the app or for the sdk. Note: for App usages directly without any SDK,
176      * the SDK Name is set to empty string.
177      */
178     public interface ReturnedTopicContract {
179         String TABLE = TOPICS_TABLE_PREFIX + "returned_topics";
180         String ID = "_id";
181         String EPOCH_ID = "epoch_id";
182         String APP = "app";
183         String SDK = "sdk";
184         String TAXONOMY_VERSION = "taxonomy_version";
185         String MODEL_VERSION = "model_version";
186         String TOPIC = "topic";
187         String LOGGED_TOPIC = "logged_topic";
188     }
189 
190     /** The returned encrypted topic for the app or for the sdk. */
191     public interface ReturnedEncryptedTopicContract {
192         String TABLE = TOPICS_TABLE_PREFIX + "returned_encrypted_topics";
193         String ID = "_id";
194         String EPOCH_ID = "epoch_id";
195         String APP = "app";
196         String SDK = "sdk";
197         String ENCRYPTED_TOPIC = "encrypted_topic";
198         String ENCAPSULATED_KEY = "encapsulated_key";
199         String KEY_IDENTIFIER = "key_identifier";
200     }
201 
202     /** Create Statement for the returned Topics table */
203     @VisibleForTesting
204     public static final String CREATE_TABLE_RETURNED_TOPIC =
205             "CREATE TABLE "
206                     + ReturnedTopicContract.TABLE
207                     + "("
208                     + ReturnedTopicContract.ID
209                     + " INTEGER PRIMARY KEY, "
210                     + ReturnedTopicContract.EPOCH_ID
211                     + " INTEGER NOT NULL, "
212                     + ReturnedTopicContract.APP
213                     + " TEXT NOT NULL, "
214                     + ReturnedTopicContract.SDK
215                     + " TEXT NOT NULL, "
216                     + ReturnedTopicContract.TAXONOMY_VERSION
217                     + " INTEGER NOT NULL, "
218                     + ReturnedTopicContract.MODEL_VERSION
219                     + " INTEGER NOT NULL, "
220                     + ReturnedTopicContract.TOPIC
221                     + " INTEGER NOT NULL, "
222                     + ReturnedTopicContract.LOGGED_TOPIC
223                     + " INTEGER"
224                     + ")";
225 
226     /** Create Statement for the returned Encrypted Topics table */
227     public static final String CREATE_TABLE_RETURNED_ENCRYPTED_TOPIC =
228             "CREATE TABLE "
229                     + ReturnedEncryptedTopicContract.TABLE
230                     + "("
231                     + ReturnedEncryptedTopicContract.ID
232                     + " INTEGER PRIMARY KEY, "
233                     + ReturnedEncryptedTopicContract.EPOCH_ID
234                     + " INTEGER NOT NULL, "
235                     + ReturnedEncryptedTopicContract.APP
236                     + " TEXT NOT NULL, "
237                     + ReturnedEncryptedTopicContract.SDK
238                     + " TEXT NOT NULL, "
239                     + ReturnedEncryptedTopicContract.ENCRYPTED_TOPIC
240                     + " BLOB NOT NULL, "
241                     + ReturnedEncryptedTopicContract.KEY_IDENTIFIER
242                     + " TEXT NOT NULL, "
243                     + ReturnedEncryptedTopicContract.ENCAPSULATED_KEY
244                     + " BLOB NOT NULL"
245                     + ")";
246 
247     /**
248      * Table to store the app/sdk usage history. Whenever an app or sdk calls the Topics API, one
249      * entry will be generated with the timestamp.
250      */
251     public interface UsageHistoryContract {
252         String TABLE = TOPICS_TABLE_PREFIX + "usage_history";
253         String EPOCH_ID = "epoch_id";
254         String APP = "app";
255         String SDK = "sdk";
256     }
257 
258     /** Create Statement for the Usage History table */
259     @VisibleForTesting
260     public static final String CREATE_TABLE_USAGE_HISTORY =
261             "CREATE TABLE "
262                     + UsageHistoryContract.TABLE
263                     + "("
264                     + UsageHistoryContract.EPOCH_ID
265                     + " INTEGER NOT NULL, "
266                     + UsageHistoryContract.APP
267                     + " TEXT NOT NULL, "
268                     + UsageHistoryContract.SDK
269                     + " TEXT"
270                     + ")";
271 
272     /**
273      * Table to store history for app only Whenever an app calls the Topics API, one entry will be
274      * generated.
275      */
276     public interface AppUsageHistoryContract {
277         String TABLE = TOPICS_TABLE_PREFIX + "app_usage_history";
278         String ID = "_id";
279         String EPOCH_ID = "epoch_id";
280         String APP = "app";
281     }
282 
283     /** Create Statement for the Usage History App Only table */
284     @VisibleForTesting
285     public static final String CREATE_TABLE_APP_USAGE_HISTORY =
286             "CREATE TABLE "
287                     + AppUsageHistoryContract.TABLE
288                     + "("
289                     + AppUsageHistoryContract.ID
290                     + " INTEGER PRIMARY KEY, "
291                     + AppUsageHistoryContract.EPOCH_ID
292                     + " INTEGER NOT NULL, "
293                     + AppUsageHistoryContract.APP
294                     + " TEXT NOT NULL"
295                     + ")";
296 
297     /** Table to store all blocked {@link Topic}s. Blocked topics are controlled by user. */
298     public interface BlockedTopicsContract {
299         String TABLE = TOPICS_TABLE_PREFIX + "blocked";
300         String ID = "_id";
301         String TAXONOMY_VERSION = "taxonomy_version";
302         String MODEL_VERSION = "model_version";
303         String TOPIC = "topic";
304     }
305 
306     /** Create Statement for the blocked topics table. */
307     @VisibleForTesting
308     public static final String CREATE_TABLE_BLOCKED_TOPICS =
309             "CREATE TABLE "
310                     + BlockedTopicsContract.TABLE
311                     + "("
312                     + BlockedTopicsContract.ID
313                     + " INTEGER PRIMARY KEY, "
314                     + BlockedTopicsContract.TAXONOMY_VERSION
315                     + " INTEGER NOT NULL, "
316                     + BlockedTopicsContract.MODEL_VERSION
317                     + " INTEGER NOT NULL, "
318                     + BlockedTopicsContract.TOPIC
319                     + " INTEGER NOT NULL"
320                     + ")";
321 
322     /**
323      * Table to store the original timestamp when the user calls Topics API. This table should have
324      * only 1 row that stores the origin.
325      */
326     public interface EpochOriginContract {
327         String TABLE = TOPICS_TABLE_PREFIX + "epoch_origin";
328         String ONE_ROW_CHECK = "one_row_check"; // to constrain 1 origin
329         String ORIGIN = "origin";
330     }
331 
332     /**
333      * At the first time inserting a record, it won't persist one_row_check field so that this first
334      * entry will have one_row_check = 1. Therefore, further persisting is not allowed as primary
335      * key cannot be duplicated value and one_row_check is constrained to only equal to 1 to forbid
336      * any increment.
337      */
338     @VisibleForTesting
339     public static final String CREATE_TABLE_EPOCH_ORIGIN =
340             "CREATE TABLE "
341                     + EpochOriginContract.TABLE
342                     + "("
343                     + EpochOriginContract.ONE_ROW_CHECK
344                     + " INTEGER PRIMARY KEY DEFAULT 1, "
345                     + EpochOriginContract.ORIGIN
346                     + " INTEGER NOT NULL, "
347                     + "CONSTRAINT one_row_constraint CHECK ("
348                     + EpochOriginContract.ONE_ROW_CHECK
349                     + " = 1) "
350                     + ")";
351 
352     /**
353      * Table to store classified topic to apps mapping. In an epoch, an app is a contributor to a
354      * topic if the app has called Topics API in this epoch and is classified to the topic.
355      */
356     public interface TopicContributorsContract {
357         String TABLE = TOPICS_TABLE_PREFIX + "topic_contributors";
358         String ID = "_id";
359         String EPOCH_ID = "epoch_id";
360         String TOPIC = "topic";
361         String APP = "app";
362     }
363 
364     /** The SQLite query to create topic_contributors table if it doesn't exist */
365     public static final String CREATE_TABLE_TOPIC_CONTRIBUTORS =
366             "CREATE TABLE IF NOT EXISTS "
367                     + TopicContributorsContract.TABLE
368                     + "("
369                     + TopicContributorsContract.ID
370                     + " INTEGER PRIMARY KEY, "
371                     + TopicContributorsContract.EPOCH_ID
372                     + " INTEGER NOT NULL, "
373                     + TopicContributorsContract.TOPIC
374                     + " INTEGER NOT NULL, "
375                     + AppUsageHistoryContract.APP
376                     + " TEXT NOT NULL"
377                     + ")";
378 
379     /** Consolidated list of create statements for all tables. */
380     public static final List<String> CREATE_STATEMENTS =
381             Collections.unmodifiableList(
382                     Arrays.asList(
383                             CREATE_TABLE_TOPICS_TAXONOMY,
384                             CREATE_TABLE_APP_CLASSIFICATION_TOPICS,
385                             CREATE_TABLE_TOP_TOPICS,
386                             CREATE_TABLE_RETURNED_TOPIC,
387                             CREATE_TABLE_RETURNED_ENCRYPTED_TOPIC,
388                             CREATE_TABLE_USAGE_HISTORY,
389                             CREATE_TABLE_APP_USAGE_HISTORY,
390                             CREATE_TABLE_CALLER_CAN_LEARN_TOPICS,
391                             CREATE_TABLE_BLOCKED_TOPICS,
392                             CREATE_TABLE_EPOCH_ORIGIN,
393                             CREATE_TABLE_TOPIC_CONTRIBUTORS));
394     // *******************************************************************************************
395     // * NOTE: Please check below steps before adding a new table:
396     // * 1) TopicsDao -> ALL_TOPICS_TABLES: User Consent to clear all tables
397     // * 2) EpochManager -> TABLE_INFO_FOR_EPOCH_GARBAGE_COLLECTION: GC for dat of old epochs.
398     // * 3) AppUpdateManager -> TABLE_INFO_TO_ERASE_APP_DATA: clear app data for app uninstallation
399     // * 4) DbHelper -> onUpgrade: Handle any new schema change
400     // *******************************************************************************************
401 
402     // Private constructor to prevent instantiation.
TopicsTables()403     private TopicsTables() {}
404 }
405