1 /* 2 * Copyright (C) 2023 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.server.adservices.data.topics; 18 19 import static com.android.server.adservices.data.topics.TopicsTables.DUMMY_MODEL_VERSION; 20 21 import android.adservices.topics.Topic; 22 import android.annotation.NonNull; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.database.Cursor; 26 import android.database.SQLException; 27 import android.database.sqlite.SQLiteDatabase; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.server.adservices.LogUtil; 31 32 import java.io.PrintWriter; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Objects; 36 import java.util.Set; 37 38 /** 39 * Data Access Object for the Topics API in adservices system service. 40 * 41 * @hide 42 */ 43 public class TopicsDao { 44 private static final Object LOCK = new Object(); 45 private static TopicsDao sSingleton; 46 47 private final TopicsDbHelper mTopicsDbHelper; 48 49 /** 50 * It's only public to unit test. 51 * 52 * @param topicsDbHelper The database to query 53 */ 54 @VisibleForTesting TopicsDao(TopicsDbHelper topicsDbHelper)55 public TopicsDao(TopicsDbHelper topicsDbHelper) { 56 mTopicsDbHelper = topicsDbHelper; 57 } 58 59 /** Returns an instance of the TopicsDAO given a context. */ 60 @NonNull getInstance(@onNull Context context)61 public static TopicsDao getInstance(@NonNull Context context) { 62 synchronized (LOCK) { 63 if (sSingleton == null) { 64 sSingleton = new TopicsDao(TopicsDbHelper.getInstance(context)); 65 } 66 return sSingleton; 67 } 68 } 69 70 /** 71 * Record {@link Topic} which should be blocked to a specific user. 72 * 73 * @param topics {@link Topic}s to block. 74 * @param userIdentifier the user id to record the blocked topic 75 */ recordBlockedTopic(@onNull List<Topic> topics, int userIdentifier)76 public void recordBlockedTopic(@NonNull List<Topic> topics, int userIdentifier) { 77 Objects.requireNonNull(topics); 78 SQLiteDatabase db = mTopicsDbHelper.safeGetWritableDatabase(); 79 if (db == null) { 80 return; 81 } 82 83 for (Topic topic : topics) { 84 // Create a new map of values, where column names are the keys 85 ContentValues values = new ContentValues(); 86 values.put(TopicsTables.BlockedTopicsContract.TOPIC, topic.getTopicId()); 87 values.put( 88 TopicsTables.BlockedTopicsContract.TAXONOMY_VERSION, 89 topic.getTaxonomyVersion()); 90 values.put(TopicsTables.BlockedTopicsContract.USER, userIdentifier); 91 92 try { 93 db.insert( 94 TopicsTables.BlockedTopicsContract.TABLE, /* nullColumnHack */ 95 null, 96 values); 97 } catch (SQLException e) { 98 LogUtil.e("Failed to record blocked topic." + e.getMessage()); 99 } 100 } 101 } 102 103 /** 104 * Remove blocked {@link Topic}. 105 * 106 * @param topic blocked {@link Topic} to remove. 107 * @param userIdentifier the user id to remove the blocked topic 108 */ removeBlockedTopic(@onNull Topic topic, int userIdentifier)109 public void removeBlockedTopic(@NonNull Topic topic, int userIdentifier) { 110 Objects.requireNonNull(topic); 111 SQLiteDatabase db = mTopicsDbHelper.safeGetWritableDatabase(); 112 if (db == null) { 113 return; 114 } 115 116 // Where statement for: topics, taxonomyVersion 117 String whereClause = 118 TopicsTables.BlockedTopicsContract.USER 119 + " = ?" 120 + " AND " 121 + TopicsTables.BlockedTopicsContract.TOPIC 122 + " = ?" 123 + " AND " 124 + TopicsTables.BlockedTopicsContract.TAXONOMY_VERSION 125 + " = ?"; 126 String[] whereArgs = { 127 String.valueOf(userIdentifier), 128 String.valueOf(topic.getTopicId()), 129 String.valueOf(topic.getTaxonomyVersion()) 130 }; 131 132 try { 133 db.delete(TopicsTables.BlockedTopicsContract.TABLE, whereClause, whereArgs); 134 } catch (SQLException e) { 135 LogUtil.e("Failed to remove blocked topic." + e.getMessage()); 136 } 137 } 138 139 /** 140 * Get a {@link List} of {@link Topic}s which are blocked. 141 * 142 * @param userIdentifier the user id to get all blocked topics 143 * @return {@link List} a {@link List} of blocked {@link Topic}s. 144 */ 145 @NonNull retrieveAllBlockedTopics(int userIdentifier)146 public Set<Topic> retrieveAllBlockedTopics(int userIdentifier) { 147 SQLiteDatabase db = mTopicsDbHelper.safeGetReadableDatabase(); 148 Set<Topic> blockedTopics = new HashSet<>(); 149 if (db == null) { 150 return blockedTopics; 151 } 152 153 String selection = TopicsTables.BlockedTopicsContract.USER + " = ?"; 154 String[] selectionArgs = {String.valueOf(userIdentifier)}; 155 156 try (Cursor cursor = 157 db.query( 158 /* distinct= */ true, 159 TopicsTables.BlockedTopicsContract.TABLE, // The table to query 160 null, // Get all columns (null for all) 161 selection, 162 selectionArgs, 163 null, // Don't group the rows 164 null, // Don't filter by row groups 165 null, // don't sort 166 null // don't limit 167 )) { 168 while (cursor.moveToNext()) { 169 long taxonomyVersion = 170 cursor.getLong( 171 cursor.getColumnIndexOrThrow( 172 TopicsTables.BlockedTopicsContract.TAXONOMY_VERSION)); 173 int topicInt = 174 cursor.getInt( 175 cursor.getColumnIndexOrThrow( 176 TopicsTables.BlockedTopicsContract.TOPIC)); 177 Topic topic = new Topic(taxonomyVersion, DUMMY_MODEL_VERSION, topicInt); 178 179 blockedTopics.add(topic); 180 } 181 } 182 183 return blockedTopics; 184 } 185 186 /** 187 * Delete all blocked topics that belongs to a user. 188 * 189 * @param userIdentifier the user id to delete data for 190 */ clearAllBlockedTopicsOfUser(int userIdentifier)191 public void clearAllBlockedTopicsOfUser(int userIdentifier) { 192 SQLiteDatabase db = mTopicsDbHelper.safeGetWritableDatabase(); 193 if (db == null) { 194 return; 195 } 196 197 String whereClause = TopicsTables.BlockedTopicsContract.USER + " = ?"; 198 String[] whereArgs = { 199 String.valueOf(userIdentifier), 200 }; 201 202 try { 203 db.delete(TopicsTables.BlockedTopicsContract.TABLE, whereClause, whereArgs); 204 } catch (SQLException e) { 205 LogUtil.e("Failed to remove all blocked topics." + e.getMessage()); 206 } 207 } 208 209 /** Dumps its internal state. */ dump(PrintWriter writer, String prefix, String[] args)210 public void dump(PrintWriter writer, String prefix, String[] args) { 211 mTopicsDbHelper.dump(writer, prefix, args); 212 } 213 } 214