Add the Sec-Fetch headers to View Source. https://redmine.stoutner.com/issues/503
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / MoveToFolderDialog.java
1 /*
2  * Copyright © 2016-2019 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.dialogs;
21
22 import android.annotation.SuppressLint;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.SharedPreferences;
28 import android.database.Cursor;
29 import android.database.DatabaseUtils;
30 import android.database.MatrixCursor;
31 import android.database.MergeCursor;
32 import android.graphics.Bitmap;
33 import android.graphics.BitmapFactory;
34 import android.graphics.drawable.BitmapDrawable;
35 import android.graphics.drawable.Drawable;
36 import android.os.Bundle;
37 import android.preference.PreferenceManager;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.view.WindowManager;
41 import android.widget.AdapterView;
42 import android.widget.Button;
43 import android.widget.CursorAdapter;
44 import android.widget.ImageView;
45 import android.widget.ListView;
46 import android.widget.TextView;
47
48 import androidx.annotation.NonNull;
49 import androidx.core.content.ContextCompat;
50 import androidx.fragment.app.DialogFragment;  // The AndroidX dialog fragment must be used or an error is produced on API <= 22.
51
52 import com.stoutner.privacybrowser.R;
53 import com.stoutner.privacybrowser.activities.BookmarksActivity;
54 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
55
56 import java.io.ByteArrayOutputStream;
57
58 public class MoveToFolderDialog extends DialogFragment {
59     // Instantiate the class variables.
60     private MoveToFolderListener moveToFolderListener;
61     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
62     private StringBuilder exceptFolders;
63
64     // The public interface is used to send information back to the parent activity.
65     public interface MoveToFolderListener {
66         void onMoveToFolder(DialogFragment dialogFragment);
67     }
68
69     public void onAttach(Context context) {
70         // Run the default commands.
71         super.onAttach(context);
72
73         // Get a handle for `MoveToFolderListener` from the launching context.
74         moveToFolderListener = (MoveToFolderListener) context;
75     }
76
77     // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
78     @SuppressLint("InflateParams")
79     @Override
80     @NonNull
81     public Dialog onCreateDialog(Bundle savedInstanceState) {
82         // Initialize the database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
83         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(getContext(), null, null, 0);
84
85         // Use an alert dialog builder to create the alert dialog.
86         AlertDialog.Builder dialogBuilder;
87
88         // Get a handle for the shared preferences.
89         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
90
91         // Get the screenshot and theme preferences.
92         boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
93         boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
94
95         // Set the style according to the theme.
96         if (darkTheme) {
97             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
98         } else {
99             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
100         }
101
102         // Set the title.
103         dialogBuilder.setTitle(R.string.move_to_folder);
104
105         // Remove the incorrect lint warning that `getActivity()` might be null.
106         assert getActivity() != null;
107
108         // Set the view.  The parent view is `null` because it will be assigned by `AlertDialog`.
109         dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_dialog, null));
110
111         // Set the listener for the negative button.
112         dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
113             // Do nothing.  The `AlertDialog` will close automatically.
114         });
115
116         // Set the listener fo the positive button.
117         dialogBuilder.setPositiveButton(R.string.move, (DialogInterface dialog, int which) -> {
118             // Return the `DialogFragment` to the parent activity on save.
119             moveToFolderListener.onMoveToFolder(MoveToFolderDialog.this);
120         });
121
122         // Create an alert dialog from the alert dialog builder.
123         final AlertDialog alertDialog = dialogBuilder.create();
124
125         // Disable screenshots if not allowed.
126         if (!allowScreenshots) {
127             // Remove the warning below that `getWindow()` might be null.
128             assert alertDialog.getWindow() != null;
129
130             // Disable screenshots.
131             alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
132         }
133
134         // Show the alert dialog so the items in the layout can be modified.
135         alertDialog.show();
136
137         // Get a handle for the positive button.
138         final Button moveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
139
140         // Initially disable the positive button.
141         moveButton.setEnabled(false);
142
143         // Initialize the variables.
144         Cursor foldersCursor;
145         CursorAdapter foldersCursorAdapter;
146         exceptFolders = new StringBuilder();
147
148         // Check to see if we are in the `Home Folder`.
149         if (BookmarksActivity.currentFolder.isEmpty()) {  // Don't display `Home Folder` at the top of the `ListView`.
150             // If a folder is selected, add it and all children to the list of folders not to display.
151             long[] selectedBookmarksLongArray = BookmarksActivity.checkedItemIds;
152             for (long databaseIdLong : selectedBookmarksLongArray) {
153                 // Get `databaseIdInt` for each selected bookmark.
154                 int databaseIdInt = (int) databaseIdLong;
155
156                 // If `databaseIdInt` is a folder.
157                 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
158                     // Get the name of the selected folder.
159                     String folderName = bookmarksDatabaseHelper.getFolderName(databaseIdInt);
160
161                     // Populate the list of folders not to get.
162                     if (exceptFolders.toString().isEmpty()){
163                         // Add the selected folder to the list of folders not to display.
164                         exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName));
165                     } else {
166                         // Add the selected folder to the end of the list of folders not to display.
167                         exceptFolders.append(",");
168                         exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName));
169                     }
170
171                     // Add the selected folder's subfolders to the list of folders not to display.
172                     addSubfoldersToExceptFolders(folderName);
173                 }
174             }
175
176             // Get a cursor containing the folders to display.
177             foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString());
178
179             // Setup `foldersCursorAdaptor` with `this` context.  `false` disables autoRequery.
180             foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersCursor, false) {
181                 @Override
182                 public View newView(Context context, Cursor cursor, ViewGroup parent) {
183                     // Remove the incorrect lint warning that `.getLayoutInflater()` might be false.
184                     assert getActivity() != null;
185
186                     // Inflate the individual item layout.  `false` does not attach it to the root.
187                     return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false);
188                 }
189
190                 @Override
191                 public void bindView(View view, Context context, Cursor cursor) {
192                     // Get the folder icon from `cursor`.
193                     byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
194                     // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
195                     Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length);
196                     // Display `folderIconBitmap` in `move_to_folder_icon`.
197                     ImageView folderIconImageView = view.findViewById(R.id.move_to_folder_icon);
198                     folderIconImageView.setImageBitmap(folderIconBitmap);
199
200                     // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`.
201                     String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
202                     TextView folderNameTextView = view.findViewById(R.id.move_to_folder_name_textview);
203                     folderNameTextView.setText(folderName);
204                 }
205             };
206         } else {  // Display `Home Folder` at the top of the `ListView`.
207             // Get the home folder icon drawable and convert it to a `Bitmap`.
208             Drawable homeFolderIconDrawable = ContextCompat.getDrawable(getActivity().getApplicationContext(), R.drawable.folder_gray_bitmap);
209             BitmapDrawable homeFolderIconBitmapDrawable = (BitmapDrawable) homeFolderIconDrawable;
210             assert homeFolderIconDrawable != null;
211             Bitmap homeFolderIconBitmap = homeFolderIconBitmapDrawable.getBitmap();
212
213             // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
214             ByteArrayOutputStream homeFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
215             homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream);
216             byte[] homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray();
217
218             // Setup a `MatrixCursor` for the `Home Folder`.
219             String[] homeFolderMatrixCursorColumnNames = {BookmarksDatabaseHelper._ID, BookmarksDatabaseHelper.BOOKMARK_NAME, BookmarksDatabaseHelper.FAVORITE_ICON};
220             MatrixCursor homeFolderMatrixCursor = new MatrixCursor(homeFolderMatrixCursorColumnNames);
221             homeFolderMatrixCursor.addRow(new Object[]{0, getString(R.string.home_folder), homeFolderIconByteArray});
222
223             // Add the parent folder to the list of folders not to display.
224             exceptFolders.append(DatabaseUtils.sqlEscapeString(BookmarksActivity.currentFolder));
225
226             // If a folder is selected, add it and all children to the list of folders not to display.
227             long[] selectedBookmarksLongArray = BookmarksActivity.checkedItemIds;
228             for (long databaseIdLong : selectedBookmarksLongArray) {
229                 // Get `databaseIdInt` for each selected bookmark.
230                 int databaseIdInt = (int) databaseIdLong;
231
232                 // If `databaseIdInt` is a folder.
233                 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
234                     // Get the name of the selected folder.
235                     String folderName = bookmarksDatabaseHelper.getFolderName(databaseIdInt);
236
237                     // Add the selected folder to the end of the list of folders not to display.
238                     exceptFolders.append(",");
239                     exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName));
240
241                     // Add the selected folder's subfolders to the list of folders not to display.
242                     addSubfoldersToExceptFolders(folderName);
243                 }
244             }
245
246             // Get a `Cursor` containing the folders to display.
247             foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString());
248
249             // Combine `homeFolderMatrixCursor` and `foldersCursor`.
250             MergeCursor foldersMergeCursor = new MergeCursor(new Cursor[]{homeFolderMatrixCursor, foldersCursor});
251
252             // Setup `foldersCursorAdaptor`.  `false` disables autoRequery.
253             foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersMergeCursor, false) {
254                 @Override
255                 public View newView(Context context, Cursor cursor, ViewGroup parent) {
256                     // Remove the incorrect lint warning that `.getLayoutInflater()` might be false.
257                     assert getActivity() != null;
258
259                     // Inflate the individual item layout.  `false` does not attach it to the root.
260                     return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false);
261                 }
262
263                 @Override
264                 public void bindView(View view, Context context, Cursor cursor) {
265                     // Get the folder icon from `cursor`.
266                     byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
267                     // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
268                     Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length);
269                     // Display `folderIconBitmap` in `move_to_folder_icon`.
270                     ImageView folderIconImageView = view.findViewById(R.id.move_to_folder_icon);
271                     folderIconImageView.setImageBitmap(folderIconBitmap);
272
273                     // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`.
274                     String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
275                     TextView folderNameTextView = view.findViewById(R.id.move_to_folder_name_textview);
276                     folderNameTextView.setText(folderName);
277                 }
278             };
279         }
280
281         // Display the ListView
282         ListView foldersListView = alertDialog.findViewById(R.id.move_to_folder_listview);
283         foldersListView.setAdapter(foldersCursorAdapter);
284
285         // Enable the move button when a folder is selected.
286         foldersListView.setOnItemClickListener((AdapterView<?> parent, View view, int position, long id) -> {
287             // Enable the move button.
288             moveButton.setEnabled(true);
289         });
290
291         // `onCreateDialog` requires the return of an `AlertDialog`.
292         return alertDialog;
293     }
294
295     private void addSubfoldersToExceptFolders(String folderName) {
296         // Get a `Cursor` will all the immediate subfolders.
297         Cursor subfoldersCursor = bookmarksDatabaseHelper.getSubfolders(folderName);
298
299         for (int i = 0; i < subfoldersCursor.getCount(); i++) {
300             // Move `subfolderCursor` to the current item.
301             subfoldersCursor.moveToPosition(i);
302
303             // Get the name of the subfolder.
304             String subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
305
306             // Add the subfolder to `exceptFolders`.
307             exceptFolders.append(",");
308             exceptFolders.append(DatabaseUtils.sqlEscapeString(subfolderName));
309
310             // Run the same tasks for any subfolders of the subfolder.
311             addSubfoldersToExceptFolders(subfolderName);
312         }
313     }
314 }