Add the ability to move bookmarks to folders.
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / BookmarksDatabaseHandler.java
1 /**
2  * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser;
21
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.database.Cursor;
25 import android.database.DatabaseUtils;
26 import android.database.sqlite.SQLiteDatabase;
27 import android.database.sqlite.SQLiteOpenHelper;
28 import android.provider.ContactsContract;
29 import android.support.design.widget.Snackbar;
30 import android.support.v4.content.ContextCompat;
31
32 public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
33     private static final int SCHEMA_VERSION = 1;
34     private static final String BOOKMARKS_DATABASE = "bookmarks.db";
35     private static final String BOOKMARKS_TABLE = "bookmarks";
36
37     public static final String _ID = "_id";
38     public static final String DISPLAY_ORDER = "displayorder";
39     public static final String BOOKMARK_NAME = "bookmarkname";
40     public static final String BOOKMARK_URL = "bookmarkurl";
41     public static final String PARENT_FOLDER = "parentfolder";
42     public static final String IS_FOLDER = "isfolder";
43     public static final String FAVORITE_ICON = "favoriteicon";
44
45     public BookmarksDatabaseHandler(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
46         super(context, BOOKMARKS_DATABASE, factory, SCHEMA_VERSION);
47     }
48
49     @Override
50     public void onCreate(SQLiteDatabase bookmarksDatabase) {
51         // Create the database if it doesn't exist.
52         String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
53                 _ID + " integer primary key, " +
54                 DISPLAY_ORDER + " integer, " +
55                 BOOKMARK_NAME + " text, " +
56                 BOOKMARK_URL + " text, " +
57                 PARENT_FOLDER + " text, " +
58                 IS_FOLDER + " boolean, " +
59                 FAVORITE_ICON + " blob);";
60
61         bookmarksDatabase.execSQL(CREATE_BOOKMARKS_TABLE);
62     }
63
64     @Override
65     public void onUpgrade(SQLiteDatabase bookmarksDatabase, int oldVersion, int newVersion) {
66         // Code for upgrading the database will be added here when the schema version > 1.
67     }
68
69     public void createBookmark(String bookmarkName, String bookmarkURL, int displayOrder, String parentFolder, byte[] favoriteIcon) {
70         ContentValues bookmarkContentValues = new ContentValues();
71
72         // ID is created automatically.
73         bookmarkContentValues.put(DISPLAY_ORDER, displayOrder);
74         bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName);
75         bookmarkContentValues.put(BOOKMARK_URL, bookmarkURL);
76         bookmarkContentValues.put(PARENT_FOLDER, parentFolder);
77         bookmarkContentValues.put(IS_FOLDER, false);
78         bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon);
79
80         // Get a writable database handle.
81         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
82
83         // The second argument is `null`, which makes it so that completely null rows cannot be created.  Not a problem in our case.
84         bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues);
85
86         // Close the database handle.
87         bookmarksDatabase.close();
88     }
89
90     public void createFolder(String folderName, int displayOrder, String parentFolder, byte[] favoriteIcon) {
91         ContentValues bookmarkContentValues = new ContentValues();
92
93         // ID is created automatically.
94         bookmarkContentValues.put(DISPLAY_ORDER, displayOrder);
95         bookmarkContentValues.put(BOOKMARK_NAME, folderName);
96         bookmarkContentValues.put(PARENT_FOLDER, parentFolder);
97         bookmarkContentValues.put(IS_FOLDER, true);
98         bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon);
99
100         // Get a writable database handle.
101         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
102
103         // The second argument is `null`, which makes it so that completely null rows cannot be created.  Not a problem in our case.
104         bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues);
105
106         // Close the database handle.
107         bookmarksDatabase.close();
108     }
109
110     public Cursor getBookmarkCursor(int databaseId) {
111         // Get a readable database handle.
112         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
113
114         // Prepare the SQL statement to get the `Cursor` for `databaseId`
115         final String GET_ONE_BOOKMARK = "Select * FROM " + BOOKMARKS_TABLE +
116                 " WHERE " + _ID + " = " + databaseId;
117
118         // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.
119         // We can't close the `Cursor` because we need to use it in the parent activity.
120         return bookmarksDatabase.rawQuery(GET_ONE_BOOKMARK, null);
121     }
122
123     public String getFolderName (int databaseId) {
124         // Get a readable database handle.
125         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
126
127         // Prepare the SQL statement to get the `Cursor` for the folder.
128         final String GET_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
129                 " WHERE " + _ID + " = " + databaseId;
130
131         // Get `folderCursor`.  The second argument is `null` because there are no `selectionArgs`.
132         Cursor folderCursor = bookmarksDatabase.rawQuery(GET_FOLDER, null);
133
134         // Get `folderName`.
135         folderCursor.moveToFirst();
136         String folderName = folderCursor.getString(folderCursor.getColumnIndex(BOOKMARK_NAME));
137
138         // Close the cursor and the database handle.
139         folderCursor.close();
140         bookmarksDatabase.close();
141
142         // Return the folder name.
143         return folderName;
144     }
145
146     public Cursor getFolderCursor(String folderName) {
147         // Get a readable database handle.
148         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
149
150         // SQL escape `folderName`.
151         folderName = DatabaseUtils.sqlEscapeString(folderName);
152
153         // Prepare the SQL statement to get the `Cursor` for the folder.
154         final String GET_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
155                 " WHERE " + BOOKMARK_NAME + " = " + folderName +
156                 " AND " + IS_FOLDER + " = " + 1;
157
158         // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.
159         // We can't close the `Cursor` because we need to use it in the parent activity.
160         return bookmarksDatabase.rawQuery(GET_FOLDER, null);
161     }
162
163     public Cursor getFoldersCursorExcept(String exceptFolders) {
164         // Get a readable database handle.
165         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
166
167         // Prepare the SQL statement to get the `Cursor` for the folders.
168         final String GET_FOLDERS_EXCEPT = "Select * FROM " + BOOKMARKS_TABLE +
169                 " WHERE " + IS_FOLDER + " = " + 1 +
170                 " AND " + BOOKMARK_NAME + " NOT IN (" + exceptFolders +
171                 ") ORDER BY " + BOOKMARK_NAME + " ASC";
172
173         // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.
174         // We can't close the `Cursor` because we need to use it in the parent activity.
175         return bookmarksDatabase.rawQuery(GET_FOLDERS_EXCEPT, null);
176     }
177
178     public Cursor getSubfoldersCursor(String currentFolder) {
179         // Get a readable database handle.
180         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
181
182         // SQL escape `currentFolder.
183         currentFolder = DatabaseUtils.sqlEscapeString(currentFolder);
184
185         // Prepare the SQL statement to get the `Cursor` for the subfolders.
186         final String GET_SUBFOLDERS = "Select * FROM " + BOOKMARKS_TABLE +
187                 " WHERE " + PARENT_FOLDER + " = " + currentFolder +
188                 " AND " + IS_FOLDER + " = " + 1;
189
190         // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.
191         // We can't close the `Cursor` because we need to use it in the parent activity.
192         return bookmarksDatabase.rawQuery(GET_SUBFOLDERS, null);
193     }
194
195     public String getParentFolder(String currentFolder) {
196         // Get a readable database handle.
197         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
198
199         // SQL escape `currentFolder`.
200         currentFolder = DatabaseUtils.sqlEscapeString(currentFolder);
201
202         // Prepare the SQL statement to get the parent folder.
203         final String GET_PARENT_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
204                 " WHERE " + IS_FOLDER + " = " + 1 +
205                 " AND " + BOOKMARK_NAME + " = " + currentFolder;
206
207         // The second argument is `null` because there are no `selectionArgs`.
208         Cursor bookmarkCursor = bookmarksDatabase.rawQuery(GET_PARENT_FOLDER, null);
209         bookmarkCursor.moveToFirst();
210
211         // Store the name of the parent folder.
212         String parentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(PARENT_FOLDER));
213
214         // Close the `Cursor`.
215         bookmarkCursor.close();
216
217         return parentFolder;
218     }
219
220     public Cursor getAllBookmarksCursor() {
221         // Get a readable database handle.
222         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
223
224         // Get everything in the BOOKMARKS_TABLE.
225         final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE;
226
227         // Return the results as a Cursor.  The second argument is `null` because there are no selectionArgs.
228         // We can't close the Cursor because we need to use it in the parent activity.
229         return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
230     }
231
232     public Cursor getAllBookmarksCursorByDisplayOrder(String folderName) {
233         // Get a readable database handle.
234         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
235
236         // SQL escape `folderName`.
237         folderName = DatabaseUtils.sqlEscapeString(folderName);
238
239         // Get everything in the BOOKMARKS_TABLE.
240         final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE +
241                 " WHERE " + PARENT_FOLDER + " = " + folderName +
242                 " ORDER BY " + DISPLAY_ORDER + " ASC";
243
244         // Return the results as a Cursor.  The second argument is `null` because there are no selectionArgs.
245         // We can't close the Cursor because we need to use it in the parent activity.
246         return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
247     }
248
249     public Cursor getBookmarksCursorExcept(long[] exceptIdLongArray, String folderName) {
250         // Get a readable database handle.
251         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
252
253         // Prepare a string that contains the comma-separated list of IDs not to get.
254         String doNotGetIdsString = "";
255         // Extract the array to `doNotGetIdsString`.
256         for (long databaseIdLong : exceptIdLongArray) {
257             // If this is the first number, only add the number.
258             if (doNotGetIdsString.isEmpty()) {
259                 doNotGetIdsString = String.valueOf(databaseIdLong);
260             } else {  // If there already is a number in the string, place a `,` before the number.
261                 doNotGetIdsString = doNotGetIdsString + "," + databaseIdLong;
262             }
263         }
264
265         // SQL escape `folderName`.
266         folderName = DatabaseUtils.sqlEscapeString(folderName);
267
268         // Prepare the SQL statement to select all items except those with the specified IDs.
269         final String GET_All_BOOKMARKS_EXCEPT_SPECIFIED = "Select * FROM " + BOOKMARKS_TABLE +
270                 " WHERE " + PARENT_FOLDER + " = " + folderName +
271                 " AND " + _ID + " NOT IN (" + doNotGetIdsString +
272                 ") ORDER BY " + DISPLAY_ORDER + " ASC";
273
274         // Return the results as a `Cursor`.  The second argument is `null` because there are no `selectionArgs`.
275         // We can't close the `Cursor` because we need to use it in the parent activity.
276         return bookmarksDatabase.rawQuery(GET_All_BOOKMARKS_EXCEPT_SPECIFIED, null);
277     }
278
279     public boolean isFolder(int databaseId) {
280         // Get a readable database handle.
281         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
282
283         // Prepare the SQL statement to determine if `databaseId` is a folder.
284         final String CHECK_IF_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
285                 " WHERE " + _ID + " = " + databaseId;
286
287         // Populate folderCursor.  The second argument is `null` because there are no `selectionArgs`.
288         Cursor folderCursor = bookmarksDatabase.rawQuery(CHECK_IF_FOLDER, null);
289
290         // Ascertain if this database ID is a folder.
291         folderCursor.moveToFirst();
292         boolean isFolder = (folderCursor.getInt(folderCursor.getColumnIndex(IS_FOLDER)) == 1);
293
294         // Close the `Cursor` and the database handle.
295         folderCursor.close();
296         bookmarksDatabase.close();
297
298         return isFolder;
299     }
300
301     public void updateBookmark(int databaseId, String bookmarkName, String bookmarkUrl) {
302         // Store the updated values in `bookmarkContentValues`.
303         ContentValues bookmarkContentValues = new ContentValues();
304
305         bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName);
306         bookmarkContentValues.put(BOOKMARK_URL, bookmarkUrl);
307
308         // Get a writable database handle.
309         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
310
311         // Update the bookmark.  The last argument is `null` because there are no `whereArgs`.
312         bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null);
313
314         // Close the database handle.
315         bookmarksDatabase.close();
316     }
317
318     public void updateBookmark(int databaseId, String bookmarkName, String bookmarkUrl, byte[] favoriteIcon) {
319         // Store the updated values in `bookmarkContentValues`.
320         ContentValues bookmarkContentValues = new ContentValues();
321
322         bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName);
323         bookmarkContentValues.put(BOOKMARK_URL, bookmarkUrl);
324         bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon);
325
326         // Get a writable database handle.
327         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
328
329         // Update the bookmark.  The last argument is `null` because there are no `whereArgs`.
330         bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null);
331
332         // Close the database handle.
333         bookmarksDatabase.close();
334     }
335
336     public void updateFolder(int databaseId, String oldFolderName, String newFolderName) {
337         // Get a writable database handle.
338         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
339
340         // Update the folder first.  Store the updated values in `folderContentValues`.
341         ContentValues folderContentValues = new ContentValues();
342
343         folderContentValues.put(BOOKMARK_NAME, newFolderName);
344
345         // Run the update on the folder.  The last argument is `null` because there are no `whereArgs`.
346         bookmarksDatabase.update(BOOKMARKS_TABLE, folderContentValues, _ID + " = " + databaseId, null);
347
348
349         // Update the bookmarks inside the folder with the new parent folder name.
350         ContentValues bookmarkContentValues = new ContentValues();
351
352         bookmarkContentValues.put(PARENT_FOLDER, newFolderName);
353
354         // SQL escape `oldFolderName`.
355         oldFolderName = DatabaseUtils.sqlEscapeString(oldFolderName);
356
357         // Run the update on the bookmarks.  The last argument is `null` because there are no `whereArgs`.
358         bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, PARENT_FOLDER + " = " + oldFolderName, null);
359
360         // Close the database handle.
361         bookmarksDatabase.close();
362     }
363
364     public void updateFolder(int databaseId, String oldFolderName, String newFolderName, byte[] folderIcon) {
365         // Get a writable database handle.
366         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
367
368         // Update the folder first.  Store the updated values in `folderContentValues`.
369         ContentValues folderContentValues = new ContentValues();
370
371         folderContentValues.put(BOOKMARK_NAME, newFolderName);
372         folderContentValues.put(FAVORITE_ICON, folderIcon);
373
374         // Run the update on the folder.  The last argument is `null` because there are no `whereArgs`.
375         bookmarksDatabase.update(BOOKMARKS_TABLE, folderContentValues, _ID + " = " + databaseId, null);
376
377
378         // Update the bookmarks inside the folder with the new parent folder name.
379         ContentValues bookmarkContentValues = new ContentValues();
380
381         bookmarkContentValues.put(PARENT_FOLDER, newFolderName);
382
383         // SQL escape `oldFolderName`.
384         oldFolderName = DatabaseUtils.sqlEscapeString(oldFolderName);
385
386         // Run the update on the bookmarks.  The last argument is `null` because there are no `whereArgs`.
387         bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, PARENT_FOLDER + " = " + oldFolderName, null);
388
389         // Close the database handle.
390         bookmarksDatabase.close();
391     }
392
393     public void updateBookmarkDisplayOrder(int databaseId, int displayOrder) {
394         // Get a writable database handle.
395         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
396
397         // Store the new display order in `bookmarkContentValues`.
398         ContentValues bookmarkContentValues = new ContentValues();
399         bookmarkContentValues.put(DISPLAY_ORDER, displayOrder);
400
401         // Update the database.  The last argument is `null` because there are no `whereArgs`.
402         bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null);
403
404         // Close the database handle.
405         bookmarksDatabase.close();
406     }
407
408     public void moveToFolder(int databaseId, String newFolder) {
409         // Get a writable database handle.
410         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
411
412         // Get the highest `DISPLAY_ORDER` in the new folder
413         String newFolderSqlEscaped = DatabaseUtils.sqlEscapeString(newFolder);
414         final String NEW_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
415                 " WHERE " + PARENT_FOLDER + " = " + newFolderSqlEscaped +
416                 " ORDER BY " + DISPLAY_ORDER + " ASC";
417         // The second argument is `null` because there are no `selectionArgs`.
418         Cursor newFolderCursor = bookmarksDatabase.rawQuery(NEW_FOLDER, null);
419         int displayOrder;
420         if (newFolderCursor.getCount() > 0) {
421             newFolderCursor.moveToLast();
422             displayOrder = newFolderCursor.getInt(newFolderCursor.getColumnIndex(DISPLAY_ORDER)) + 1;
423         } else {
424             displayOrder = 0;
425         }
426         newFolderCursor.close();
427
428         // Store the new values in `bookmarkContentValues`.
429         ContentValues bookmarkContentValues = new ContentValues();
430         bookmarkContentValues.put(DISPLAY_ORDER, displayOrder);
431         bookmarkContentValues.put(PARENT_FOLDER, newFolder);
432
433         // Update the database.  The last argument is 'null' because there are no 'whereArgs'.
434         bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null);
435
436         // Close the database handle.
437         bookmarksDatabase.close();
438     }
439
440     public void deleteBookmark(int databaseId) {
441         // Get a writable database handle.
442         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
443
444         // Deletes the row with the given databaseId.  The last argument is null because we don't need additional parameters.
445         bookmarksDatabase.delete(BOOKMARKS_TABLE, _ID + " = " + databaseId, null);
446
447         // Close the database handle.
448         bookmarksDatabase.close();
449     }
450 }