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