/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.settings.slices; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Build; import android.util.Log; import androidx.annotation.VisibleForTesting; import java.util.Locale; /** * Defines the schema for the Slices database. */ public class SlicesDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "SlicesDatabaseHelper"; private static final String DATABASE_NAME = "slices_index.db"; private static final String SHARED_PREFS_TAG = "slices_shared_prefs"; private static final int DATABASE_VERSION = 10; public interface Tables { String TABLE_SLICES_INDEX = "slices_index"; } public interface IndexColumns { /** * Primary key of the DB. Preference key from preference controllers. */ String KEY = "key"; /** * Title of the Setting. */ String TITLE = "title"; /** * Summary / Subtitle for the setting. */ String SUMMARY = "summary"; /** * Title of the Setting screen on which the Setting lives. */ String SCREENTITLE = "screentitle"; /** * String with a comma separated list of keywords relating to the Slice. */ String KEYWORDS = "keywords"; /** * Resource ID for the icon of the setting. Should be 0 for no icon. */ String ICON_RESOURCE = "icon"; /** * Classname of the fragment name of the page that hosts the setting. */ String FRAGMENT = "fragment"; /** * Class name of the controller backing the setting. Must be a * {@link com.android.settings.core.BasePreferenceController}. */ String CONTROLLER = "controller"; /** * {@link SliceData.SliceType} representing the inline type of the result. */ String SLICE_TYPE = "slice_type"; /** * Customized subtitle if it's a unavailable slice */ String UNAVAILABLE_SLICE_SUBTITLE = "unavailable_slice_subtitle"; /** * The uri of slice. */ String SLICE_URI = "slice_uri"; /** * Whether the slice should be exposed publicly. */ String PUBLIC_SLICE = "public_slice"; /** * Resource ID for the menu entry of the setting. */ String HIGHLIGHT_MENU_RESOURCE = "highlight_menu"; /** * The name of user restriction for the setting. */ String USER_RESTRICTION = "user_restriction"; } private static final String CREATE_SLICES_TABLE = "CREATE VIRTUAL TABLE " + Tables.TABLE_SLICES_INDEX + " USING fts4" + "(" + IndexColumns.KEY + ", " + IndexColumns.SLICE_URI + ", " + IndexColumns.TITLE + ", " + IndexColumns.SUMMARY + ", " + IndexColumns.SCREENTITLE + ", " + IndexColumns.KEYWORDS + ", " + IndexColumns.ICON_RESOURCE + ", " + IndexColumns.FRAGMENT + ", " + IndexColumns.CONTROLLER + ", " + IndexColumns.SLICE_TYPE + ", " + IndexColumns.UNAVAILABLE_SLICE_SUBTITLE + ", " + IndexColumns.PUBLIC_SLICE + ", " + IndexColumns.HIGHLIGHT_MENU_RESOURCE + ", " + IndexColumns.USER_RESTRICTION + " INTEGER DEFAULT 0 " + ");"; private final Context mContext; private static SlicesDatabaseHelper sSingleton; public static synchronized SlicesDatabaseHelper getInstance(Context context) { if (sSingleton == null) { sSingleton = new SlicesDatabaseHelper(context.getApplicationContext()); } return sSingleton; } private SlicesDatabaseHelper(Context context) { super(context, DATABASE_NAME, null /* CursorFactor */, DATABASE_VERSION); mContext = context; } @Override public void onCreate(SQLiteDatabase db) { createDatabases(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion < DATABASE_VERSION) { Log.d(TAG, "Reconstructing DB from " + oldVersion + " to " + newVersion); reconstruct(db); } } /** * Drops the currently stored databases rebuilds them. * Also un-marks the state of the data such that any subsequent call to * {@link#isNewIndexingState(Context)} will return {@code true}. */ void reconstruct(SQLiteDatabase db) { mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) .edit() .clear() .apply(); dropTables(db); createDatabases(db); } /** * Marks the current state of the device for the validity of the data. Should be called after * a full index of the TABLE_SLICES_INDEX. */ public void setIndexedState() { setBuildIndexed(); setLocaleIndexed(); } /** * Indicates if the indexed slice data reflects the current state of the phone. * * @return {@code true} if database should be rebuilt, {@code false} otherwise. */ public boolean isSliceDataIndexed() { return isBuildIndexed() && isLocaleIndexed(); } private void createDatabases(SQLiteDatabase db) { db.execSQL(CREATE_SLICES_TABLE); Log.d(TAG, "Created databases"); } private void dropTables(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SLICES_INDEX); } private void setBuildIndexed() { mContext.getSharedPreferences(SHARED_PREFS_TAG, 0 /* mode */) .edit() .putBoolean(getBuildTag(), true /* value */) .apply(); } private void setLocaleIndexed() { mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) .edit() .putBoolean(Locale.getDefault().toString(), true /* value */) .apply(); } private boolean isBuildIndexed() { return mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) .getBoolean(getBuildTag(), false /* default */); } private boolean isLocaleIndexed() { return mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) .getBoolean(Locale.getDefault().toString(), false /* default */); } @VisibleForTesting String getBuildTag() { return Build.FINGERPRINT; } }