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