2 * Copyright © 2015-2020 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.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
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
48 import com.stoutner.privacybrowser.BuildConfig
49 import com.stoutner.privacybrowser.R
51 import java.io.ByteArrayOutputStream
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
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.
62 fun createDialog(shortcutName: String, urlString: String, favoriteIconBitmap: Bitmap): CreateHomeScreenShortcutDialog {
63 // Create a favorite icon byte array output stream.
64 val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
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)
69 // Convert the byte array output stream to a byte array.
70 val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
72 // Create an arguments bundle.
73 val argumentsBundle = Bundle()
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)
80 // Create a new instance of the dialog.
81 val createHomeScreenShortcutDialog = CreateHomeScreenShortcutDialog()
83 // Add the bundle to the dialog.
84 createHomeScreenShortcutDialog.arguments = argumentsBundle
86 // Return the new dialog.
87 return createHomeScreenShortcutDialog
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 {
95 val arguments = arguments!!
97 // Get the strings from the arguments.
98 val initialShortcutName = arguments.getString("shortcut_name")
99 val initialUrlString = arguments.getString("url_string")
101 // Get the favorite icon byte array.
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 // Get a handle for the shared preferences.
108 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
110 // Get the theme and screenshot preferences.
111 val allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false)
112 val darkTheme = sharedPreferences.getBoolean("dark_theme", false)
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(activity!!, R.style.PrivacyBrowserAlertDialogDark)
118 AlertDialog.Builder(activity!!, R.style.PrivacyBrowserAlertDialogLight)
121 // Create a drawable version of the favorite icon.
122 val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
124 // Set the title and icon.
125 dialogBuilder.setTitle(R.string.create_shortcut)
126 dialogBuilder.setIcon(favoriteIconDrawable)
128 // Set the view. The parent view is null because it will be assigned by the alert dialog.
129 dialogBuilder.setView(activity!!.layoutInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null))
131 // Set a listener on the close button. Using null closes the dialog without doing anything else.
132 dialogBuilder.setNegativeButton(R.string.cancel, null)
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)
140 // Create an alert dialog from the alert dialog builder.
141 val alertDialog = dialogBuilder.create()
143 // Disable screenshots if not allowed.
144 if (!allowScreenshots) {
145 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
148 // The alert dialog must be shown before the contents can be modified.
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)
157 // Populate the edit texts.
158 shortcutNameEditText.setText(initialShortcutName)
159 urlEditText.setText(initialUrlString)
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) {
167 override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
171 override fun afterTextChanged(s: Editable) {
172 // Update the create button.
173 updateCreateButton(createButton)
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) {
183 override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
187 override fun afterTextChanged(s: Editable) {
188 // Update the create button.
189 updateCreateButton(createButton)
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)
200 // Manually dismiss the alert dialog.
201 alertDialog.dismiss()
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
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)
218 // Manually dismiss the alert dialog.
219 alertDialog.dismiss()
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
229 // Return the alert dialog.
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()
238 // Enable the create button if both the shortcut name and the URL are not empty.
239 createButton.isEnabled = shortcutName.isNotEmpty() && urlString.isNotEmpty()
242 private fun createHomeScreenShortcut(favoriteIconBitmap: Bitmap) {
243 // Get a handle for the context.
244 val context = context!!
246 // Get the strings from the edit texts.
247 val shortcutName = shortcutNameEditText.text.toString()
248 val urlString = urlEditText.text.toString()
250 // Convert the favorite icon bitmap to an icon. `IconCompat` must be used until the minimum API >= 26.
251 val favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap)
253 // Create a shortcut intent.
254 val shortcutIntent = Intent(Intent.ACTION_VIEW)
256 // Check to see if the shortcut should open up Privacy Browser explicitly.
257 if (openWithPrivacyBrowserRadioButton.isChecked) {
258 // Set the current application ID as the target package.
259 shortcutIntent.setPackage(BuildConfig.APPLICATION_ID)
262 // Add the URL to the intent.
263 shortcutIntent.data = Uri.parse(urlString)
265 // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
266 val shortcutInfoBuilder = ShortcutInfoCompat.Builder(context, shortcutName)
268 // Add the required fields to the shortcut info builder.
269 shortcutInfoBuilder.setIcon(favoriteIcon)
270 shortcutInfoBuilder.setIntent(shortcutIntent)
271 shortcutInfoBuilder.setShortLabel(shortcutName)
273 // Add the shortcut to the home screen. `ShortcutManagerCompat` can be switched to `ShortcutManager` once the minimum API >= 26.
274 ShortcutManagerCompat.requestPinShortcut(context, shortcutInfoBuilder.build(), null)