2 * Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
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.
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.
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/>.
20 package com.stoutner.privacybrowser.dialogs
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
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
48 import com.stoutner.privacybrowser.R
50 import java.io.ByteArrayOutputStream
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"
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
64 // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
66 fun createDialog(shortcutName: String, urlString: String, favoriteIconBitmap: Bitmap): CreateHomeScreenShortcutDialog {
67 // Create a favorite icon byte array output stream.
68 val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
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)
73 // Convert the favorite icon byte array output stream to a byte array.
74 val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
76 // Create an arguments bundle.
77 val argumentsBundle = Bundle()
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)
84 // Create a new instance of the dialog.
85 val createHomeScreenShortcutDialog = CreateHomeScreenShortcutDialog()
87 // Add the bundle to the dialog.
88 createHomeScreenShortcutDialog.arguments = argumentsBundle
90 // Return the new dialog.
91 return createHomeScreenShortcutDialog
95 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
97 val arguments = requireArguments()
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)!!
104 // Convert the favorite icon byte array to a bitmap.
105 val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
107 // Create a drawable version of the favorite icon.
108 val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
110 // Use an alert dialog builder to create the dialog.
111 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
114 dialogBuilder.setTitle(R.string.create_shortcut)
117 dialogBuilder.setIcon(favoriteIconDrawable)
120 dialogBuilder.setView(R.layout.create_home_screen_shortcut_dialog)
122 // Set a listener on the close button. Using null closes the dialog without doing anything else.
123 dialogBuilder.setNegativeButton(R.string.cancel, null)
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)
131 // Create an alert dialog from the alert dialog builder.
132 val alertDialog = dialogBuilder.create()
134 // Get a handle for the shared preferences.
135 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
137 // Get the screenshot preference.
138 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
140 // Disable screenshots if not allowed.
141 if (!allowScreenshots) {
142 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
145 // The alert dialog must be shown before the contents can be modified.
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)
154 // Populate the edit texts.
155 shortcutNameEditText.setText(initialShortcutName)
156 urlEditText.setText(initialUrlString)
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) {
164 override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
168 override fun afterTextChanged(s: Editable) {
169 // Update the create button.
170 updateCreateButton(createButton)
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) {
180 override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
184 override fun afterTextChanged(s: Editable) {
185 // Update the create button.
186 updateCreateButton(createButton)
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)
197 // Manually dismiss the alert dialog.
198 alertDialog.dismiss()
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
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)
215 // Manually dismiss the alert dialog.
216 alertDialog.dismiss()
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
226 // Return the alert dialog.
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()
235 // Enable the create button if both the shortcut name and the URL are not empty.
236 createButton.isEnabled = shortcutName.isNotEmpty() && urlString.isNotEmpty()
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()
244 // Convert the favorite icon bitmap to an icon. `IconCompat` must be used until the minimum API >= 26.
245 val favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap)
247 // Create a shortcut intent.
248 val shortcutIntent = Intent(Intent.ACTION_VIEW)
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)
256 // Add the URL to the intent.
257 shortcutIntent.data = Uri.parse(urlString)
259 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
260 val shortcutInfoBuilder = ShortcutInfoCompat.Builder(requireContext(), shortcutName)
262 // Add the required fields to the shortcut info builder.
263 shortcutInfoBuilder.setIcon(favoriteIcon)
264 shortcutInfoBuilder.setIntent(shortcutIntent)
265 shortcutInfoBuilder.setShortLabel(shortcutName)
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)