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