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