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.ondevicepersonalization.services.data.user; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.database.Cursor; 22 import android.database.sqlite.SQLiteDatabase; 23 import android.database.sqlite.SQLiteException; 24 import android.util.Log; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper; 28 29 import java.util.Calendar; 30 import java.util.List; 31 32 /** DAO for accessing to vendor data tables. */ 33 public class UserDataDao { 34 private static final String TAG = "UserDataDao"; 35 36 private static UserDataDao sUserDataDao; 37 private final OnDevicePersonalizationDbHelper mDbHelper; 38 public static final int TTL_IN_MEMORY_DAYS = 30; 39 UserDataDao(OnDevicePersonalizationDbHelper dbHelper)40 private UserDataDao(OnDevicePersonalizationDbHelper dbHelper) { 41 this.mDbHelper = dbHelper; 42 } 43 44 /** 45 * Returns an instance of the UserDataDao given a context. 46 * 47 * @param context The context of the application. 48 * @return Instance of UserDataDao for accessing the requested package's table. 49 */ getInstance(Context context)50 public static UserDataDao getInstance(Context context) { 51 synchronized (UserDataDao.class) { 52 if (sUserDataDao == null) { 53 sUserDataDao = new UserDataDao( 54 OnDevicePersonalizationDbHelper.getInstance(context)); 55 } 56 return sUserDataDao; 57 } 58 } 59 60 /** 61 * Returns an instance of the UserDataDao given a context. This is used for testing only. 62 */ 63 @VisibleForTesting getInstanceForTest(Context context)64 public static UserDataDao getInstanceForTest(Context context) { 65 synchronized (UserDataDao.class) { 66 if (sUserDataDao == null) { 67 sUserDataDao = new UserDataDao( 68 OnDevicePersonalizationDbHelper.getInstanceForTest(context)); 69 } 70 return sUserDataDao; 71 } 72 } 73 74 /** 75 * Inserts location history row if it doesn't already exist. 76 * 77 * @return true if the insert succeeded, false otherwise. 78 */ insertLocationHistoryData(long timeSec, String latitude, String longitude, int source, boolean isPrecise)79 public boolean insertLocationHistoryData(long timeSec, String latitude, String longitude, 80 int source, boolean isPrecise) { 81 try { 82 SQLiteDatabase db = mDbHelper.getWritableDatabase(); 83 if (db == null) { 84 return false; 85 } 86 ContentValues values = new ContentValues(); 87 values.put(UserDataTables.LocationHistory.TIME_SEC, timeSec); 88 values.put(UserDataTables.LocationHistory.LATITUDE, latitude); 89 values.put(UserDataTables.LocationHistory.LONGITUDE, longitude); 90 values.put(UserDataTables.LocationHistory.SOURCE, source); 91 values.put(UserDataTables.LocationHistory.IS_PRECISE, isPrecise); 92 return db.insertWithOnConflict(UserDataTables.LocationHistory.TABLE_NAME, null, values, 93 SQLiteDatabase.CONFLICT_REPLACE) != -1; 94 } catch (SQLiteException e) { 95 Log.e(TAG, "Failed to insert location history data", e); 96 return false; 97 } 98 } 99 100 /** 101 * Inserts a single app usage history entry. 102 * 103 * @return true if the insert succeeded, false otherwise. 104 */ insertAppUsageStatsData(String packageName, long startingTimeSec, long endingTimeSec, long totalTimeUsedSec)105 public boolean insertAppUsageStatsData(String packageName, long startingTimeSec, 106 long endingTimeSec, long totalTimeUsedSec) { 107 try { 108 SQLiteDatabase db = mDbHelper.getWritableDatabase(); 109 ContentValues values = new ContentValues(); 110 values.put(UserDataTables.AppUsageHistory.PACKAGE_NAME, packageName); 111 values.put(UserDataTables.AppUsageHistory.STARTING_TIME_SEC, startingTimeSec); 112 values.put(UserDataTables.AppUsageHistory.ENDING_TIME_SEC, endingTimeSec); 113 values.put(UserDataTables.AppUsageHistory.TOTAL_TIME_USED_SEC, totalTimeUsedSec); 114 return db.insertWithOnConflict(UserDataTables.AppUsageHistory.TABLE_NAME, null, values, 115 SQLiteDatabase.CONFLICT_REPLACE) != -1; 116 } catch (SQLiteException e) { 117 Log.e(TAG, "Failed to insert app usage history data", e); 118 return false; 119 } 120 } 121 122 /** 123 * Batch inserts a list of [UsageStats]. 124 * @return true if all insertions succeed as a transaction, false otherwise. 125 */ batchInsertAppUsageStatsData(List<AppUsageEntry> appUsageEntries)126 public boolean batchInsertAppUsageStatsData(List<AppUsageEntry> appUsageEntries) { 127 SQLiteDatabase db = mDbHelper.getWritableDatabase(); 128 if (db == null) { 129 return false; 130 } 131 try { 132 db.beginTransaction(); 133 for (AppUsageEntry entry : appUsageEntries) { 134 if (!insertAppUsageStatsData(entry.packageName, entry.startTimeMillis, 135 entry.endTimeMillis, entry.totalTimeUsedMillis)) { 136 return false; 137 } 138 } 139 db.setTransactionSuccessful(); 140 } finally { 141 db.endTransaction(); 142 } 143 return true; 144 } 145 146 /** 147 * Read all app usage rows collected in the last x days. 148 * @return 149 */ readAppUsageInLastXDays(int dayCount)150 public Cursor readAppUsageInLastXDays(int dayCount) { 151 if (dayCount > TTL_IN_MEMORY_DAYS) { 152 Log.e(TAG, "Illegal attempt to read " + dayCount + " rows, which is more than " 153 + TTL_IN_MEMORY_DAYS + " days"); 154 return null; 155 } 156 Calendar cal = Calendar.getInstance(); 157 cal.add(Calendar.DATE, -1 * dayCount); 158 final long thresholdTimeMillis = cal.getTimeInMillis(); 159 try { 160 SQLiteDatabase db = mDbHelper.getReadableDatabase(); 161 String[] columns = new String[]{UserDataTables.AppUsageHistory.PACKAGE_NAME, 162 UserDataTables.AppUsageHistory.STARTING_TIME_SEC, 163 UserDataTables.AppUsageHistory.ENDING_TIME_SEC, 164 UserDataTables.AppUsageHistory.TOTAL_TIME_USED_SEC}; 165 String selection = UserDataTables.AppUsageHistory.ENDING_TIME_SEC + " >= ?"; 166 String[] selectionArgs = new String[]{String.valueOf(thresholdTimeMillis)}; 167 String orderBy = UserDataTables.AppUsageHistory.ENDING_TIME_SEC; 168 return db.query( 169 UserDataTables.AppUsageHistory.TABLE_NAME, 170 columns, 171 selection, 172 selectionArgs, 173 null, 174 null, 175 orderBy 176 ); 177 } catch (SQLiteException e) { 178 Log.e(TAG, "Failed to read " + UserDataTables.AppUsageHistory.TABLE_NAME 179 + " in the last " + dayCount + " days" , e); 180 } 181 return null; 182 } 183 184 /** 185 * Return all location rows collected in the last X days. 186 * @return 187 */ readLocationInLastXDays(int dayCount)188 public Cursor readLocationInLastXDays(int dayCount) { 189 if (dayCount > TTL_IN_MEMORY_DAYS) { 190 Log.e(TAG, "Illegal attempt to read " + dayCount + " rows, which is more than " 191 + TTL_IN_MEMORY_DAYS + " days"); 192 return null; 193 } 194 Calendar cal = Calendar.getInstance(); 195 cal.add(Calendar.DATE, -1 * dayCount); 196 final long thresholdTimeMillis = cal.getTimeInMillis(); 197 try { 198 SQLiteDatabase db = mDbHelper.getReadableDatabase(); 199 String[] columns = new String[]{UserDataTables.LocationHistory.TIME_SEC, 200 UserDataTables.LocationHistory.LATITUDE, 201 UserDataTables.LocationHistory.LONGITUDE, 202 UserDataTables.LocationHistory.SOURCE, 203 UserDataTables.LocationHistory.IS_PRECISE}; 204 String selection = UserDataTables.LocationHistory.TIME_SEC + " >= ?"; 205 String[] selectionArgs = new String[]{String.valueOf(thresholdTimeMillis)}; 206 String orderBy = UserDataTables.LocationHistory.TIME_SEC; 207 return db.query( 208 UserDataTables.LocationHistory.TABLE_NAME, 209 columns, 210 selection, 211 selectionArgs, 212 null, 213 null, 214 orderBy 215 ); 216 } catch (SQLiteException e) { 217 Log.e(TAG, "Failed to read " + UserDataTables.LocationHistory.TABLE_NAME 218 + " in the last " + dayCount + " days" , e); 219 } 220 return null; 221 } 222 223 /** 224 * Clear all records in user data tables. 225 * @return true if succeed, false otherwise. 226 */ clearUserData()227 public boolean clearUserData() { 228 SQLiteDatabase db = mDbHelper.getWritableDatabase(); 229 if (db == null) { 230 return false; 231 } 232 db.delete(UserDataTables.AppUsageHistory.TABLE_NAME, null, null); 233 db.delete(UserDataTables.LocationHistory.TABLE_NAME, null, null); 234 return true; 235 } 236 } 237