55d7c92e4111d2cc3a58af10497b715093ba3955
[PrivacyCell.git] / app / src / main / java / com / stoutner / privacycell / activities / PrivacyCellActivity.kt
1 /*
2  * Copyright © 2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
5  *
6  * Privacy Cell 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 Cell 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 Cell.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacycell.activities
21
22 import android.Manifest
23 import android.app.ActivityManager
24 import android.content.Context
25 import android.content.Intent
26 import android.content.pm.PackageManager
27 import android.net.Uri
28 import android.os.Bundle
29 import android.telephony.PhoneStateListener  // This can be replaced by `TelephonyCallback` once the minimum API >= 31.
30 import android.telephony.ServiceState
31 import android.telephony.TelephonyDisplayInfo
32 import android.telephony.TelephonyManager
33 import android.view.MenuItem
34 import android.view.View
35 import android.widget.ImageView
36 import android.widget.LinearLayout
37 import android.widget.TextView
38
39 import androidx.appcompat.app.ActionBar
40 import androidx.appcompat.app.ActionBarDrawerToggle
41 import androidx.appcompat.app.AppCompatActivity
42 import androidx.appcompat.content.res.AppCompatResources
43 import androidx.appcompat.widget.Toolbar
44 import androidx.core.app.ActivityCompat
45 import androidx.core.view.GravityCompat
46 import androidx.drawerlayout.widget.DrawerLayout
47 import androidx.preference.PreferenceManager
48
49 import com.google.android.material.navigation.NavigationView
50
51 import com.stoutner.privacycell.R
52 import com.stoutner.privacycell.dialogs.PhonePermissionDialog
53 import com.stoutner.privacycell.dialogs.WebViewDialog
54 import com.stoutner.privacycell.services.RealtimeMonitoringService
55
56 class PrivacyCellActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, PhonePermissionDialog.StoragePermissionDialogListener {
57     // Declare the class variables.
58     private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
59     private lateinit var phoneStateListener: PhoneStateListener  // The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
60
61     // Declare the class views.
62     private lateinit var drawerLayout: DrawerLayout
63     private lateinit var stingrayTextView: TextView
64
65     override fun onCreate(savedInstanceState: Bundle?) {
66         // Run the default commands.
67         super.onCreate(savedInstanceState)
68
69         // Get a handle for the shared preferences.
70         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
71
72         // Get the preferences.
73         val realtimeMonitoring = sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)
74         val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
75
76         // Set the content view.
77         if (bottomAppBar) {
78             setContentView(R.layout.privacy_cell_bottom_appbar)
79         } else {
80             setContentView(R.layout.privacy_cell_top_appbar)
81         }
82
83         // Get handles for the views.
84         drawerLayout = findViewById(R.id.drawerlayout)
85         val toolbar = findViewById<Toolbar>(R.id.toolbar)
86         val stingrayLinearLayout = findViewById<LinearLayout>(R.id.stingray_linearlayout)
87         val stingrayImageView = findViewById<ImageView>(R.id.stingray_imageview)
88         stingrayTextView = findViewById(R.id.stingray_textview)
89         val voiceNetworkLinearLayout = findViewById<LinearLayout>(R.id.voice_network_linearlayout)
90         val voiceNetworkTextView = findViewById<TextView>(R.id.voice_network)
91         val voiceNetworkDetailsTextView = findViewById<TextView>(R.id.voice_network_details)
92         val dataNetworkLinearLayout = findViewById<LinearLayout>(R.id.data_network_linearlayout)
93         val dataNetworkTextView = findViewById<TextView>(R.id.data_network)
94         val dataNetworkDetailsTextView = findViewById<TextView>(R.id.data_network_details)
95         val additionalNetworkInfoLinearLayout = findViewById<LinearLayout>(R.id.additional_network_info_linearlayout)
96         val additionalNetworkInfoTextView = findViewById<TextView>(R.id.additional_network_info)
97         val additionalNetworkInfoDetailsTextView = findViewById<TextView>(R.id.additional_network_info_details)
98         val navigationView = findViewById<NavigationView>(R.id.navigationview)
99
100         // Set the support action bar.
101         setSupportActionBar(toolbar)
102
103         // Get a handle for the action bar.
104         val actionBar = supportActionBar!!
105
106         // Set a custom view on the action bar.
107         actionBar.setCustomView(R.layout.app_bar_textview)
108
109         // Display the custom view.
110         actionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
111
112         // Define a hamburger icon at the start of the app bar.  It will be populated in `onPostCreate()`.
113         actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer)
114
115         // Listen for touches on the navigation menu.
116         navigationView.setNavigationItemSelectedListener(this)
117
118         // Add a drawer listener.
119         drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
120             override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
121                 // Do nothing.
122             }
123
124             override fun onDrawerOpened(drawerView: View) {
125                 // Do nothing.
126             }
127
128             override fun onDrawerClosed(drawerView: View) {
129                 // Reset the drawer icon when the drawer is closed.  Otherwise, it is an arrow if the drawer is open when the app is restarted.
130                 actionBarDrawerToggle.syncState()
131             }
132
133             override fun onDrawerStateChanged(newState: Int) {
134                 // Do nothing.
135             }
136         })
137
138         // Define the phone state listener.  The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
139         phoneStateListener = object : PhoneStateListener() {
140             override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
141                 // Populate the stingray security information.  <https://source.android.com/devices/tech/connect/acts-5g-testing>
142                 if (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_NR) {  // This is a secure 5G NR SA network.
143                     // Populate the image view.
144                     stingrayImageView.setImageDrawable(AppCompatResources.getDrawable(applicationContext, R.drawable.secure_5g_nr_sa))
145
146                     // Set the text.
147                     stingrayTextView.text = getString(R.string.secure_from_stingray)
148
149                     // Set the text color.
150                     stingrayTextView.setTextColor(getColor(R.color.blue_text))
151                 } else {  // This is not a secure 5G NR SA network.
152                     // Populate the image view.
153                     stingrayImageView.setImageDrawable(AppCompatResources.getDrawable(applicationContext, R.drawable.not_secure))
154
155                     // Set the text.
156                     stingrayTextView.text = getString(R.string.not_secure_from_stingray)
157
158                     // Set the text color.
159                     stingrayTextView.setTextColor(getColor(R.color.red_text))
160                 }
161
162                 // Get the strings that correspond to the network information.
163                 val dataNetworkType = getNetworkType(telephonyDisplayInfo.networkType)
164                 val additionalNetworkInfo = getAdditionalNetworkInfo(telephonyDisplayInfo.overrideNetworkType)
165
166                 // Populate the data network text views.
167                 dataNetworkTextView.text = getString(R.string.data_network, dataNetworkType[0])
168                 dataNetworkDetailsTextView.text = dataNetworkType[1]
169                 additionalNetworkInfoTextView.text = getString(R.string.additional_network_info, additionalNetworkInfo[0])
170                 additionalNetworkInfoDetailsTextView.text = additionalNetworkInfo[1]
171
172                 // Set the stingray click listener.
173                 stingrayLinearLayout.setOnClickListener {
174                     // Instantiate the stingray dialog fragment.
175                     val stingrayDialogFragment = WebViewDialog().type(WebViewDialog.STINGRAY)
176
177                     // Show the alert dialog.
178                     stingrayDialogFragment.show(supportFragmentManager, getString(R.string.stingrays))
179                 }
180
181                 // Set the data network click listener.
182                 dataNetworkLinearLayout.setOnClickListener {
183                     // Instantiate the data network dialog fragment according to the network type.
184                     val dataNetworkDialogFragment = when (telephonyDisplayInfo.networkType) {
185                         TelephonyManager.NETWORK_TYPE_UNKNOWN -> WebViewDialog().type(WebViewDialog.NETWORK_UNKNOWN)
186                         TelephonyManager.NETWORK_TYPE_GPRS -> WebViewDialog().type(WebViewDialog.NETWORK_GPRS)
187                         TelephonyManager.NETWORK_TYPE_EDGE -> WebViewDialog().type(WebViewDialog.NETWORK_EDGE)
188                         TelephonyManager.NETWORK_TYPE_UMTS -> WebViewDialog().type(WebViewDialog.NETWORK_UMTS)
189                         TelephonyManager.NETWORK_TYPE_CDMA -> WebViewDialog().type(WebViewDialog.NETWORK_CDMA)
190                         TelephonyManager.NETWORK_TYPE_EVDO_0 -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_0)
191                         TelephonyManager.NETWORK_TYPE_EVDO_A -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_A)
192                         TelephonyManager.NETWORK_TYPE_1xRTT -> WebViewDialog().type(WebViewDialog.NETWORK_1xRTT)
193                         TelephonyManager.NETWORK_TYPE_HSDPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSDPA)
194                         TelephonyManager.NETWORK_TYPE_HSUPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSUPA)
195                         TelephonyManager.NETWORK_TYPE_HSPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSPA)
196                         TelephonyManager.NETWORK_TYPE_IDEN -> WebViewDialog().type(WebViewDialog.NETWORK_IDEN)
197                         TelephonyManager.NETWORK_TYPE_EVDO_B -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_B)
198                         TelephonyManager.NETWORK_TYPE_LTE -> WebViewDialog().type(WebViewDialog.NETWORK_LTE)
199                         TelephonyManager.NETWORK_TYPE_EHRPD -> WebViewDialog().type(WebViewDialog.NETWORK_EHRPD)
200                         TelephonyManager.NETWORK_TYPE_HSPAP -> WebViewDialog().type(WebViewDialog.NETWORK_HSPAP)
201                         TelephonyManager.NETWORK_TYPE_GSM -> WebViewDialog().type(WebViewDialog.NETWORK_GSM)
202                         TelephonyManager.NETWORK_TYPE_TD_SCDMA -> WebViewDialog().type(WebViewDialog.NETWORK_TD_SCDMA)
203                         TelephonyManager.NETWORK_TYPE_IWLAN -> WebViewDialog().type(WebViewDialog.NETWORK_IWLAN)
204                         TelephonyManager.NETWORK_TYPE_NR -> WebViewDialog().type(WebViewDialog.NETWORK_NR)
205                         else -> WebViewDialog().type(WebViewDialog.NETWORK_UNKNOWN)
206                     }
207
208                     // Show the alert dialog.
209                     dataNetworkDialogFragment.show(supportFragmentManager, getString(R.string.voice_network))
210                 }
211
212                 // Set the additional network info click listener.
213                 additionalNetworkInfoLinearLayout.setOnClickListener {
214                     // Instantiate the initial network info dialog fragment according to the network type.
215                     val additionalNetworkInfoDialogFragment = when (telephonyDisplayInfo.overrideNetworkType) {
216                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NONE)
217                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_LTE_CA)
218                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_LTE_ADVANCED_PRO)
219                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NR_NSA)
220                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NR_NSA_MMWAVE)  // Can be removed once the minimum API >= 31.
221                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NR_ADVANCED)
222                         else -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NONE)
223                     }
224
225                     // Show the alert dialog.
226                     additionalNetworkInfoDialogFragment.show(supportFragmentManager, getString(R.string.voice_network))
227                 }
228             }
229
230             override fun onServiceStateChanged(serviceState: ServiceState) {
231                 // Get the network registration info for the voice network, which is the second of the three entries (the first appears to be Wi-Fi and the third appears to be the cell data network).
232                 val networkRegistrationInfo = serviceState.networkRegistrationInfoList[1]
233
234                 // Get the voice network type.
235                 val voiceNetworkType = getNetworkType(networkRegistrationInfo.accessNetworkTechnology)
236
237                 // Populate the voice network text views.
238                 voiceNetworkTextView.text = getString(R.string.voice_network, voiceNetworkType[0])
239                 voiceNetworkDetailsTextView.text = voiceNetworkType[1]
240
241                 // Set the voice network click listener.
242                 voiceNetworkLinearLayout.setOnClickListener {
243                     // Instantiate the voice network dialog fragment according to the network type.
244                     val voiceNetworkDialogFragment = when (networkRegistrationInfo.accessNetworkTechnology) {
245                         TelephonyManager.NETWORK_TYPE_UNKNOWN -> WebViewDialog().type(WebViewDialog.NETWORK_UNKNOWN)
246                         TelephonyManager.NETWORK_TYPE_GPRS -> WebViewDialog().type(WebViewDialog.NETWORK_GPRS)
247                         TelephonyManager.NETWORK_TYPE_EDGE -> WebViewDialog().type(WebViewDialog.NETWORK_EDGE)
248                         TelephonyManager.NETWORK_TYPE_UMTS -> WebViewDialog().type(WebViewDialog.NETWORK_UMTS)
249                         TelephonyManager.NETWORK_TYPE_CDMA -> WebViewDialog().type(WebViewDialog.NETWORK_CDMA)
250                         TelephonyManager.NETWORK_TYPE_EVDO_0 -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_0)
251                         TelephonyManager.NETWORK_TYPE_EVDO_A -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_A)
252                         TelephonyManager.NETWORK_TYPE_1xRTT -> WebViewDialog().type(WebViewDialog.NETWORK_1xRTT)
253                         TelephonyManager.NETWORK_TYPE_HSDPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSDPA)
254                         TelephonyManager.NETWORK_TYPE_HSUPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSUPA)
255                         TelephonyManager.NETWORK_TYPE_HSPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSPA)
256                         TelephonyManager.NETWORK_TYPE_IDEN -> WebViewDialog().type(WebViewDialog.NETWORK_IDEN)
257                         TelephonyManager.NETWORK_TYPE_EVDO_B -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_B)
258                         TelephonyManager.NETWORK_TYPE_LTE -> WebViewDialog().type(WebViewDialog.NETWORK_LTE)
259                         TelephonyManager.NETWORK_TYPE_EHRPD -> WebViewDialog().type(WebViewDialog.NETWORK_EHRPD)
260                         TelephonyManager.NETWORK_TYPE_HSPAP -> WebViewDialog().type(WebViewDialog.NETWORK_HSPAP)
261                         TelephonyManager.NETWORK_TYPE_GSM -> WebViewDialog().type(WebViewDialog.NETWORK_GSM)
262                         TelephonyManager.NETWORK_TYPE_TD_SCDMA -> WebViewDialog().type(WebViewDialog.NETWORK_TD_SCDMA)
263                         TelephonyManager.NETWORK_TYPE_IWLAN -> WebViewDialog().type(WebViewDialog.NETWORK_IWLAN)
264                         TelephonyManager.NETWORK_TYPE_NR -> WebViewDialog().type(WebViewDialog.NETWORK_NR)
265                         else -> WebViewDialog().type(WebViewDialog.NETWORK_UNKNOWN)
266                     }
267
268                     // Show the alert dialog.
269                     voiceNetworkDialogFragment.show(supportFragmentManager, getString(R.string.voice_network))
270                 }
271             }
272         }
273
274         // Start the realtime monitoring service if it is enabled.
275         if (realtimeMonitoring) {
276             // Get a handle for the activity manager.
277             val activityManager: ActivityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
278
279             // Get a list of the running service info.  The deprecated `getRunningServices()` now only returns services stared by Privacy Cell, but that is all we want to know anyway.
280             val runningServiceInfoList: List<ActivityManager.RunningServiceInfo> = activityManager.getRunningServices(1)
281
282             // Start the service if it is not already running.
283             if (runningServiceInfoList.isEmpty()) {
284                 startService(Intent(this, RealtimeMonitoringService::class.java))
285             }
286         }
287     }
288
289     override fun onPostCreate(savedInstanceState: Bundle?) {
290         // Run the default commands.
291         super.onPostCreate(savedInstanceState)
292
293         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
294         actionBarDrawerToggle.syncState()
295     }
296
297     override fun onStart() {
298         // Run the default commands.
299         super.onStart()
300
301         // Check to see if the read phone state permission has been granted.  These commands need to be run on every start so that the listener is reregistered.
302         if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {  // The storage permission has been granted.
303             // Register the telephony manager listener.
304             registerTelephonyManagerListener()
305         } else {  // The phone permission has not been granted.
306             // Check if the user has previously denied the storage permission.
307             if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_PHONE_STATE)) {  // Show a dialog explaining the request first.
308                 // Check to see if a phone permission dialog is already displayed.  This happens if the app is restarted when the dialog is shown.
309                 if (supportFragmentManager.findFragmentByTag(getString(R.string.phone_permission)) == null) {  // No dialog is currently shown.
310                     // Instantiate the phone permission dialog fragment.
311                     val phonePermissionDialogFragment = PhonePermissionDialog()
312
313                     // Show the phone permission alert dialog.  The permission will be requested when the dialog is closed.
314                     phonePermissionDialogFragment.show(supportFragmentManager, getString(R.string.phone_permission))
315                 }
316             } else {  // Show the permission request directly.
317                 // Request the read phone state permission.  There is only one permission request in the app, so it has a request code of 0.
318                 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 0)
319             }
320         }
321     }
322
323     override fun onStop() {
324         // Run the default commands.
325         super.onStop()
326
327         // Get a handle for the telephony manager.
328         val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
329
330         // Unregister the telephony manager listener.  The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
331         telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
332     }
333
334     override fun onNavigationItemSelected(menuItem: MenuItem) : Boolean {
335         // Run the commands that correspond to the selected menu item.
336         when (menuItem.itemId) {
337             R.id.settings -> {  // Settings.
338                 // Create an intent to load the Settings activity.
339                 val settingsIntent = Intent(this, SettingsActivity::class.java)
340
341                 // Make it so.
342                 startActivity(settingsIntent)
343             }
344
345             R.id.logcat -> {  // Logcat.
346                 // Create an intent to load the Logcat activity.
347                 val logcatIntent = Intent(this, LogcatActivity::class.java)
348
349                 // Make it so.
350                 startActivity(logcatIntent)
351             }
352
353             R.id.permissions -> {  // Permissions.
354                 // Instantiate the permissions dialog fragment.
355                 val permissionsDialogFragment = WebViewDialog().type(WebViewDialog.PERMISSIONS)
356
357                 // Show the alert dialog.
358                 permissionsDialogFragment.show(supportFragmentManager, getString(R.string.permissions))
359             }
360
361             R.id.privacy_policy -> {  // Privacy Policy.
362                 // Instantiate the privacy policy dialog fragment.
363                 val privacyPolicyDialogFragment = WebViewDialog().type(WebViewDialog.PRIVACY_POLICY)
364
365                 // Show the alert dialog.
366                 privacyPolicyDialogFragment.show(supportFragmentManager, getString(R.string.privacy_policy))
367             }
368
369             R.id.changelog -> {  // Changelog.
370                 // Instantiate the changelog dialog fragment.
371                 val changelogDialogFragment = WebViewDialog().type(WebViewDialog.CHANGELOG)
372
373                 // Show the alert dialog.
374                 changelogDialogFragment.show(supportFragmentManager, getString(R.string.changelog))
375             }
376
377             R.id.licenses -> {  // Licenses.
378                 // Instantiate the licenses dialog fragment.
379                 val licensesDialogFragment = WebViewDialog().type(WebViewDialog.LICENSES)
380
381                 // Show the alert dialog.
382                 licensesDialogFragment.show(supportFragmentManager, getString(R.string.licenses))
383             }
384
385             R.id.contributors -> {  // Contributors.
386                 // Instantiate the contributors dialog fragment.
387                 val contributorsDialogFragment = WebViewDialog().type(WebViewDialog.CONTRIBUTORS)
388
389                 // Show the alert dialog.
390                 contributorsDialogFragment.show(supportFragmentManager, getString(R.string.contributors))
391             }
392
393             R.id.news -> {  // News.
394                 // Create a news URL intent.
395                 val newsUrlIntent = Intent(Intent.ACTION_VIEW)
396
397                 // Add the URL to the intent.
398                 newsUrlIntent.data = Uri.parse("https://www.stoutner.com/category/privacy-cell/")
399
400                 // Make it so.
401                 startActivity(newsUrlIntent)
402             }
403
404             R.id.roadmap -> {  // Roadmap.
405                 // Create a roadmap URL intent.
406                 val roadmapUrlIntent = Intent(Intent.ACTION_VIEW)
407
408                 // Add the URL to the intent.
409                 roadmapUrlIntent.data = Uri.parse("https://www.stoutner.com/category/privacy-cell-roadmap/")
410
411                 // Make it so.
412                 startActivity(roadmapUrlIntent)
413             }
414
415             R.id.bug_tracker -> {  // Bug tracker.
416                 // Create a bug tracker URL intent.
417                 val bugTrackerUrlIntent = Intent(Intent.ACTION_VIEW)
418
419                 // Add the URL to the intent.
420                 bugTrackerUrlIntent.data = Uri.parse("https://redmine.stoutner.com/projects/privacy-cell/issues")
421
422                 // Make it so.
423                 startActivity(bugTrackerUrlIntent)
424             }
425
426             R.id.forum -> {  // Forum.
427                 // Create a forum URL intent.
428                 val forumUrlIntent = Intent(Intent.ACTION_VIEW)
429
430                 // Add the URL to the intent.
431                 forumUrlIntent.data = Uri.parse("https://redmine.stoutner.com/projects/privacy-cell/boards")
432
433                 // Make it so.
434                 startActivity(forumUrlIntent)
435             }
436
437             R.id.donations -> {  // Donations.
438                 // Create a donations URL intent.
439                 val donationsUrlIntent = Intent(Intent.ACTION_VIEW)
440
441                 // Add the URL to the intent.
442                 donationsUrlIntent.data = Uri.parse("https://www.stoutner.com/donations/")
443
444                 // Make it so.
445                 startActivity(donationsUrlIntent)
446             }
447         }
448
449         // Close the navigation drawer.
450         drawerLayout.closeDrawer(GravityCompat.START)
451
452         // Consume the click.
453         return true
454     }
455
456     override fun onCloseStoragePermissionDialog() {
457         // Request the read phone state permission.  There is only one permission request in the app, so it has a request code of 0.
458         ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 0)
459     }
460
461     override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
462         // Run the default commands.
463         super.onRequestPermissionsResult(requestCode, permissions, grantResults)
464
465         //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included).
466         if (grantResults.isNotEmpty()) {
467             // Check to see if the read phone state permission was granted.  If the dialog was canceled the grant results will be empty.
468             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  // The read phone state permission was granted.
469                 // Populate Privacy Cell.
470                 registerTelephonyManagerListener()
471             } else {  // The read phone state permission was denied.
472                 // Display the phone permission text on the main activity.
473                 stingrayTextView.text = getString(R.string.phone_permission_text)
474             }
475         }
476     }
477
478     private fun registerTelephonyManagerListener() {
479         // Get a handle for the telephony manager.
480         val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
481
482         // Listen to changes in the cell network state.  The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
483         telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED or PhoneStateListener.LISTEN_SERVICE_STATE)
484     }
485
486     private fun getNetworkType(networkType: Int) : Array<String> {
487         // Return the string that corresponds to the network type.
488         return when(networkType) {
489             TelephonyManager.NETWORK_TYPE_UNKNOWN -> arrayOf(getString(R.string.unknown), "")
490             TelephonyManager.NETWORK_TYPE_GPRS -> arrayOf(getString(R.string.gprs), getString(R.string.gprs_detail))
491             TelephonyManager.NETWORK_TYPE_EDGE -> arrayOf(getString(R.string.edge), getString(R.string.edge_detail))
492             TelephonyManager.NETWORK_TYPE_UMTS -> arrayOf(getString(R.string.umts), getString(R.string.umts_detail))
493             TelephonyManager.NETWORK_TYPE_CDMA -> arrayOf(getString(R.string.cdma), getString(R.string.cdma_detail))
494             TelephonyManager.NETWORK_TYPE_EVDO_0 -> arrayOf(getString(R.string.evdo_0), getString(R.string.evdo_0_detail))
495             TelephonyManager.NETWORK_TYPE_EVDO_A -> arrayOf(getString(R.string.evdo_a), getString(R.string.evdo_a_detail))
496             TelephonyManager.NETWORK_TYPE_1xRTT -> arrayOf(getString(R.string.rtt), getString(R.string.rtt_detail))
497             TelephonyManager.NETWORK_TYPE_HSDPA -> arrayOf(getString(R.string.hsdpa), getString(R.string.hsdpa_detail))
498             TelephonyManager.NETWORK_TYPE_HSUPA -> arrayOf(getString(R.string.hsupa), getString(R.string.hsupa_detail))
499             TelephonyManager.NETWORK_TYPE_HSPA -> arrayOf(getString(R.string.hspa), getString(R.string.hspa_detail))
500             TelephonyManager.NETWORK_TYPE_IDEN -> arrayOf(getString(R.string.iden), getString(R.string.iden_detail))
501             TelephonyManager.NETWORK_TYPE_EVDO_B -> arrayOf(getString(R.string.evdo_b), getString(R.string.evdo_b_detail))
502             TelephonyManager.NETWORK_TYPE_LTE -> arrayOf(getString(R.string.lte), getString(R.string.lte_detail))
503             TelephonyManager.NETWORK_TYPE_EHRPD -> arrayOf(getString(R.string.ehrpd), getString(R.string.ehrpd_detail))
504             TelephonyManager.NETWORK_TYPE_HSPAP -> arrayOf(getString(R.string.hspap), getString(R.string.hspap_detail))
505             TelephonyManager.NETWORK_TYPE_GSM -> arrayOf(getString(R.string.gsm), getString(R.string.gsm_detail))
506             TelephonyManager.NETWORK_TYPE_TD_SCDMA -> arrayOf(getString(R.string.td_scdma), getString(R.string.td_scdma_detail))
507             TelephonyManager.NETWORK_TYPE_IWLAN -> arrayOf(getString(R.string.iwlan), getString(R.string.iwlan_detail))
508             TelephonyManager.NETWORK_TYPE_NR -> arrayOf(getString(R.string.nr), getString(R.string.nr_detail))
509             else -> arrayOf(getString(R.string.error), "")
510         }
511     }
512
513     private fun getAdditionalNetworkInfo(overrideNetworkType: Int) : Array<String> {
514         // Return the string that corresponds to the override network type.
515         return when(overrideNetworkType) {
516             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE -> arrayOf(getString(R.string.none), "")
517             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA -> arrayOf(getString(R.string.lte_ca), getString(R.string.lte_ca_detail))
518             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO -> arrayOf(getString(R.string.lte_advanced_pro), getString(R.string.lte_advanced_pro_detail))
519             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> arrayOf(getString(R.string.nr_nsa), getString(R.string.nr_nsa_detail))
520             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> arrayOf(getString(R.string.nr_nsa_mmwave), getString(R.string.nr_nsa_mmwave_detail))  // Can be removed once the minimum API >= 31.
521             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED -> arrayOf(getString(R.string.nr_advanced), getString(R.string.nr_advanced_detail))
522             else -> arrayOf(getString(R.string.error), "")
523         }
524     }
525 }