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.slices; 18 19 import android.content.Context; 20 21 import android.database.sqlite.SQLiteDatabase; 22 import android.database.sqlite.SQLiteOpenHelper; 23 import android.os.Build; 24 import android.support.annotation.VisibleForTesting; 25 import android.util.Log; 26 27 import java.util.Locale; 28 29 /** 30 * Defines the schema for the Slices database. 31 */ 32 public class SlicesDatabaseHelper extends SQLiteOpenHelper { 33 34 private static final String TAG = "SlicesDatabaseHelper"; 35 36 private static final String DATABASE_NAME = "slices_index.db"; 37 private static final String SHARED_PREFS_TAG = "slices_shared_prefs"; 38 39 private static final int DATABASE_VERSION = 2; 40 41 public interface Tables { 42 String TABLE_SLICES_INDEX = "slices_index"; 43 } 44 45 public interface IndexColumns { 46 /** 47 * Primary key of the DB. Preference key from preference controllers. 48 */ 49 String KEY = "key"; 50 51 /** 52 * Title of the Setting. 53 */ 54 String TITLE = "title"; 55 56 /** 57 * Summary / Subtitle for the setting. 58 */ 59 String SUMMARY = "summary"; 60 61 /** 62 * Title of the Setting screen on which the Setting lives. 63 */ 64 String SCREENTITLE = "screentitle"; 65 66 /** 67 * String with a comma separated list of keywords relating to the Slice. 68 */ 69 String KEYWORDS = "keywords"; 70 71 /** 72 * Resource ID for the icon of the setting. Should be 0 for no icon. 73 */ 74 String ICON_RESOURCE = "icon"; 75 76 /** 77 * Classname of the fragment name of the page that hosts the setting. 78 */ 79 String FRAGMENT = "fragment"; 80 81 /** 82 * Class name of the controller backing the setting. Must be a 83 * {@link com.android.settings.core.BasePreferenceController}. 84 */ 85 String CONTROLLER = "controller"; 86 87 /** 88 * Boolean flag, {@code true} when the Slice is officially platform-supported. 89 */ 90 String PLATFORM_SLICE = "platform_slice"; 91 92 /** 93 * {@link SliceData.SliceType} representing the inline type of the result. 94 */ 95 String SLICE_TYPE = "slice_type"; 96 } 97 98 private static final String CREATE_SLICES_TABLE = 99 "CREATE VIRTUAL TABLE " + Tables.TABLE_SLICES_INDEX + " USING fts4" + 100 "(" + 101 IndexColumns.KEY + 102 ", " + 103 IndexColumns.TITLE + 104 ", " + 105 IndexColumns.SUMMARY + 106 ", " + 107 IndexColumns.SCREENTITLE + 108 ", " + 109 IndexColumns.KEYWORDS + 110 ", " + 111 IndexColumns.ICON_RESOURCE + 112 ", " + 113 IndexColumns.FRAGMENT + 114 ", " + 115 IndexColumns.CONTROLLER + 116 ", " + 117 IndexColumns.PLATFORM_SLICE + 118 ", " + 119 IndexColumns.SLICE_TYPE + 120 ");"; 121 122 private final Context mContext; 123 124 private static SlicesDatabaseHelper sSingleton; 125 getInstance(Context context)126 public static synchronized SlicesDatabaseHelper getInstance(Context context) { 127 if (sSingleton == null) { 128 sSingleton = new SlicesDatabaseHelper(context.getApplicationContext()); 129 } 130 return sSingleton; 131 } 132 SlicesDatabaseHelper(Context context)133 private SlicesDatabaseHelper(Context context) { 134 super(context, DATABASE_NAME, null /* CursorFactor */, DATABASE_VERSION); 135 mContext = context; 136 } 137 138 @Override onCreate(SQLiteDatabase db)139 public void onCreate(SQLiteDatabase db) { 140 createDatabases(db); 141 } 142 143 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)144 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 145 if (oldVersion < DATABASE_VERSION) { 146 Log.d(TAG, "Reconstructing DB from " + oldVersion + "to " + newVersion); 147 reconstruct(db); 148 } 149 } 150 151 /** 152 * Drops the currently stored databases rebuilds them. 153 * Also un-marks the state of the data such that any subsequent call to 154 * {@link#isNewIndexingState(Context)} will return {@code true}. 155 */ reconstruct(SQLiteDatabase db)156 void reconstruct(SQLiteDatabase db) { 157 mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) 158 .edit() 159 .clear() 160 .apply(); 161 dropTables(db); 162 createDatabases(db); 163 } 164 165 /** 166 * Marks the current state of the device for the validity of the data. Should be called after 167 * a full index of the TABLE_SLICES_INDEX. 168 */ setIndexedState()169 public void setIndexedState() { 170 setBuildIndexed(); 171 setLocaleIndexed(); 172 } 173 174 /** 175 * Indicates if the indexed slice data reflects the current state of the phone. 176 * 177 * @return {@code true} if database should be rebuilt, {@code false} otherwise. 178 */ isSliceDataIndexed()179 public boolean isSliceDataIndexed() { 180 return isBuildIndexed() && isLocaleIndexed(); 181 } 182 createDatabases(SQLiteDatabase db)183 private void createDatabases(SQLiteDatabase db) { 184 db.execSQL(CREATE_SLICES_TABLE); 185 Log.d(TAG, "Created databases"); 186 } 187 dropTables(SQLiteDatabase db)188 private void dropTables(SQLiteDatabase db) { 189 db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SLICES_INDEX); 190 } 191 setBuildIndexed()192 private void setBuildIndexed() { 193 mContext.getSharedPreferences(SHARED_PREFS_TAG, 0 /* mode */) 194 .edit() 195 .putBoolean(getBuildTag(), true /* value */) 196 .apply(); 197 } 198 setLocaleIndexed()199 private void setLocaleIndexed() { 200 mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) 201 .edit() 202 .putBoolean(Locale.getDefault().toString(), true /* value */) 203 .apply(); 204 } 205 isBuildIndexed()206 private boolean isBuildIndexed() { 207 return mContext.getSharedPreferences(SHARED_PREFS_TAG, 208 Context.MODE_PRIVATE) 209 .getBoolean(getBuildTag(), false /* default */); 210 } 211 isLocaleIndexed()212 private boolean isLocaleIndexed() { 213 return mContext.getSharedPreferences(SHARED_PREFS_TAG, 214 Context.MODE_PRIVATE) 215 .getBoolean(Locale.getDefault().toString(), false /* default */); 216 } 217 218 @VisibleForTesting getBuildTag()219 String getBuildTag() { 220 return Build.FINGERPRINT; 221 } 222 }