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 = 10; 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 * {@link SliceData.SliceType} representing the inline type of the result. 89 */ 90 String SLICE_TYPE = "slice_type"; 91 92 /** 93 * Customized subtitle if it's a unavailable slice 94 */ 95 String UNAVAILABLE_SLICE_SUBTITLE = "unavailable_slice_subtitle"; 96 97 /** 98 * The uri of slice. 99 */ 100 String SLICE_URI = "slice_uri"; 101 102 /** 103 * Whether the slice should be exposed publicly. 104 */ 105 String PUBLIC_SLICE = "public_slice"; 106 107 /** 108 * Resource ID for the menu entry of the setting. 109 */ 110 String HIGHLIGHT_MENU_RESOURCE = "highlight_menu"; 111 112 /** 113 * The name of user restriction for the setting. 114 */ 115 String USER_RESTRICTION = "user_restriction"; 116 } 117 118 private static final String CREATE_SLICES_TABLE = 119 "CREATE VIRTUAL TABLE " + Tables.TABLE_SLICES_INDEX + " USING fts4" 120 + "(" 121 + IndexColumns.KEY 122 + ", " 123 + IndexColumns.SLICE_URI 124 + ", " 125 + IndexColumns.TITLE 126 + ", " 127 + IndexColumns.SUMMARY 128 + ", " 129 + IndexColumns.SCREENTITLE 130 + ", " 131 + IndexColumns.KEYWORDS 132 + ", " 133 + IndexColumns.ICON_RESOURCE 134 + ", " 135 + IndexColumns.FRAGMENT 136 + ", " 137 + IndexColumns.CONTROLLER 138 + ", " 139 + IndexColumns.SLICE_TYPE 140 + ", " 141 + IndexColumns.UNAVAILABLE_SLICE_SUBTITLE 142 + ", " 143 + IndexColumns.PUBLIC_SLICE 144 + ", " 145 + IndexColumns.HIGHLIGHT_MENU_RESOURCE 146 + ", " 147 + IndexColumns.USER_RESTRICTION 148 + " INTEGER DEFAULT 0 " 149 + ");"; 150 151 private final Context mContext; 152 153 private static SlicesDatabaseHelper sSingleton; 154 getInstance(Context context)155 public static synchronized SlicesDatabaseHelper getInstance(Context context) { 156 if (sSingleton == null) { 157 sSingleton = new SlicesDatabaseHelper(context.getApplicationContext()); 158 } 159 return sSingleton; 160 } 161 SlicesDatabaseHelper(Context context)162 private SlicesDatabaseHelper(Context context) { 163 super(context, DATABASE_NAME, null /* CursorFactor */, DATABASE_VERSION); 164 mContext = context; 165 } 166 167 @Override onCreate(SQLiteDatabase db)168 public void onCreate(SQLiteDatabase db) { 169 createDatabases(db); 170 } 171 172 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)173 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 174 if (oldVersion < DATABASE_VERSION) { 175 Log.d(TAG, "Reconstructing DB from " + oldVersion + " to " + newVersion); 176 reconstruct(db); 177 } 178 } 179 180 /** 181 * Drops the currently stored databases rebuilds them. 182 * Also un-marks the state of the data such that any subsequent call to 183 * {@link#isNewIndexingState(Context)} will return {@code true}. 184 */ reconstruct(SQLiteDatabase db)185 void reconstruct(SQLiteDatabase db) { 186 mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) 187 .edit() 188 .clear() 189 .apply(); 190 dropTables(db); 191 createDatabases(db); 192 } 193 194 /** 195 * Marks the current state of the device for the validity of the data. Should be called after 196 * a full index of the TABLE_SLICES_INDEX. 197 */ setIndexedState()198 public void setIndexedState() { 199 setBuildIndexed(); 200 setLocaleIndexed(); 201 } 202 203 /** 204 * Indicates if the indexed slice data reflects the current state of the phone. 205 * 206 * @return {@code true} if database should be rebuilt, {@code false} otherwise. 207 */ isSliceDataIndexed()208 public boolean isSliceDataIndexed() { 209 return isBuildIndexed() && isLocaleIndexed(); 210 } 211 createDatabases(SQLiteDatabase db)212 private void createDatabases(SQLiteDatabase db) { 213 db.execSQL(CREATE_SLICES_TABLE); 214 Log.d(TAG, "Created databases"); 215 } 216 dropTables(SQLiteDatabase db)217 private void dropTables(SQLiteDatabase db) { 218 db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SLICES_INDEX); 219 } 220 setBuildIndexed()221 private void setBuildIndexed() { 222 mContext.getSharedPreferences(SHARED_PREFS_TAG, 0 /* mode */) 223 .edit() 224 .putBoolean(getBuildTag(), true /* value */) 225 .apply(); 226 } 227 setLocaleIndexed()228 private void setLocaleIndexed() { 229 mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) 230 .edit() 231 .putBoolean(Locale.getDefault().toString(), true /* value */) 232 .apply(); 233 } 234 isBuildIndexed()235 private boolean isBuildIndexed() { 236 return mContext.getSharedPreferences(SHARED_PREFS_TAG, 237 Context.MODE_PRIVATE) 238 .getBoolean(getBuildTag(), false /* default */); 239 } 240 isLocaleIndexed()241 private boolean isLocaleIndexed() { 242 return mContext.getSharedPreferences(SHARED_PREFS_TAG, 243 Context.MODE_PRIVATE) 244 .getBoolean(Locale.getDefault().toString(), false /* default */); 245 } 246 247 @VisibleForTesting getBuildTag()248 String getBuildTag() { 249 return Build.FINGERPRINT; 250 } 251 }