• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }