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