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