da2f534d507942942a3b09ca4387d0caca7bc1a2
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / MoveToFolder.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.annotation.SuppressLint;
23 import android.app.Dialog;
24 import android.app.DialogFragment;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.database.Cursor;
28 import android.database.DatabaseUtils;
29 import android.database.MatrixCursor;
30 import android.database.MergeCursor;
31 import android.graphics.Bitmap;
32 import android.graphics.BitmapFactory;
33 import android.graphics.drawable.BitmapDrawable;
34 import android.graphics.drawable.Drawable;
35 import android.os.Bundle;
36 // If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
37 import android.support.v4.content.ContextCompat;
38 import android.support.v7.app.AlertDialog;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.CursorAdapter;
42 import android.widget.ImageView;
43 import android.widget.ListView;
44 import android.widget.TextView;
45
46 import java.io.ByteArrayOutputStream;
47
48 public class MoveToFolder extends DialogFragment {
49     // The public interface is used to send information back to the parent activity.
50     public interface MoveToFolderListener {
51         void onCancelMoveToFolder(DialogFragment dialogFragment);
52
53         void onMoveToFolder(DialogFragment dialogFragment);
54     }
55
56     // `moveToFolderListener` is used in `onAttach()` and `onCreateDialog`.
57     private MoveToFolderListener moveToFolderListener;
58
59     public void onAttach(Context context) {
60         super.onAttach(context);
61
62         // Get a handle for `MoveToFolderListener` from `parentActivity`.
63         try {
64             moveToFolderListener = (MoveToFolderListener) context;
65         } catch(ClassCastException exception) {
66             throw new ClassCastException(context.toString() + " must implement EditBookmarkFolderListener.");
67         }
68     }
69
70     // `exceptFolders` is used in `onCreateDialog()` and `addSubfoldersToExceptFolders()`.
71     private String exceptFolders;
72
73     // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
74     @SuppressLint("InflateParams")
75     @Override
76     public Dialog onCreateDialog(Bundle savedInstanceState) {
77         // Use `AlertDialog.Builder` to create the `AlertDialog`.  The style formats the color of the button text.
78         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog);
79         dialogBuilder.setTitle(R.string.move_to_folder);
80         // The parent view is `null` because it will be assigned by `AlertDialog`.
81         dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_dialog, null));
82
83         // Set an `onClick()` listener for the negative button.
84         dialogBuilder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() {
85             @Override
86             public void onClick(DialogInterface dialog, int which) {
87                 // Return the `DialogFragment` to the parent activity on cancel.
88                 moveToFolderListener.onCancelMoveToFolder(MoveToFolder.this);
89             }
90         });
91
92         // Set the `onClick()` listener fo the positive button.
93         dialogBuilder.setPositiveButton(R.string.move, new Dialog.OnClickListener() {
94             @Override
95             public void onClick(DialogInterface dialog, int which) {
96                 // Return the `DialogFragment` to the parent activity on save.
97                 moveToFolderListener.onMoveToFolder(MoveToFolder.this);
98             }
99         });
100
101         // Create an `AlertDialog` from the `AlertDialog.Builder`.
102         final AlertDialog alertDialog = dialogBuilder.create();
103
104         // We need to show the `AlertDialog` before we can modify items in the layout.
105         alertDialog.show();
106
107         // Initialize the `Cursor` and `CursorAdapter` variables.
108         Cursor foldersCursor;
109         CursorAdapter foldersCursorAdapter;
110
111         // Check to see if we are in the `Home Folder`.
112         if (BookmarksActivity.currentFolder.isEmpty()) {  // Don't display `Home Folder` at the top of the `ListView`.
113             // Initialize `exceptFolders`.
114             exceptFolders = "";
115
116             // If a folder is selected, add it and all children to the list of folders not to display.
117             long[] selectedBookmarksLongArray = BookmarksActivity.checkedItemIds;
118             for (long databaseIdLong : selectedBookmarksLongArray) {
119                 // Get `databaseIdInt` for each selected bookmark.
120                 int databaseIdInt = (int) databaseIdLong;
121
122                 // If `databaseIdInt` is a folder.
123                 if (BookmarksActivity.bookmarksDatabaseHandler.isFolder(databaseIdInt)) {
124                     // Get the name of the selected folder.
125                     String folderName = BookmarksActivity.bookmarksDatabaseHandler.getFolderName(databaseIdInt);
126
127                     if (exceptFolders.isEmpty()){
128                         // Add the selected folder to the list of folders not to display.
129                         exceptFolders = DatabaseUtils.sqlEscapeString(folderName);
130                     } else {
131                         // Add the selected folder to the end of the list of folders not to display.
132                         exceptFolders = exceptFolders + "," + DatabaseUtils.sqlEscapeString(folderName);
133                     }
134
135                     // Add the selected folder's subfolders to the list of folders not to display.
136                     addSubfoldersToExceptFolders(folderName);
137                 }
138             }
139
140             // Get a `Cursor` containing the folders to display.
141             foldersCursor = BookmarksActivity.bookmarksDatabaseHandler.getFoldersCursorExcept(exceptFolders);
142
143             // Setup `foldersCursorAdaptor` with `this` context.  `false` disables autoRequery.
144             foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersCursor, false) {
145                 @Override
146                 public View newView(Context context, Cursor cursor, ViewGroup parent) {
147                     // Inflate the individual item layout.  `false` does not attach it to the root.
148                     return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false);
149                 }
150
151                 @Override
152                 public void bindView(View view, Context context, Cursor cursor) {
153                     // Get the folder icon from `cursor`.
154                     byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHandler.FAVORITE_ICON));
155                     // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
156                     Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length);
157                     // Display `folderIconBitmap` in `move_to_folder_icon`.
158                     ImageView folderIconImageView = (ImageView) view.findViewById(R.id.move_to_folder_icon);
159                     assert folderIconImageView != null;  // Remove the warning below that `currentIconImageView` might be null;
160                     folderIconImageView.setImageBitmap(folderIconBitmap);
161
162                     // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`.
163                     String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
164                     TextView folderNameTextView = (TextView) view.findViewById(R.id.move_to_folder_name_textview);
165                     folderNameTextView.setText(folderName);
166                 }
167             };
168         } else {  // Display `Home Folder` at the top of the `ListView`.
169             // Get the home folder icon drawable and convert it to a `Bitmap`.  `this` specifies the current context.
170             Drawable homeFolderIconDrawable = ContextCompat.getDrawable(getActivity().getApplicationContext(), R.drawable.folder_grey_bitmap);
171             BitmapDrawable homeFolderIconBitmapDrawable = (BitmapDrawable) homeFolderIconDrawable;
172             Bitmap homeFolderIconBitmap = homeFolderIconBitmapDrawable.getBitmap();
173             // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
174             ByteArrayOutputStream homeFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
175             homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream);
176             byte[] homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray();
177
178             // Setup a `MatrixCursor` for the `Home Folder`.
179             String[] homeFolderMatrixCursorColumnNames = {BookmarksDatabaseHandler._ID, BookmarksDatabaseHandler.BOOKMARK_NAME, BookmarksDatabaseHandler.FAVORITE_ICON};
180             MatrixCursor homeFolderMatrixCursor = new MatrixCursor(homeFolderMatrixCursorColumnNames);
181             homeFolderMatrixCursor.addRow(new Object[]{0, getString(R.string.home_folder), homeFolderIconByteArray});
182
183             // Add the parent folder to the list of folders not to display.
184             exceptFolders = DatabaseUtils.sqlEscapeString(BookmarksActivity.currentFolder);
185
186             // If a folder is selected, add it and all children to the list of folders not to display.
187             long[] selectedBookmarksLongArray = BookmarksActivity.checkedItemIds;
188             for (long databaseIdLong : selectedBookmarksLongArray) {
189                 // Get `databaseIdInt` for each selected bookmark.
190                 int databaseIdInt = (int) databaseIdLong;
191
192                 // If `databaseIdInt` is a folder.
193                 if (BookmarksActivity.bookmarksDatabaseHandler.isFolder(databaseIdInt)) {
194                     // Get the name of the selected folder.
195                     String folderName = BookmarksActivity.bookmarksDatabaseHandler.getFolderName(databaseIdInt);
196
197                     // Add the selected folder to the end of the list of folders not to display.
198                     exceptFolders = exceptFolders + "," + DatabaseUtils.sqlEscapeString(folderName);
199
200                     // Add the selected folder's subfolders to the list of folders not to display.
201                     addSubfoldersToExceptFolders(folderName);
202                 }
203             }
204
205             // Get a `foldersCursor`.
206             foldersCursor = BookmarksActivity.bookmarksDatabaseHandler.getFoldersCursorExcept(exceptFolders);
207
208             // Combine `homeFolderMatrixCursor` and `foldersCursor`.
209             MergeCursor foldersMergeCursor = new MergeCursor(new Cursor[]{homeFolderMatrixCursor, foldersCursor});
210
211             // Setup `foldersCursorAdaptor` with `this` context.  `false` disables autoRequery.
212             foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersMergeCursor, false) {
213                 @Override
214                 public View newView(Context context, Cursor cursor, ViewGroup parent) {
215                     // Inflate the individual item layout.  `false` does not attach it to the root.
216                     return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false);
217                 }
218
219                 @Override
220                 public void bindView(View view, Context context, Cursor cursor) {
221                     // Get the folder icon from `cursor`.
222                     byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHandler.FAVORITE_ICON));
223                     // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
224                     Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length);
225                     // Display `folderIconBitmap` in `move_to_folder_icon`.
226                     ImageView folderIconImageView = (ImageView) view.findViewById(R.id.move_to_folder_icon);
227                     assert folderIconImageView != null;  // Remove the warning below that `currentIconImageView` might be null;
228                     folderIconImageView.setImageBitmap(folderIconBitmap);
229
230                     // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`.
231                     String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
232                     TextView folderNameTextView = (TextView) view.findViewById(R.id.move_to_folder_name_textview);
233                     folderNameTextView.setText(folderName);
234                 }
235             };
236         }
237
238         // Display the ListView
239         ListView foldersListView = (ListView) alertDialog.findViewById(R.id.move_to_folder_listview);
240         assert foldersListView != null;  // Remove the warning below that `foldersListView` might be null.
241         foldersListView.setAdapter(foldersCursorAdapter);
242
243         // `onCreateDialog` requires the return of an `AlertDialog`.
244         return alertDialog;
245     }
246
247     public void addSubfoldersToExceptFolders(String folderName) {
248         // Get a `Cursor` will all the immediate subfolders.
249         Cursor subfoldersCursor = BookmarksActivity.bookmarksDatabaseHandler.getSubfoldersCursor(folderName);
250
251         for (int i = 0; i < subfoldersCursor.getCount(); i++) {
252             // Move `subfolderCursor` to the current item.
253             subfoldersCursor.moveToPosition(i);
254
255             // Get the name of the subfolder.
256             String subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
257
258             // Run the same tasks for any subfolders of the subfolder.
259             addSubfoldersToExceptFolders(subfolderName);
260
261             // Add the subfolder to `exceptFolders`.
262             subfolderName = DatabaseUtils.sqlEscapeString(subfolderName);
263             exceptFolders = exceptFolders + "," + subfolderName;
264         }
265
266     }
267 }