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; 18 19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATABASE_READ_EXCEPTION; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATABASE_WRITE_EXCEPTION; 21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP; 22 23 import android.annotation.Nullable; 24 import android.content.Context; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.database.sqlite.SQLiteException; 27 import android.database.sqlite.SQLiteOpenHelper; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 31 import com.android.ondevicepersonalization.services.data.events.EventStateContract; 32 import com.android.ondevicepersonalization.services.data.events.EventsContract; 33 import com.android.ondevicepersonalization.services.data.events.QueriesContract; 34 import com.android.ondevicepersonalization.services.data.user.UserDataContract; 35 import com.android.ondevicepersonalization.services.data.vendor.VendorSettingsContract; 36 import com.android.ondevicepersonalization.services.statsd.errorlogging.ClientErrorLogger; 37 38 import java.util.List; 39 40 /** Helper to manage the OnDevicePersonalization database. */ 41 public class OnDevicePersonalizationDbHelper extends SQLiteOpenHelper { 42 43 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 44 private static final String TAG = "OnDevicePersonalizationDbHelper"; 45 46 public static final int DATABASE_VERSION = 5; 47 private static final String DATABASE_NAME = "ondevicepersonalization.db"; 48 49 private static volatile OnDevicePersonalizationDbHelper sSingleton = null; 50 OnDevicePersonalizationDbHelper(Context context, String dbName)51 private OnDevicePersonalizationDbHelper(Context context, String dbName) { 52 super(context, dbName, null, DATABASE_VERSION); 53 } 54 55 /** Returns an instance of the OnDevicePersonalizationDbHelper given a context. */ getInstance(Context context)56 public static OnDevicePersonalizationDbHelper getInstance(Context context) { 57 if (sSingleton == null) { 58 synchronized (OnDevicePersonalizationDbHelper.class) { 59 if (sSingleton == null) { 60 sSingleton = 61 new OnDevicePersonalizationDbHelper( 62 context.getApplicationContext(), DATABASE_NAME); 63 } 64 } 65 } 66 return sSingleton; 67 } 68 69 /** 70 * Returns an instance of the OnDevicePersonalizationDbHelper given a context. This is used for 71 * testing only. 72 */ 73 @VisibleForTesting getInstanceForTest(Context context)74 public static OnDevicePersonalizationDbHelper getInstanceForTest(Context context) { 75 synchronized (OnDevicePersonalizationDbHelper.class) { 76 if (sSingleton == null) { 77 // Use null database name to make it in-memory 78 sSingleton = new OnDevicePersonalizationDbHelper(context, null); 79 } 80 return sSingleton; 81 } 82 } 83 84 @Override onCreate(SQLiteDatabase db)85 public void onCreate(SQLiteDatabase db) { 86 db.execSQL(VendorSettingsContract.VendorSettingsEntry.CREATE_TABLE_STATEMENT); 87 88 // Queries and events tables. 89 db.execSQL(QueriesContract.QueriesEntry.CREATE_TABLE_STATEMENT); 90 db.execSQL(EventsContract.EventsEntry.CREATE_TABLE_STATEMENT); 91 db.execSQL(EventStateContract.EventStateEntry.CREATE_TABLE_STATEMENT); 92 db.execSQL(UserDataContract.AppInstall.CREATE_TABLE_STATEMENT); 93 } 94 95 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)96 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 97 sLogger.d(TAG + ": DB upgrade from " + oldVersion + " to " + newVersion); 98 99 if (oldVersion < 2) { 100 execSqlIgnoreError(db, QueriesContract.QueriesEntry.UPGRADE_V1_TO_V2_STATEMENT); 101 } 102 103 if (oldVersion < 3) { 104 execSqlIgnoreError(db, QueriesContract.QueriesEntry.UPGRADE_V2_TO_V3_STATEMENT); 105 } 106 107 if (oldVersion < 4) { 108 execSqlIgnoreError(db, UserDataContract.AppInstall.CREATE_TABLE_STATEMENT); 109 } 110 111 if (oldVersion < 5) { 112 execSqlIgnoreError(db, EventsContract.EventsEntry.UPGRADE_V4_TO_V5_STATEMENTS); 113 } 114 } 115 execSqlIgnoreError(SQLiteDatabase db, List<String> sqls)116 private void execSqlIgnoreError(SQLiteDatabase db, List<String> sqls) { 117 for (String sql : sqls) { 118 execSqlIgnoreError(db, sql); 119 } 120 } 121 execSqlIgnoreError(SQLiteDatabase db, String sql)122 private void execSqlIgnoreError(SQLiteDatabase db, String sql) { 123 try { 124 db.execSQL(sql); 125 } catch (Exception e) { 126 // Ignore upgrade errors on upgrade after downgrade: the column is already present. 127 sLogger.w(TAG + ": error ", e); 128 } 129 } 130 131 @Override onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)132 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 133 sLogger.d(TAG + ": DB downgrade from " + newVersion + " to " + oldVersion); 134 // All data is retained for the package between upgrades and rollbacks. Update the 135 // DB version to the oldVersion, but maintain the data and schema from the new Version. It 136 // is assumed that the new version will be fully backward compatible. 137 } 138 139 @Override onConfigure(SQLiteDatabase db)140 public void onConfigure(SQLiteDatabase db) { 141 db.setForeignKeyConstraintsEnabled(true); 142 db.enableWriteAheadLogging(); 143 } 144 145 /** Wraps getWritableDatabase to catch SQLiteException and log error. */ 146 @Nullable safeGetWritableDatabase()147 public SQLiteDatabase safeGetWritableDatabase() { 148 try { 149 return super.getWritableDatabase(); 150 } catch (SQLiteException e) { 151 sLogger.e(e, TAG + ": Failed to get a writeable database"); 152 ClientErrorLogger.getInstance() 153 .logErrorWithExceptionInfo( 154 e, 155 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATABASE_WRITE_EXCEPTION, 156 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP); 157 return null; 158 } 159 } 160 161 /** Wraps getReadableDatabase to catch SQLiteException and log error. */ 162 @Nullable safeGetReadableDatabase()163 public SQLiteDatabase safeGetReadableDatabase() { 164 try { 165 return super.getReadableDatabase(); 166 } catch (SQLiteException e) { 167 sLogger.e(e, TAG + ": Failed to get a readable database"); 168 ClientErrorLogger.getInstance() 169 .logErrorWithExceptionInfo( 170 e, 171 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DATABASE_READ_EXCEPTION, 172 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__ODP); 173 return null; 174 } 175 } 176 } 177