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 import android.database.sqlite.SQLiteDatabase; 21 import android.database.sqlite.SQLiteOpenHelper; 22 import android.os.Build; 23 import android.util.Log; 24 25 import androidx.annotation.VisibleForTesting; 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 = 5; 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 * Customized subtitle if it's a unavailable slice 99 */ 100 String UNAVAILABLE_SLICE_SUBTITLE = "unavailable_slice_subtitle"; 101 } 102 103 private static final String CREATE_SLICES_TABLE = 104 "CREATE VIRTUAL TABLE " + Tables.TABLE_SLICES_INDEX + " USING fts4" + 105 "(" + 106 IndexColumns.KEY + 107 ", " + 108 IndexColumns.TITLE + 109 ", " + 110 IndexColumns.SUMMARY + 111 ", " + 112 IndexColumns.SCREENTITLE + 113 ", " + 114 IndexColumns.KEYWORDS + 115 ", " + 116 IndexColumns.ICON_RESOURCE + 117 ", " + 118 IndexColumns.FRAGMENT + 119 ", " + 120 IndexColumns.CONTROLLER + 121 ", " + 122 IndexColumns.PLATFORM_SLICE + 123 ", " + 124 IndexColumns.SLICE_TYPE + 125 ", " + 126 IndexColumns.UNAVAILABLE_SLICE_SUBTITLE + 127 ");"; 128 129 private final Context mContext; 130 131 private static SlicesDatabaseHelper sSingleton; 132 getInstance(Context context)133 public static synchronized SlicesDatabaseHelper getInstance(Context context) { 134 if (sSingleton == null) { 135 sSingleton = new SlicesDatabaseHelper(context.getApplicationContext()); 136 } 137 return sSingleton; 138 } 139 SlicesDatabaseHelper(Context context)140 private SlicesDatabaseHelper(Context context) { 141 super(context, DATABASE_NAME, null /* CursorFactor */, DATABASE_VERSION); 142 mContext = context; 143 } 144 145 @Override onCreate(SQLiteDatabase db)146 public void onCreate(SQLiteDatabase db) { 147 createDatabases(db); 148 } 149 150 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)151 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 152 if (oldVersion < DATABASE_VERSION) { 153 Log.d(TAG, "Reconstructing DB from " + oldVersion + " to " + newVersion); 154 reconstruct(db); 155 } 156 } 157 158 /** 159 * Drops the currently stored databases rebuilds them. 160 * Also un-marks the state of the data such that any subsequent call to 161 * {@link#isNewIndexingState(Context)} will return {@code true}. 162 */ reconstruct(SQLiteDatabase db)163 void reconstruct(SQLiteDatabase db) { 164 mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) 165 .edit() 166 .clear() 167 .apply(); 168 dropTables(db); 169 createDatabases(db); 170 } 171 172 /** 173 * Marks the current state of the device for the validity of the data. Should be called after 174 * a full index of the TABLE_SLICES_INDEX. 175 */ setIndexedState()176 public void setIndexedState() { 177 setBuildIndexed(); 178 setLocaleIndexed(); 179 } 180 181 /** 182 * Indicates if the indexed slice data reflects the current state of the phone. 183 * 184 * @return {@code true} if database should be rebuilt, {@code false} otherwise. 185 */ isSliceDataIndexed()186 public boolean isSliceDataIndexed() { 187 return isBuildIndexed() && isLocaleIndexed(); 188 } 189 createDatabases(SQLiteDatabase db)190 private void createDatabases(SQLiteDatabase db) { 191 db.execSQL(CREATE_SLICES_TABLE); 192 Log.d(TAG, "Created databases"); 193 } 194 dropTables(SQLiteDatabase db)195 private void dropTables(SQLiteDatabase db) { 196 db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SLICES_INDEX); 197 } 198 setBuildIndexed()199 private void setBuildIndexed() { 200 mContext.getSharedPreferences(SHARED_PREFS_TAG, 0 /* mode */) 201 .edit() 202 .putBoolean(getBuildTag(), true /* value */) 203 .apply(); 204 } 205 setLocaleIndexed()206 private void setLocaleIndexed() { 207 mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) 208 .edit() 209 .putBoolean(Locale.getDefault().toString(), true /* value */) 210 .apply(); 211 } 212 isBuildIndexed()213 private boolean isBuildIndexed() { 214 return mContext.getSharedPreferences(SHARED_PREFS_TAG, 215 Context.MODE_PRIVATE) 216 .getBoolean(getBuildTag(), false /* default */); 217 } 218 isLocaleIndexed()219 private boolean isLocaleIndexed() { 220 return mContext.getSharedPreferences(SHARED_PREFS_TAG, 221 Context.MODE_PRIVATE) 222 .getBoolean(Locale.getDefault().toString(), false /* default */); 223 } 224 225 @VisibleForTesting getBuildTag()226 String getBuildTag() { 227 return Build.FINGERPRINT; 228 } 229 }