]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Add options to copy, share, and save View Headers. https://redmine.stoutner.com...
authorSoren Stoutner <soren@stoutner.com>
Fri, 27 Oct 2023 04:12:14 +0000 (21:12 -0700)
committerSoren Stoutner <soren@stoutner.com>
Fri, 27 Oct 2023 04:12:14 +0000 (21:12 -0700)
13 files changed:
app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/ViewHeadersActivity.kt
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt
app/src/main/res/menu/view_headers_options_menu.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-fr/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-pt-rBR/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values-tr/strings.xml
app/src/main/res/values-zh-rCN/strings.xml
app/src/main/res/values/strings.xml

index 52a4075f7f3d61e2b16c1c26f3bcc22bff3fce50..367511041dbc0376e6246627c1a7ac88b38c8e5f 100644 (file)
@@ -204,8 +204,9 @@ class LogcatActivity : AppCompatActivity() {
                 // Place the clip data on the clipboard.
                 clipboardManager.setPrimaryClip(logcatClipData)
 
-                // Display a snackbar.
-                Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show()
+                // Display a snackbar if the API <= 32 (Android 12L).  Beginning in Android 13 the OS displays a notification that covers up the snackbar.
+                if (Build.VERSION.SDK_INT <= 32)
+                    Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show()
 
                 // Consume the event.
                 true
index 145ced5d4d1e9ee568a523fe44d1801487bf84e2..ce86183b021b79f83053a1f276a759ba59cb6390 100644 (file)
 
 package com.stoutner.privacybrowser.activities
 
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
 import android.os.Bundle
+import android.provider.OpenableColumns
 import android.text.SpannableStringBuilder
 import android.text.style.ForegroundColorSpan
 import android.util.TypedValue
@@ -35,11 +42,11 @@ import android.widget.EditText
 import android.widget.ProgressBar
 import android.widget.TextView
 
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.ActionBar
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.widget.Toolbar
 import androidx.core.app.NavUtils
-import androidx.fragment.app.DialogFragment
 import androidx.lifecycle.ViewModelProvider
 import androidx.preference.PreferenceManager
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@@ -58,6 +65,14 @@ import com.stoutner.privacybrowser.helpers.UrlHelper
 import com.stoutner.privacybrowser.viewmodelfactories.ViewHeadersFactory
 import com.stoutner.privacybrowser.viewmodels.HeadersViewModel
 
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.lang.Exception
+import java.nio.charset.StandardCharsets
+
 // Define the public constants.
 const val USER_AGENT = "user_agent"
 
@@ -82,7 +97,58 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener
     private lateinit var responseMessageTitleTextView: TextView
     private lateinit var responseMessageTextView: TextView
     private lateinit var responseHeadersTitleTextView: TextView
+    private lateinit var responseHeadersTextView: TextView
     private lateinit var responseBodyTitleTextView: TextView
+    private lateinit var responseBodyTextView: TextView
+
+    // Define the save text activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
+    private val saveTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri ->
+        // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
+        if (fileUri != null) {
+            // Initialize the file name string from the file URI last path segment.
+            var fileNameString = fileUri.lastPathSegment
+
+            // Query the exact file name if the API >= 26.
+            if (Build.VERSION.SDK_INT >= 26) {
+                // Get a cursor from the content resolver.
+                val contentResolverCursor = contentResolver.query(fileUri, null, null, null)!!
+
+                // Move to the first row.
+                contentResolverCursor.moveToFirst()
+
+                // Get the file name from the cursor.
+                fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
+
+                // Close the cursor.
+                contentResolverCursor.close()
+            }
+
+            try {
+                // Get the about version string.
+                val headersString = getHeadersString()
+
+                // Open an output stream.
+                val outputStream = contentResolver.openOutputStream(fileUri)!!
+
+                // Save the headers using a coroutine with Dispatchers.IO.
+                CoroutineScope(Dispatchers.Main).launch {
+                    withContext(Dispatchers.IO) {
+                        // Write the headers string to the output stream.
+                        outputStream.write(headersString.toByteArray(StandardCharsets.UTF_8))
+
+                        // Close the output stream.
+                        outputStream.close()
+                    }
+                }
+
+                // Display a snackbar with the saved logcat information.
+                Snackbar.make(urlEditText, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
+            } catch (exception: Exception) {
+                // Display a snackbar with the error message.
+                Snackbar.make(urlEditText, getString(R.string.error_saving_file, fileNameString, exception.toString()), Snackbar.LENGTH_INDEFINITE).show()
+            }
+        }
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         // Get a handle for the shared preferences.
@@ -142,9 +208,9 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener
         responseMessageTitleTextView = findViewById(R.id.response_message_title_textview)
         responseMessageTextView = findViewById(R.id.response_message_textview)
         responseHeadersTitleTextView = findViewById(R.id.response_headers_title_textview)
-        val responseHeadersTextView = findViewById<TextView>(R.id.response_headers_textview)
+        responseHeadersTextView = findViewById(R.id.response_headers_textview)
         responseBodyTitleTextView = findViewById(R.id.response_body_title_textview)
-        val responseBodyTextView = findViewById<TextView>(R.id.response_body_textview)
+        responseBodyTextView = findViewById(R.id.response_body_textview)
 
         // Initialize the gray foreground color spans for highlighting the URLs.
         initialGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500))
@@ -369,22 +435,138 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener
     }
 
     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
-        // Instantiate the about dialog fragment.
-        val aboutDialogFragment: DialogFragment = AboutViewHeadersDialog()
+        // Run the appropriate commands.
+        when (menuItem.itemId) {
+            R.id.copy_headers -> {  // Copy the headers.
+                // Get the headers string.
+                val headersString = getHeadersString()
 
-        // Show the about alert dialog.
-        aboutDialogFragment.show(supportFragmentManager, getString(R.string.about))
+                // Get a handle for the clipboard manager.
+                val clipboardManager = (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
 
-        // Consume the event.
-        return true
+                // Place the headers string in a clip data.
+                val headersClipData = ClipData.newPlainText(getString(R.string.view_headers), headersString)
+
+                // Place the clip data on the clipboard.
+                clipboardManager.setPrimaryClip(headersClipData)
+
+                // Display a snackbar if the API <= 32 (Android 12L).  Beginning in Android 13 the OS displays a notification that covers up the snackbar.
+                if (Build.VERSION.SDK_INT <= 32)
+                    Snackbar.make(urlEditText, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
+
+                // Consume the event.
+                return true
+            }
+
+            R.id.share_headers -> {  // Share the headers.
+                // Get the headers string.
+                val headersString = getHeadersString()
+
+                // Create a share intent.
+                val shareIntent = Intent(Intent.ACTION_SEND)
+
+                // Add the headers string to the intent.
+                shareIntent.putExtra(Intent.EXTRA_TEXT, headersString)
+
+                // Set the MIME type.
+                shareIntent.type = "text/plain"
+
+                // Set the intent to open in a new task.
+                shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                // Make it so.
+                startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
+
+                // Consume the event.
+                return true
+            }
+
+            R.id.save_headers -> {  // Save the headers as a text file.
+                // Get the current URL.
+                val currentUrlString = urlEditText.text.toString()
+
+                // Get a URI for the current URL.
+                val currentUri = Uri.parse(currentUrlString)
+
+                // Get the current domain name.
+                val currentDomainName = currentUri.host
+
+                // Open the file picker.
+                saveTextActivityResultLauncher.launch(getString(R.string.headers_txt, currentDomainName))
+
+                // Consume the event.
+                return true
+            }
+
+            R.id.about_view_headers -> {  // Display the about dialog.
+                // Instantiate the about dialog fragment.
+                val aboutDialogFragment = AboutViewHeadersDialog()
+
+                // Show the about alert dialog.
+                aboutDialogFragment.show(supportFragmentManager, getString(R.string.about))
+
+                // Consume the event.
+                return true
+            }
+
+            else -> {  // The home button was selected.
+                // Run the parents class on return.
+                return super.onOptionsItemSelected(menuItem)
+            }
+        }
     }
 
-    // This method must be named `goBack()` and must have a View argument to match the default back arrow in the app bar.
+    // This method must be named `goBack()` and must have a View argument to match the default back arrow in the app bar or a crash occurs.
     fun goBack(@Suppress("UNUSED_PARAMETER") view: View) {
         // Go home.
         NavUtils.navigateUpFromSameTask(this)
     }
 
+    private fun getHeadersString(): String {
+        // Initialize a headers string builder.
+        val headersStringBuilder = StringBuilder()
+
+        // Populate the SSL information if it is visible (an HTTPS URL is loaded).
+        if (sslInformationTitleTextView.visibility == View.VISIBLE) {
+            headersStringBuilder.append(sslInformationTitleTextView.text)
+            headersStringBuilder.append("\n")
+            headersStringBuilder.append(sslInformationTextView.text)
+            headersStringBuilder.append("\n\n")
+            headersStringBuilder.append(getString(R.string.available_ciphers))
+            headersStringBuilder.append("\n")
+            headersStringBuilder.append(availableCiphersString)
+            headersStringBuilder.append("\n\n")
+            headersStringBuilder.append(getString(R.string.ssl_certificate))
+            headersStringBuilder.append("\n")
+            headersStringBuilder.append(sslCertificateString)
+            headersStringBuilder.append("\n")  // Only a single new line is needed after the certificate as it already ends in one.
+        }
+
+        // Populate the request information if it is visible (an HTTP URL is loaded).
+        if (requestHeadersTitleTextView.visibility == View.VISIBLE) {
+            headersStringBuilder.append(requestHeadersTitleTextView.text)
+            headersStringBuilder.append("\n")
+            headersStringBuilder.append(requestHeadersTextView.text)
+            headersStringBuilder.append("\n\n")
+            headersStringBuilder.append(responseMessageTitleTextView.text)
+            headersStringBuilder.append("\n")
+            headersStringBuilder.append(responseMessageTextView.text)
+            headersStringBuilder.append("\n\n")
+        }
+
+        // Populate the response information, which is visible for both HTTP and content URLs.
+        headersStringBuilder.append(responseHeadersTitleTextView.text)
+        headersStringBuilder.append("\n")
+        headersStringBuilder.append(responseHeadersTextView.text)
+        headersStringBuilder.append("\n\n")
+        headersStringBuilder.append(responseBodyTitleTextView.text)
+        headersStringBuilder.append("\n")
+        headersStringBuilder.append(responseBodyTextView.text)
+
+        // Return the string.
+        return headersStringBuilder.toString()
+    }
+
     override fun loadAnyway() {
         // Load the URL anyway.
         headersViewModel.updateHeaders(urlEditText.text.toString(), true)
index c2c90f368c5f47d636a88669e6829453b8aaa1e4..03fa6f052e4f6bb3c79a0f3b0bdda9930c6711f4 100644 (file)
@@ -628,14 +628,15 @@ class AboutVersionFragment : Fragment() {
                 // Get a handle for the clipboard manager.
                 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
 
-                // Save the about version string in a clip data.
+                // Place the about version string in a clip data.
                 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
 
                 // Place the clip data on the clipboard.
                 clipboardManager.setPrimaryClip(aboutVersionClipData)
 
-                // Display a snackbar.
-                Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
+                // Display a snackbar if the API <= 32 (Android 12L).  Beginning in Android 13 the OS displays a notification that covers up the snackbar.
+                if (Build.VERSION.SDK_INT <= 32)
+                    Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
 
                 // Consume the event.
                 return true
@@ -645,20 +646,20 @@ class AboutVersionFragment : Fragment() {
                 // Get the about version string.
                 val aboutString = getAboutVersionString()
 
-                // Create an email intent.
-                val emailIntent = Intent(Intent.ACTION_SEND)
+                // Create a share intent.
+                val shareIntent = Intent(Intent.ACTION_SEND)
 
                 // Add the about version string to the intent.
-                emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
+                shareIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
 
                 // Set the MIME type.
-                emailIntent.type = "text/plain"
+                shareIntent.type = "text/plain"
 
                 // Set the intent to open in a new task.
-                emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
                 // Make it so.
-                startActivity(Intent.createChooser(emailIntent, getString(R.string.share)))
+                startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
 
                 // Consume the event.
                 return true
@@ -679,6 +680,7 @@ class AboutVersionFragment : Fragment() {
                 // Consume the event.
                 return true
             }
+
             else -> {  // The home button was selected.
                 // Run the parents class on return.
                 return super.onOptionsItemSelected(menuItem)
index d7439959adf43ddfd36958edcaf435acea62c146..5557ccf63a8a99a899ad6f72219cea3c0166bbf0 100644 (file)
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
-    <!-- `android:iconTint` can be used once API >= 26 instead of including separate drawable files. -->
+    <item
+        android:id="@+id/copy_headers"
+        android:title="@string/copy_string"
+        android:orderInCategory="10"
+        android:icon="@drawable/copy"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/share_headers"
+        android:title="@string/share"
+        android:orderInCategory="20"
+        android:icon="@drawable/share"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/save_headers"
+        android:title="@string/save"
+        android:orderInCategory="30"
+        android:icon="@drawable/save"
+        app:showAsAction="never" />
+
     <item
         android:id="@+id/about_view_headers"
         android:title="@string/about"
-        android:orderInCategory="10"
+        android:orderInCategory="40"
         android:icon="@drawable/about"
-        app:showAsAction="ifRoom" />
+        app:showAsAction="never" />
 </menu>
index 4cc9670c21b7f3de8e68ca5c0d62a924ee928d13..5abeb8eb0a3e78c5ee3c1029126c8b79ca36e8ce 100644 (file)
     <string name="error_saving_file">Fehler beim Speichern der Datei %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Unbekannter Fehler</string>
 
-    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">: \u0020</string>
     <string name="request_headers">Anfragekopfzeilen</string>
     <string name="response_message">Status-Code</string>
index b69c1f5d1fa9b9b3bef39a9a053ba9d394a59f3d..87feb9119e4e960cc1bfc6b8342d6b347239943d 100644 (file)
     <string name="error_saving_file">Error al guardar %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Error desconocido</string>
 
-    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">: \u0020</string>
     <string name="ssl_information">Información sobre SSL</string>
     <string name="applied_cipher">Cifrado aplicado</string>
index 795847187a78dcd123d748697d82387545a05f70..e6cf67fad159dee6c2813be7612e94e43b3b3988 100644 (file)
     <string name="error_saving_file">Erreur lors de l\'enregistrement de %1$s : %2$s</string>
     <string name="unknown_error">Erreur inconnue</string>
 
-    <!-- View Headers. Android removes initial and trailing spaces, but they can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes initial and trailing spaces, but they can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">\u0020:\u0020</string>
     <string name="request_headers">En-tête de la requête</string>
     <string name="response_message">Message de la réponse</string>
index 1b5de9dbdf3407653deee91a257c70d947e580dc..3abad243314f275159d6631cc3700a683b77cc2f 100644 (file)
     <string name="error_saving_file">Error di salvataggio di %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Errore sconosciuto</string>
 
-    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">: \u0020</string>
     <string name="ssl_information">Informazioni SSL</string>
     <string name="applied_cipher">Cifratura Applicata</string>
index 6c802033c8a7e2a1ef4430916ebe673da33ae75e..01deebddd736907b2e54a961b460e94e701f3041 100644 (file)
     <string name="error_saving_file">Erro ao salvar %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Erro desconhecido</string>
 
-    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">: \u0020</string>
     <string name="request_headers">Solicitar cabeçalhos</string>
     <string name="response_message">Mensagem de Resposta</string>
index 0a8ea2b461a15dd039b7a4dcbbaad147a165d660..293953e759510c985253a046c67e160810ba703b 100644 (file)
     <string name="error_saving_file">Ошибка сохранения %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Неизвестная ошибка</string>
 
-    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">: \u0020</string>
     <string name="request_headers">Заголовки запроса</string>
     <string name="response_message">Ответное сообщение</string>
index 63cf8548651082d61a452be0bc106e72437b4d8a..1b180023ae60eaee6fb00d2ac233c8867f302d02 100644 (file)
     <string name="file_name">Dosya adı</string>
     <string name="unknown_size">Bilinmeyen boyut</string>
 
-    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">: \u0020</string>
     <string name="request_headers">İstek Başlıkları</string>
     <string name="response_message">Yanıt Mesajı</string>
index 37f2e2bfef4af9a03a6daf05b126318102c60be4..82c58ee3879972fae6ee5900649de8a264114f67 100644 (file)
     <string name="error_saving_file">保存失败 %1$s:\u0020 %2$s</string>
     <string name="unknown_error">未知错误</string>
 
-    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">: \u0020</string>
     <string name="request_headers">请求头</string>
     <string name="response_message">响应</string>
index 96aa8d549580b409d37ba5d5bb4ef1f79af92b0d..a2310b82de93c0eb804701b6ebd51c0cfa534232 100644 (file)
     <string name="error_saving_file">Error saving %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Unknown error</string>
 
-    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+        The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="colon">: \u0020</string>
     <string name="ssl_information">SSL Information</string>
     <string name="applied_cipher">Applied Cipher</string>
     <string name="about_view_headers_message">Because Android’s WebView does not expose the source information,
         a separate request was made using system tools to gather the information displayed in this activity.
         There may be some differences between this data and that used by the WebView in the main activity. This limitation will be removed in the 4.x series with the release of Privacy WebView.</string>
+    <string name="headers_copied">Headers copied.</string>
+    <string name="headers_txt">%1$s headers.txt</string>
 
     <!-- Create Home Screen Shortcut Alert Dialog. -->
     <string name="create_shortcut">Create Shortcut</string>