• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.settings.search;
18 
19 import android.content.Context;
20 import android.content.pm.ResolveInfo;
21 import android.database.Cursor;
22 import android.database.sqlite.SQLiteDatabase;
23 import android.database.sqlite.SQLiteOpenHelper;
24 import android.os.Build;
25 import android.provider.SearchIndexablesContract.SiteMapColumns;
26 import android.support.annotation.VisibleForTesting;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import java.util.List;
31 
32 public class IndexDatabaseHelper extends SQLiteOpenHelper {
33 
34     private static final String TAG = "IndexDatabaseHelper";
35 
36     private static final String DATABASE_NAME = "search_index.db";
37     private static final int DATABASE_VERSION = 118;
38 
39     private static final String SHARED_PREFS_TAG = "indexing_manager";
40 
41     private static final String PREF_KEY_INDEXED_PROVIDERS = "indexed_providers";
42 
43     public interface Tables {
44         String TABLE_PREFS_INDEX = "prefs_index";
45         String TABLE_SITE_MAP = "site_map";
46         String TABLE_META_INDEX = "meta_index";
47         String TABLE_SAVED_QUERIES = "saved_queries";
48     }
49 
50     public interface IndexColumns {
51         String DOCID = "docid";
52         String LOCALE = "locale";
53         String DATA_RANK = "data_rank";
54         String DATA_TITLE = "data_title";
55         String DATA_TITLE_NORMALIZED = "data_title_normalized";
56         String DATA_SUMMARY_ON = "data_summary_on";
57         String DATA_SUMMARY_ON_NORMALIZED = "data_summary_on_normalized";
58         String DATA_SUMMARY_OFF = "data_summary_off";
59         String DATA_SUMMARY_OFF_NORMALIZED = "data_summary_off_normalized";
60         String DATA_ENTRIES = "data_entries";
61         String DATA_KEYWORDS = "data_keywords";
62         String CLASS_NAME = "class_name";
63         String SCREEN_TITLE = "screen_title";
64         String INTENT_ACTION = "intent_action";
65         String INTENT_TARGET_PACKAGE = "intent_target_package";
66         String INTENT_TARGET_CLASS = "intent_target_class";
67         String ICON = "icon";
68         String ENABLED = "enabled";
69         String DATA_KEY_REF = "data_key_reference";
70         String USER_ID = "user_id";
71         String PAYLOAD_TYPE = "payload_type";
72         String PAYLOAD = "payload";
73     }
74 
75     public interface MetaColumns {
76         String BUILD = "build";
77     }
78 
79     public interface SavedQueriesColumns {
80         String QUERY = "query";
81         String TIME_STAMP = "timestamp";
82     }
83 
84     private static final String CREATE_INDEX_TABLE =
85             "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" +
86                     "(" +
87                     IndexColumns.LOCALE +
88                     ", " +
89                     IndexColumns.DATA_RANK +
90                     ", " +
91                     IndexColumns.DATA_TITLE +
92                     ", " +
93                     IndexColumns.DATA_TITLE_NORMALIZED +
94                     ", " +
95                     IndexColumns.DATA_SUMMARY_ON +
96                     ", " +
97                     IndexColumns.DATA_SUMMARY_ON_NORMALIZED +
98                     ", " +
99                     IndexColumns.DATA_SUMMARY_OFF +
100                     ", " +
101                     IndexColumns.DATA_SUMMARY_OFF_NORMALIZED +
102                     ", " +
103                     IndexColumns.DATA_ENTRIES +
104                     ", " +
105                     IndexColumns.DATA_KEYWORDS +
106                     ", " +
107                     IndexColumns.SCREEN_TITLE +
108                     ", " +
109                     IndexColumns.CLASS_NAME +
110                     ", " +
111                     IndexColumns.ICON +
112                     ", " +
113                     IndexColumns.INTENT_ACTION +
114                     ", " +
115                     IndexColumns.INTENT_TARGET_PACKAGE +
116                     ", " +
117                     IndexColumns.INTENT_TARGET_CLASS +
118                     ", " +
119                     IndexColumns.ENABLED +
120                     ", " +
121                     IndexColumns.DATA_KEY_REF +
122                     ", " +
123                     IndexColumns.USER_ID +
124                     ", " +
125                     IndexColumns.PAYLOAD_TYPE +
126                     ", " +
127                     IndexColumns.PAYLOAD +
128                     ");";
129 
130     private static final String CREATE_META_TABLE =
131             "CREATE TABLE " + Tables.TABLE_META_INDEX +
132                     "(" +
133                     MetaColumns.BUILD + " VARCHAR(32) NOT NULL" +
134                     ")";
135 
136     private static final String CREATE_SAVED_QUERIES_TABLE =
137             "CREATE TABLE " + Tables.TABLE_SAVED_QUERIES +
138                     "(" +
139                     SavedQueriesColumns.QUERY + " VARCHAR(64) NOT NULL" +
140                     ", " +
141                     SavedQueriesColumns.TIME_STAMP + " INTEGER" +
142                     ")";
143 
144     private static final String CREATE_SITE_MAP_TABLE =
145             "CREATE VIRTUAL TABLE " + Tables.TABLE_SITE_MAP + " USING fts4" +
146                     "(" +
147                     SiteMapColumns.PARENT_CLASS +
148                     ", " +
149                     SiteMapColumns.CHILD_CLASS +
150                     ", " +
151                     SiteMapColumns.PARENT_TITLE +
152                     ", " +
153                     SiteMapColumns.CHILD_TITLE +
154                     ")";
155     private static final String INSERT_BUILD_VERSION =
156             "INSERT INTO " + Tables.TABLE_META_INDEX +
157                     " VALUES ('" + Build.VERSION.INCREMENTAL + "');";
158 
159     private static final String SELECT_BUILD_VERSION =
160             "SELECT " + MetaColumns.BUILD + " FROM " + Tables.TABLE_META_INDEX + " LIMIT 1;";
161 
162     private static IndexDatabaseHelper sSingleton;
163 
164     private final Context mContext;
165 
getInstance(Context context)166     public static synchronized IndexDatabaseHelper getInstance(Context context) {
167         if (sSingleton == null) {
168             sSingleton = new IndexDatabaseHelper(context);
169         }
170         return sSingleton;
171     }
172 
IndexDatabaseHelper(Context context)173     public IndexDatabaseHelper(Context context) {
174         super(context, DATABASE_NAME, null, DATABASE_VERSION);
175         mContext = context.getApplicationContext();
176     }
177 
178     @Override
onCreate(SQLiteDatabase db)179     public void onCreate(SQLiteDatabase db) {
180         bootstrapDB(db);
181     }
182 
bootstrapDB(SQLiteDatabase db)183     private void bootstrapDB(SQLiteDatabase db) {
184         db.execSQL(CREATE_INDEX_TABLE);
185         db.execSQL(CREATE_META_TABLE);
186         db.execSQL(CREATE_SAVED_QUERIES_TABLE);
187         db.execSQL(CREATE_SITE_MAP_TABLE);
188         db.execSQL(INSERT_BUILD_VERSION);
189         Log.i(TAG, "Bootstrapped database");
190     }
191 
192     @Override
onOpen(SQLiteDatabase db)193     public void onOpen(SQLiteDatabase db) {
194         super.onOpen(db);
195 
196         Log.i(TAG, "Using schema version: " + db.getVersion());
197 
198         if (!Build.VERSION.INCREMENTAL.equals(getBuildVersion(db))) {
199             Log.w(TAG, "Index needs to be rebuilt as build-version is not the same");
200             // We need to drop the tables and recreate them
201             reconstruct(db);
202         } else {
203             Log.i(TAG, "Index is fine");
204         }
205     }
206 
207     @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)208     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
209         if (oldVersion < DATABASE_VERSION) {
210             Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
211                     "Index needs to be rebuilt for schema version '" + newVersion + "'.");
212             // We need to drop the tables and recreate them
213             reconstruct(db);
214         }
215     }
216 
217     @Override
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)218     public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
219         Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
220                 "Index needs to be rebuilt for schema version '" + newVersion + "'.");
221         // We need to drop the tables and recreate them
222         reconstruct(db);
223     }
224 
reconstruct(SQLiteDatabase db)225     public void reconstruct(SQLiteDatabase db) {
226         mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
227                 .edit()
228                 .clear()
229                 .commit();
230         dropTables(db);
231         bootstrapDB(db);
232     }
233 
getBuildVersion(SQLiteDatabase db)234     private String getBuildVersion(SQLiteDatabase db) {
235         String version = null;
236         Cursor cursor = null;
237         try {
238             cursor = db.rawQuery(SELECT_BUILD_VERSION, null);
239             if (cursor.moveToFirst()) {
240                 version = cursor.getString(0);
241             }
242         } catch (Exception e) {
243             Log.e(TAG, "Cannot get build version from Index metadata");
244         } finally {
245             if (cursor != null) {
246                 cursor.close();
247             }
248         }
249         return version;
250     }
251 
252     @VisibleForTesting
buildProviderVersionedNames(List<ResolveInfo> providers)253     static String buildProviderVersionedNames(List<ResolveInfo> providers) {
254         StringBuilder sb = new StringBuilder();
255         for (ResolveInfo info : providers) {
256             sb.append(info.providerInfo.packageName)
257                     .append(':')
258                     .append(info.providerInfo.applicationInfo.longVersionCode)
259                     .append(',');
260         }
261         return sb.toString();
262     }
263 
setLocaleIndexed(Context context, String locale)264     static void setLocaleIndexed(Context context, String locale) {
265         context.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
266                 .edit()
267                 .putBoolean(locale, true)
268                 .apply();
269     }
270 
setProvidersIndexed(Context context, String providerVersionedNames)271     static void setProvidersIndexed(Context context, String providerVersionedNames) {
272         context.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
273                 .edit()
274                 .putString(PREF_KEY_INDEXED_PROVIDERS, providerVersionedNames)
275                 .apply();
276     }
277 
isLocaleAlreadyIndexed(Context context, String locale)278     static boolean isLocaleAlreadyIndexed(Context context, String locale) {
279         return context.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
280                 .getBoolean(locale, false);
281     }
282 
areProvidersIndexed(Context context, String providerVersionedNames)283     static boolean areProvidersIndexed(Context context, String providerVersionedNames) {
284         final String indexedProviders =
285                 context.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
286                         .getString(PREF_KEY_INDEXED_PROVIDERS, null);
287         return TextUtils.equals(indexedProviders, providerVersionedNames);
288     }
289 
isBuildIndexed(Context context, String buildNo)290     static boolean isBuildIndexed(Context context, String buildNo) {
291         return context.getSharedPreferences(SHARED_PREFS_TAG,
292                 Context.MODE_PRIVATE).getBoolean(buildNo, false);
293     }
294 
setBuildIndexed(Context context, String buildNo)295     static void setBuildIndexed(Context context, String buildNo) {
296         // Use #apply() instead of #commit() since #commit() Robolectric loop indefinitely in sdk 26
297         context.getSharedPreferences(SHARED_PREFS_TAG, 0).edit().putBoolean(buildNo, true).apply();
298     }
299 
dropTables(SQLiteDatabase db)300     private void dropTables(SQLiteDatabase db) {
301         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
302         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
303         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
304         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SITE_MAP);
305     }
306 }
307