Migrate five dialogs to Kotlin. https://redmine.stoutner.com/issues/543
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / CreateHomeScreenShortcutDialog.kt
1 /*
2  * Copyright © 2015-2020 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.Dialog
24 import android.content.DialogInterface
25 import android.content.Intent
26 import android.graphics.Bitmap
27 import android.graphics.BitmapFactory
28 import android.graphics.drawable.BitmapDrawable
29 import android.graphics.drawable.Drawable
30 import android.net.Uri
31 import android.os.Bundle
32 import android.text.Editable
33 import android.text.TextWatcher
34 import android.view.KeyEvent
35 import android.view.View
36 import android.view.WindowManager
37 import android.widget.Button
38 import android.widget.EditText
39 import android.widget.RadioButton
40
41 import androidx.appcompat.app.AlertDialog
42 import androidx.core.content.pm.ShortcutInfoCompat
43 import androidx.core.content.pm.ShortcutManagerCompat
44 import androidx.core.graphics.drawable.IconCompat
45 import androidx.fragment.app.DialogFragment
46 import androidx.preference.PreferenceManager
47
48 import com.stoutner.privacybrowser.BuildConfig
49 import com.stoutner.privacybrowser.R
50
51 import java.io.ByteArrayOutputStream
52
53 class CreateHomeScreenShortcutDialog: DialogFragment() {
54     // Define the class variables.
55     private lateinit var shortcutNameEditText: EditText
56     private lateinit var urlEditText: EditText
57     private lateinit var openWithPrivacyBrowserRadioButton: RadioButton
58
59     companion object {
60         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
61         @JvmStatic
62         fun createDialog(shortcutName: String, urlString: String, favoriteIconBitmap: Bitmap): CreateHomeScreenShortcutDialog {
63             // Create a favorite icon byte array output stream.
64             val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
65
66             // Convert the favorite icon to a PNG and place it in the byte array output stream.  `0` is for lossless compression (the only option for a PNG).
67             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
68
69             // Convert the byte array output stream to a byte array.
70             val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
71
72             // Create an arguments bundle.
73             val argumentsBundle = Bundle()
74
75             // Store the variables in the bundle.
76             argumentsBundle.putString("shortcut_name", shortcutName)
77             argumentsBundle.putString("url_string", urlString)
78             argumentsBundle.putByteArray("favorite_icon_byte_array", favoriteIconByteArray)
79
80             // Create a new instance of the dialog.
81             val createHomeScreenShortcutDialog = CreateHomeScreenShortcutDialog()
82
83             // Add the bundle to the dialog.
84             createHomeScreenShortcutDialog.arguments = argumentsBundle
85
86             // Return the new dialog.
87             return createHomeScreenShortcutDialog
88         }
89     }
90
91     // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
92     @SuppressLint("InflateParams")
93     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
94         // Get the arguments.
95         val arguments = requireArguments()
96
97         // Get the strings from the arguments.
98         val initialShortcutName = arguments.getString("shortcut_name")
99         val initialUrlString = arguments.getString("url_string")
100
101         // Get the favorite icon byte array.
102         val favoriteIconByteArray = arguments.getByteArray("favorite_icon_byte_array")!!
103
104         // Convert the favorite icon byte array to a bitmap.
105         val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
106
107         // Get a handle for the shared preferences.
108         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
109
110         // Get the theme and screenshot preferences.
111         val allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false)
112         val darkTheme = sharedPreferences.getBoolean("dark_theme", false)
113
114         // Use an alert dialog builder to create the dialog and set the style according to the theme.
115         val dialogBuilder = if (darkTheme) {
116             AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialogDark)
117         } else {
118             AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialogLight)
119         }
120
121         // Create a drawable version of the favorite icon.
122         val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
123
124         // Set the title and icon.
125         dialogBuilder.setTitle(R.string.create_shortcut)
126         dialogBuilder.setIcon(favoriteIconDrawable)
127
128         // Set the view.  The parent view is null because it will be assigned by the alert dialog.
129         dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null))
130
131         // Set a listener on the close button.  Using null closes the dialog without doing anything else.
132         dialogBuilder.setNegativeButton(R.string.cancel, null)
133
134         // Set a listener on the create button.
135         dialogBuilder.setPositiveButton(R.string.create) { _: DialogInterface, _: Int ->
136             // Create the home screen shortcut.
137             createHomeScreenShortcut(favoriteIconBitmap)
138         }
139
140         // Create an alert dialog from the alert dialog builder.
141         val alertDialog = dialogBuilder.create()
142
143         // Disable screenshots if not allowed.
144         if (!allowScreenshots) {
145             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
146         }
147
148         // The alert dialog must be shown before the contents can be modified.
149         alertDialog.show()
150
151         // Get handles for the views.
152         shortcutNameEditText = alertDialog.findViewById(R.id.shortcut_name_edittext)!!
153         urlEditText = alertDialog.findViewById(R.id.url_edittext)!!
154         openWithPrivacyBrowserRadioButton = alertDialog.findViewById(R.id.open_with_privacy_browser_radiobutton)!!
155         val createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
156
157         // Populate the edit texts.
158         shortcutNameEditText.setText(initialShortcutName)
159         urlEditText.setText(initialUrlString)
160
161         // Add a text change listener to the shortcut name edit text.
162         shortcutNameEditText.addTextChangedListener(object: TextWatcher {
163             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
164                 // Do nothing.
165             }
166
167             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
168                 // Do nothing.
169             }
170
171             override fun afterTextChanged(s: Editable) {
172                 // Update the create button.
173                 updateCreateButton(createButton)
174             }
175         })
176
177         // Add a text change listener to the URL edit text.
178         urlEditText.addTextChangedListener(object : TextWatcher {
179             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
180                 // Do nothing.
181             }
182
183             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
184                 // Do nothing.
185             }
186
187             override fun afterTextChanged(s: Editable) {
188                 // Update the create button.
189                 updateCreateButton(createButton)
190             }
191         })
192
193         // Allow the enter key on the keyboard to create the shortcut when editing the name.
194         shortcutNameEditText.setOnKeyListener { _: View?, keyCode: Int, keyEvent: KeyEvent ->
195             // Check the key code, event, and button status.
196             if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && createButton.isEnabled) {  // The event is a key-down on the enter key and the create button is enabled.
197                 // Create the home screen shortcut.
198                 createHomeScreenShortcut(favoriteIconBitmap)
199
200                 // Manually dismiss the alert dialog.
201                 alertDialog.dismiss()
202
203                 // Consume the event.
204                 return@setOnKeyListener true
205             } else {  // Some other key was pressed or the create button is disabled.
206                 // Do not consume the event.
207                 return@setOnKeyListener false
208             }
209         }
210
211         // Set the enter key on the keyboard to create the shortcut when editing the URL.
212         urlEditText.setOnKeyListener { _: View?, keyCode: Int, keyEvent: KeyEvent ->
213             // Check the key code, event, and button status.
214             if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && createButton.isEnabled) {  // The event is a key-down on the enter key and the create button is enabled.
215                 // Create the home screen shortcut.
216                 createHomeScreenShortcut(favoriteIconBitmap)
217
218                 // Manually dismiss the alert dialog.
219                 alertDialog.dismiss()
220
221                 // Consume the event.
222                 return@setOnKeyListener true
223             } else {  // Some other key was pressed or the create button is disabled.
224                 // Do not consume the event.
225                 return@setOnKeyListener false
226             }
227         }
228
229         // Return the alert dialog.
230         return alertDialog
231     }
232
233     private fun updateCreateButton(createButton: Button) {
234         // Get the contents of the edit texts.
235         val shortcutName = shortcutNameEditText.text.toString()
236         val urlString = urlEditText.text.toString()
237
238         // Enable the create button if both the shortcut name and the URL are not empty.
239         createButton.isEnabled = shortcutName.isNotEmpty() && urlString.isNotEmpty()
240     }
241
242     private fun createHomeScreenShortcut(favoriteIconBitmap: Bitmap) {
243         // Get the strings from the edit texts.
244         val shortcutName = shortcutNameEditText.text.toString()
245         val urlString = urlEditText.text.toString()
246
247         // Convert the favorite icon bitmap to an icon.  `IconCompat` must be used until the minimum API >= 26.
248         val favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap)
249
250         // Create a shortcut intent.
251         val shortcutIntent = Intent(Intent.ACTION_VIEW)
252
253         // Check to see if the shortcut should open up Privacy Browser explicitly.
254         if (openWithPrivacyBrowserRadioButton.isChecked) {
255             // Set the current application ID as the target package.
256             shortcutIntent.setPackage(BuildConfig.APPLICATION_ID)
257         }
258
259         // Add the URL to the intent.
260         shortcutIntent.data = Uri.parse(urlString)
261
262         // Create a shortcut info builder.  The shortcut name becomes the shortcut ID.
263         val shortcutInfoBuilder = ShortcutInfoCompat.Builder(requireContext(), shortcutName)
264
265         // Add the required fields to the shortcut info builder.
266         shortcutInfoBuilder.setIcon(favoriteIcon)
267         shortcutInfoBuilder.setIntent(shortcutIntent)
268         shortcutInfoBuilder.setShortLabel(shortcutName)
269
270         // Add the shortcut to the home screen.  `ShortcutManagerCompat` can be switched to `ShortcutManager` once the minimum API >= 26.
271         ShortcutManagerCompat.requestPinShortcut(requireContext(), shortcutInfoBuilder.build(), null)
272     }
273 }