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