1 /* SPDX-License-Identifier: GPL-3.0-or-later
2 * SPDX-FileCopyrightText: 2021-2022, 2025 Soren Stoutner <soren@stoutner.com>
4 * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell/>.
6 * This program is free software: you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16 * You should have received a copy of the GNU General Public License along with
17 * this program. If not, see <https://www.gnu.org/licenses/>.
20 // The suppression of deprecation lint can be removed once the minimum API >= 31.
21 @file:Suppress("DEPRECATION")
23 package com.stoutner.privacycell.activities
25 import android.Manifest
26 import android.annotation.SuppressLint
27 import android.app.ActivityManager
28 import android.content.Context
29 import android.content.Intent
30 import android.content.pm.PackageManager
31 import android.os.Build
32 import android.os.Bundle
33 import android.telephony.PhoneStateListener // This can be replaced by `TelephonyCallback` once the minimum API >= 31.
34 import android.telephony.ServiceState
35 import android.telephony.SubscriptionManager
36 import android.telephony.TelephonyDisplayInfo
37 import android.telephony.TelephonyManager
38 import android.view.MenuItem
39 import android.view.View
40 import android.widget.ImageView
41 import android.widget.LinearLayout
42 import android.widget.TextView
44 import androidx.appcompat.app.ActionBar
45 import androidx.appcompat.app.ActionBarDrawerToggle
46 import androidx.appcompat.app.AppCompatActivity
47 import androidx.appcompat.content.res.AppCompatResources
48 import androidx.appcompat.widget.Toolbar
49 import androidx.cardview.widget.CardView
50 import androidx.core.app.ActivityCompat
51 import androidx.core.net.toUri
52 import androidx.core.view.GravityCompat
53 import androidx.drawerlayout.widget.DrawerLayout
54 import androidx.preference.PreferenceManager
56 import com.google.android.material.navigation.NavigationView
58 import com.stoutner.privacycell.R
59 import com.stoutner.privacycell.dialogs.PhonePermissionDialog
60 import com.stoutner.privacycell.dialogs.PhonePermissionDialog.StoragePermissionDialogListener
61 import com.stoutner.privacycell.dialogs.WebViewDialog
62 import com.stoutner.privacycell.helpers.ProtocolHelper
63 import com.stoutner.privacycell.services.RealtimeMonitoringService
65 class PrivacyCellActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, StoragePermissionDialogListener {
66 // Define the class variables.
67 private var voiceNetworkSecurityStatus = ProtocolHelper.UNPOPULATED
68 private var dataNetworkSecurityStatus = ProtocolHelper.UNPOPULATED
70 // Declare the class variables.
71 private lateinit var phoneStateListener: PhoneStateListener // The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
73 // Declare the class views.
74 private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
75 private lateinit var drawerLayout: DrawerLayout
76 private lateinit var overallStatusCardView: CardView
77 private lateinit var overallStatusImageView: ImageView
78 private lateinit var overallStatusTextView: TextView
81 // Define the public constants.
82 const val PHONE_PERMISSION_REQUEST_CODE = 0
83 const val NOTIFICATION_PERMISSION_REQUEST_CODE = 1
86 override fun onCreate(savedInstanceState: Bundle?) {
87 // Run the default commands.
88 super.onCreate(savedInstanceState)
90 // Get a handle for the shared preferences.
91 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
93 // Get the preferences.
94 val realtimeMonitoring = sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)
95 val consider3gAntiquated = sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)
96 val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
98 // Set the content view.
100 setContentView(R.layout.privacy_cell_bottom_appbar)
102 setContentView(R.layout.privacy_cell_top_appbar)
105 // Get handles for the views.
106 drawerLayout = findViewById(R.id.drawerlayout)
107 val toolbar = findViewById<Toolbar>(R.id.toolbar)
108 overallStatusCardView = findViewById(R.id.overall_status_cardview)
109 overallStatusImageView = findViewById(R.id.overall_status_imageview)
110 overallStatusTextView = findViewById(R.id.overall_status_textview)
111 val voiceNetworkCardView = findViewById<CardView>(R.id.voice_network_cardview)
112 val voiceNetworkTextView = findViewById<TextView>(R.id.voice_network)
113 val voiceNetworkDetailsTextView = findViewById<TextView>(R.id.voice_network_details)
114 val voiceNetworkSubscriptionInfoTextView = findViewById<TextView>(R.id.voice_network_subscription_info)
115 val dataNetworkLinearLayout = findViewById<LinearLayout>(R.id.data_network_linearlayout)
116 val dataNetworkTextView = findViewById<TextView>(R.id.data_network)
117 val dataNetworkDetailsTextView = findViewById<TextView>(R.id.data_network_details)
118 val dataNetworkSubscriptionInfoTextView = findViewById<TextView>(R.id.data_network_subscription_info)
119 val additionalNetworkInfoLinearLayout = findViewById<LinearLayout>(R.id.additional_network_info_linearlayout)
120 val additionalNetworkInfoTextView = findViewById<TextView>(R.id.additional_network_info)
121 val additionalNetworkInfoDetailsTextView = findViewById<TextView>(R.id.additional_network_info_details)
122 val navigationView = findViewById<NavigationView>(R.id.navigationview)
124 // Set the support action bar.
125 setSupportActionBar(toolbar)
127 // Get a handle for the action bar.
128 val actionBar = supportActionBar!!
130 // Set a custom view on the action bar.
131 actionBar.setCustomView(R.layout.app_bar_textview)
133 // Display the custom view.
134 actionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
136 // Define a hamburger icon at the start of the app bar. It will be populated in `onPostCreate()`.
137 actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer)
139 // Listen for touches on the navigation menu.
140 navigationView.setNavigationItemSelectedListener(this)
142 // Add a drawer listener.
143 drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
144 override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
148 override fun onDrawerOpened(drawerView: View) {
152 override fun onDrawerClosed(drawerView: View) {
153 // Reset the drawer icon when the drawer is closed. Otherwise, it is an arrow if the drawer is open when the app is restarted.
154 actionBarDrawerToggle.syncState()
157 override fun onDrawerStateChanged(newState: Int) {
162 // Instantiate the protocol helper.
163 val protocolHelper = ProtocolHelper()
165 // Get a handle for the subscription manager.
166 val subscriptionManager = getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
168 // Define the phone state listener. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
169 phoneStateListener = object : PhoneStateListener() {
170 @Deprecated("Deprecated in Java")
171 override fun onServiceStateChanged(serviceState: ServiceState) { // Update the voice network.
172 // 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).
173 val networkRegistrationInfo = serviceState.networkRegistrationInfoList[1]
175 // Get the voice network type int.
176 val voiceNetworkTypeInt = networkRegistrationInfo.accessNetworkTechnology
178 // Get the voice network security status.
179 voiceNetworkSecurityStatus = protocolHelper.checkNetwork(voiceNetworkTypeInt, consider3gAntiquated)
181 // Get the voice network type.
182 val voiceNetworkStringArray = protocolHelper.getNetworkTypeStringArray(voiceNetworkTypeInt, applicationContext)
184 // Get the default voice subscription id.
185 val voiceSubscriptionId = SubscriptionManager.getDefaultVoiceSubscriptionId()
187 // Create the voice subscription info string.
188 var voiceSubscriptionInfoString = ""
190 // Populate the voice carrier info string if the appropriate permission has been granted.
191 if (ActivityCompat.checkSelfPermission(applicationContext, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED) {
192 // Populate the voice carrier info string. The deprecated command must be used until the API >= 33.
193 voiceSubscriptionInfoString = if (Build.VERSION.SDK_INT <= 32)
194 "${subscriptionManager.getActiveSubscriptionInfo(voiceSubscriptionId).carrierName} – ${subscriptionManager.getActiveSubscriptionInfo(voiceSubscriptionId).number}"
196 "${subscriptionManager.getActiveSubscriptionInfo(voiceSubscriptionId).carrierName} – ${subscriptionManager.getPhoneNumber(voiceSubscriptionId)}"
199 // Populate the voice network text views.
200 voiceNetworkTextView.text = getString(R.string.voice_network, voiceNetworkStringArray[0])
201 voiceNetworkDetailsTextView.text = voiceNetworkStringArray[1]
202 voiceNetworkSubscriptionInfoTextView.text = voiceSubscriptionInfoString
204 // Hide the voice network details text view if it is empty, which happens with Wi-Fi calling.
205 if (voiceNetworkDetailsTextView.text.isNullOrBlank())
206 voiceNetworkDetailsTextView.visibility = View.GONE
208 voiceNetworkDetailsTextView.visibility = View.VISIBLE
210 // Set the color of the voice network.
211 when (voiceNetworkSecurityStatus) {
212 ProtocolHelper.SECURE -> voiceNetworkTextView.setTextColor(getColor(R.color.blue_text))
213 ProtocolHelper.INSECURE -> voiceNetworkTextView.setTextColor(getColor(R.color.yellow_text))
214 ProtocolHelper.ANTIQUATED -> voiceNetworkTextView.setTextColor(getColor(R.color.red_text))
217 // Set the voice network click listener.
218 voiceNetworkCardView.setOnClickListener {
219 // Instantiate the voice network dialog fragment according to the network type.
220 val voiceNetworkDialogFragment = when (voiceNetworkTypeInt) {
221 TelephonyManager.NETWORK_TYPE_UNKNOWN -> WebViewDialog().type(WebViewDialog.NETWORK_UNKNOWN)
222 TelephonyManager.NETWORK_TYPE_GPRS -> WebViewDialog().type(WebViewDialog.NETWORK_GPRS)
223 TelephonyManager.NETWORK_TYPE_EDGE -> WebViewDialog().type(WebViewDialog.NETWORK_EDGE)
224 TelephonyManager.NETWORK_TYPE_UMTS -> WebViewDialog().type(WebViewDialog.NETWORK_UMTS)
225 TelephonyManager.NETWORK_TYPE_CDMA -> WebViewDialog().type(WebViewDialog.NETWORK_CDMA)
226 TelephonyManager.NETWORK_TYPE_EVDO_0 -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_0)
227 TelephonyManager.NETWORK_TYPE_EVDO_A -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_A)
228 TelephonyManager.NETWORK_TYPE_1xRTT -> WebViewDialog().type(WebViewDialog.NETWORK_1XRTT)
229 TelephonyManager.NETWORK_TYPE_HSDPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSDPA)
230 TelephonyManager.NETWORK_TYPE_HSUPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSUPA)
231 TelephonyManager.NETWORK_TYPE_HSPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSPA)
232 TelephonyManager.NETWORK_TYPE_IDEN -> WebViewDialog().type(WebViewDialog.NETWORK_IDEN)
233 TelephonyManager.NETWORK_TYPE_EVDO_B -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_B)
234 TelephonyManager.NETWORK_TYPE_LTE -> WebViewDialog().type(WebViewDialog.NETWORK_LTE)
235 TelephonyManager.NETWORK_TYPE_EHRPD -> WebViewDialog().type(WebViewDialog.NETWORK_EHRPD)
236 TelephonyManager.NETWORK_TYPE_HSPAP -> WebViewDialog().type(WebViewDialog.NETWORK_HSPAP)
237 TelephonyManager.NETWORK_TYPE_GSM -> WebViewDialog().type(WebViewDialog.NETWORK_GSM)
238 TelephonyManager.NETWORK_TYPE_TD_SCDMA -> WebViewDialog().type(WebViewDialog.NETWORK_TD_SCDMA)
239 TelephonyManager.NETWORK_TYPE_IWLAN -> WebViewDialog().type(WebViewDialog.NETWORK_IWLAN)
240 TelephonyManager.NETWORK_TYPE_NR -> WebViewDialog().type(WebViewDialog.NETWORK_NR)
241 else -> WebViewDialog().type(WebViewDialog.NETWORK_UNKNOWN)
244 // Show the alert dialog.
245 voiceNetworkDialogFragment.show(supportFragmentManager, getString(R.string.voice_network))
248 // Populate the overall security status.
249 populateOverallSecurityStatus()
252 @Deprecated("Deprecated in Java")
253 @SuppressLint("SwitchIntDef")
254 override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) { // Update the data network.
255 // Get the network type integers. <https://source.android.com/devices/tech/connect/acts-5g-testing>
256 val dataNetworkTypeInt = telephonyDisplayInfo.networkType
257 val additionalNetworkInfoTypeInt = telephonyDisplayInfo.overrideNetworkType
259 // Get the data network security status.
260 dataNetworkSecurityStatus = protocolHelper.checkNetwork(dataNetworkTypeInt, consider3gAntiquated)
262 // Get the strings that correspond to the network information.
263 val dataNetworkStringArray = protocolHelper.getNetworkTypeStringArray(dataNetworkTypeInt, applicationContext)
264 val additionalNetworkInfoStringArray = protocolHelper.getAdditionalNetworkInfoStringArray(additionalNetworkInfoTypeInt, applicationContext)
266 // Get the active data subscription id.
267 val dataSubscriptionId = SubscriptionManager.getActiveDataSubscriptionId()
269 // Create the data carrier info string.
270 var dataSubscriptionInfoString = ""
272 // Populate the data carrier info string if the appropriate permission has been granted.
273 if (ActivityCompat.checkSelfPermission(applicationContext, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED) {
274 // Populate the data carrier info string. The deprecated command must be used until the API >= 33.
275 dataSubscriptionInfoString = if (Build.VERSION.SDK_INT <= 32)
276 "${subscriptionManager.getActiveSubscriptionInfo(dataSubscriptionId).carrierName} – ${subscriptionManager.getActiveSubscriptionInfo(dataSubscriptionId).number}"
278 "${subscriptionManager.getActiveSubscriptionInfo(dataSubscriptionId).carrierName} – ${subscriptionManager.getPhoneNumber(dataSubscriptionId)}"
281 // Populate the data network text views.
282 dataNetworkTextView.text = getString(R.string.data_network, dataNetworkStringArray[0])
283 dataNetworkDetailsTextView.text = dataNetworkStringArray[1]
284 dataNetworkSubscriptionInfoTextView.text = dataSubscriptionInfoString
285 additionalNetworkInfoTextView.text = getString(R.string.additional_network_info, additionalNetworkInfoStringArray[0])
286 additionalNetworkInfoDetailsTextView.text = additionalNetworkInfoStringArray[1]
288 // Set the color of the data network.
289 when (dataNetworkSecurityStatus) {
290 ProtocolHelper.SECURE -> dataNetworkTextView.setTextColor(getColor(R.color.blue_text))
291 ProtocolHelper.INSECURE -> dataNetworkTextView.setTextColor(getColor(R.color.yellow_text))
292 ProtocolHelper.ANTIQUATED -> dataNetworkTextView.setTextColor(getColor(R.color.red_text))
295 // Set the color of the additional network info.
296 when (protocolHelper.checkAdditionalNetworkInfo(additionalNetworkInfoTypeInt)) {
297 ProtocolHelper.SECURE -> additionalNetworkInfoTextView.setTextColor(getColor(R.color.blue_text))
298 ProtocolHelper.INSECURE -> additionalNetworkInfoTextView.setTextColor(getColor(R.color.yellow_text))
299 ProtocolHelper.ANTIQUATED -> additionalNetworkInfoTextView.setTextColor(getColor(R.color.red_text))
302 // Set the data network click listener.
303 dataNetworkLinearLayout.setOnClickListener {
304 // Instantiate the data network dialog fragment according to the network type.
305 val dataNetworkDialogFragment = when (dataNetworkTypeInt) {
306 TelephonyManager.NETWORK_TYPE_UNKNOWN -> WebViewDialog().type(WebViewDialog.NETWORK_UNKNOWN)
307 TelephonyManager.NETWORK_TYPE_GPRS -> WebViewDialog().type(WebViewDialog.NETWORK_GPRS)
308 TelephonyManager.NETWORK_TYPE_EDGE -> WebViewDialog().type(WebViewDialog.NETWORK_EDGE)
309 TelephonyManager.NETWORK_TYPE_UMTS -> WebViewDialog().type(WebViewDialog.NETWORK_UMTS)
310 TelephonyManager.NETWORK_TYPE_CDMA -> WebViewDialog().type(WebViewDialog.NETWORK_CDMA)
311 TelephonyManager.NETWORK_TYPE_EVDO_0 -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_0)
312 TelephonyManager.NETWORK_TYPE_EVDO_A -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_A)
313 TelephonyManager.NETWORK_TYPE_1xRTT -> WebViewDialog().type(WebViewDialog.NETWORK_1XRTT)
314 TelephonyManager.NETWORK_TYPE_HSDPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSDPA)
315 TelephonyManager.NETWORK_TYPE_HSUPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSUPA)
316 TelephonyManager.NETWORK_TYPE_HSPA -> WebViewDialog().type(WebViewDialog.NETWORK_HSPA)
317 TelephonyManager.NETWORK_TYPE_IDEN -> WebViewDialog().type(WebViewDialog.NETWORK_IDEN)
318 TelephonyManager.NETWORK_TYPE_EVDO_B -> WebViewDialog().type(WebViewDialog.NETWORK_EVDO_B)
319 TelephonyManager.NETWORK_TYPE_LTE -> WebViewDialog().type(WebViewDialog.NETWORK_LTE)
320 TelephonyManager.NETWORK_TYPE_EHRPD -> WebViewDialog().type(WebViewDialog.NETWORK_EHRPD)
321 TelephonyManager.NETWORK_TYPE_HSPAP -> WebViewDialog().type(WebViewDialog.NETWORK_HSPAP)
322 TelephonyManager.NETWORK_TYPE_GSM -> WebViewDialog().type(WebViewDialog.NETWORK_GSM)
323 TelephonyManager.NETWORK_TYPE_TD_SCDMA -> WebViewDialog().type(WebViewDialog.NETWORK_TD_SCDMA)
324 TelephonyManager.NETWORK_TYPE_IWLAN -> WebViewDialog().type(WebViewDialog.NETWORK_IWLAN)
325 TelephonyManager.NETWORK_TYPE_NR -> WebViewDialog().type(WebViewDialog.NETWORK_NR)
326 else -> WebViewDialog().type(WebViewDialog.NETWORK_UNKNOWN)
329 // Show the alert dialog.
330 dataNetworkDialogFragment.show(supportFragmentManager, getString(R.string.voice_network))
333 // Set the additional network info click listener.
334 additionalNetworkInfoLinearLayout.setOnClickListener {
335 // Instantiate the initial network info dialog fragment according to the network type.
336 val additionalNetworkInfoDialogFragment = when (telephonyDisplayInfo.overrideNetworkType) {
337 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NONE)
338 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_LTE_CA)
339 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_LTE_ADVANCED_PRO)
340 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NR_NSA)
341 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NR_NSA_MMWAVE) // Can be removed once the minimum API >= 31.
342 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NR_ADVANCED)
343 else -> WebViewDialog().type(WebViewDialog.OVERRIDE_NETWORK_NONE)
346 // Show the alert dialog.
347 additionalNetworkInfoDialogFragment.show(supportFragmentManager, getString(R.string.voice_network))
350 // Populate the overall security status.
351 populateOverallSecurityStatus()
355 // Start the realtime monitoring service if it is enabled.
356 if (realtimeMonitoring) {
357 // Get a handle for the activity manager.
358 val activityManager: ActivityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
360 // 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.
361 val runningServiceInfoList: List<ActivityManager.RunningServiceInfo> = activityManager.getRunningServices(1)
363 // Start the service if it is not already running.
364 if (runningServiceInfoList.isEmpty()) {
365 startService(Intent(this, RealtimeMonitoringService::class.java))
370 override fun onPostCreate(savedInstanceState: Bundle?) {
371 // Run the default commands.
372 super.onPostCreate(savedInstanceState)
374 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
375 actionBarDrawerToggle.syncState()
378 override fun onStart() {
379 // Run the default commands.
382 // 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.
383 if ((ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) &&
384 (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED)) { // The phone permissions have been granted.
385 // Register the telephony manager listener.
386 registerTelephonyManagerListener()
387 } else { // The phone permission has not been granted.
388 // Check if the user has previously denied the storage permission.
389 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_PHONE_STATE)) { // Show a dialog explaining the request first.
390 // Check to see if a phone permission dialog is already displayed. This happens if the app is restarted while the dialog is shown.
391 if (supportFragmentManager.findFragmentByTag(getString(R.string.phone_permission)) == null) { // No dialog is currently shown.
392 // Instantiate the phone permission dialog fragment.
393 val phonePermissionDialogFragment = PhonePermissionDialog()
395 // Show the phone permission alert dialog. The permission will be requested when the dialog is closed.
396 phonePermissionDialogFragment.show(supportFragmentManager, getString(R.string.phone_permission))
398 } else { // Show the permission request directly.
399 // Request the read phone state permission.
400 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS), PHONE_PERMISSION_REQUEST_CODE)
405 override fun onStop() {
406 // Run the default commands.
409 // Get a handle for the telephony manager.
410 val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
412 // Unregister the telephony manager listener. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
413 telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
416 override fun onNavigationItemSelected(menuItem: MenuItem) : Boolean {
417 // Run the commands that correspond to the selected menu item.
418 when (menuItem.itemId) {
419 R.id.settings -> { // Settings.
420 // Create an intent to load the Settings activity.
421 val settingsIntent = Intent(this, SettingsActivity::class.java)
424 startActivity(settingsIntent)
428 // Create an intent to load the Protocols activity.
429 val protocolsIntent = Intent(this, ProtocolsActivity::class.java)
432 startActivity(protocolsIntent)
435 R.id.logcat -> { // Logcat.
436 // Create an intent to load the Logcat activity.
437 val logcatIntent = Intent(this, LogcatActivity::class.java)
440 startActivity(logcatIntent)
443 R.id.permissions -> { // Permissions.
444 // Instantiate the permissions dialog fragment.
445 val permissionsDialogFragment = WebViewDialog().type(WebViewDialog.PERMISSIONS)
447 // Show the alert dialog.
448 permissionsDialogFragment.show(supportFragmentManager, getString(R.string.permissions))
451 R.id.privacy_policy -> { // Privacy Policy.
452 // Instantiate the privacy policy dialog fragment.
453 val privacyPolicyDialogFragment = WebViewDialog().type(WebViewDialog.PRIVACY_POLICY)
455 // Show the alert dialog.
456 privacyPolicyDialogFragment.show(supportFragmentManager, getString(R.string.privacy_policy))
459 R.id.changelog -> { // Changelog.
460 // Instantiate the changelog dialog fragment.
461 val changelogDialogFragment = WebViewDialog().type(WebViewDialog.CHANGELOG)
463 // Show the alert dialog.
464 changelogDialogFragment.show(supportFragmentManager, getString(R.string.changelog))
467 R.id.licenses -> { // Licenses.
468 // Instantiate the licenses dialog fragment.
469 val licensesDialogFragment = WebViewDialog().type(WebViewDialog.LICENSES)
471 // Show the alert dialog.
472 licensesDialogFragment.show(supportFragmentManager, getString(R.string.licenses))
475 R.id.contributors -> { // Contributors.
476 // Instantiate the contributors dialog fragment.
477 val contributorsDialogFragment = WebViewDialog().type(WebViewDialog.CONTRIBUTORS)
479 // Show the alert dialog.
480 contributorsDialogFragment.show(supportFragmentManager, getString(R.string.contributors))
483 R.id.news -> { // News.
484 // Create a news URL intent.
485 val newsUrlIntent = Intent(Intent.ACTION_VIEW)
487 // Add the URL to the intent.
488 newsUrlIntent.data = "https://www.stoutner.com/category/privacy-cell/".toUri()
491 startActivity(newsUrlIntent)
494 R.id.roadmap -> { // Roadmap.
495 // Create a roadmap URL intent.
496 val roadmapUrlIntent = Intent(Intent.ACTION_VIEW)
498 // Add the URL to the intent.
499 roadmapUrlIntent.data = "https://www.stoutner.com/category/privacy-cell-roadmap/".toUri()
502 startActivity(roadmapUrlIntent)
505 R.id.bug_tracker -> { // Bug tracker.
506 // Create a bug tracker URL intent.
507 val bugTrackerUrlIntent = Intent(Intent.ACTION_VIEW)
509 // Add the URL to the intent.
510 bugTrackerUrlIntent.data = "https://redmine.stoutner.com/projects/privacy-cell/issues".toUri()
513 startActivity(bugTrackerUrlIntent)
516 R.id.forum -> { // Forum.
517 // Create a forum URL intent.
518 val forumUrlIntent = Intent(Intent.ACTION_VIEW)
520 // Add the URL to the intent.
521 forumUrlIntent.data = "https://redmine.stoutner.com/projects/privacy-cell/boards".toUri()
524 startActivity(forumUrlIntent)
527 R.id.donations -> { // Donations.
528 // Create a donations URL intent.
529 val donationsUrlIntent = Intent(Intent.ACTION_VIEW)
531 // Add the URL to the intent.
532 donationsUrlIntent.data = "https://www.stoutner.com/donations/".toUri()
535 startActivity(donationsUrlIntent)
539 // Close the navigation drawer.
540 drawerLayout.closeDrawer(GravityCompat.START)
542 // Consume the click.
546 override fun onCloseStoragePermissionDialog() {
547 // Request the read phone state permission.
548 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), PHONE_PERMISSION_REQUEST_CODE)
551 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
552 // Run the default commands.
553 super.onRequestPermissionsResult(requestCode, permissions, grantResults)
555 // 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) and the result is for the phone permission.
556 if (grantResults.isNotEmpty() && (requestCode == PHONE_PERMISSION_REQUEST_CODE)) {
557 // Check to see if the read phone state permission was granted. If the dialog was canceled the grant results will be empty.
558 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The read phone state permission was granted.
559 // Populate Privacy Cell.
560 registerTelephonyManagerListener()
561 } else { // The read phone state permission was denied.
562 // Display the phone permission text on the main activity.
563 overallStatusTextView.text = getString(R.string.phone_permission_text)
568 private fun registerTelephonyManagerListener() {
569 // Get a handle for the telephony manager.
570 val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
572 // Listen to changes in the cell network state. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
573 telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
576 private fun populateOverallSecurityStatus() {
577 // Create an overall status dialog type integer.
578 val overallStatusDialogTypeInt: Int
580 // Populate the over security status.
581 if ((voiceNetworkSecurityStatus == ProtocolHelper.ANTIQUATED) || (dataNetworkSecurityStatus == ProtocolHelper.ANTIQUATED)) { // This is an antiquated network.
582 // Populate the image view.
583 overallStatusImageView.setImageDrawable(AppCompatResources.getDrawable(applicationContext, R.drawable.antiquated))
586 overallStatusTextView.text = getString(R.string.antiquated_protocols)
588 // Set the text color.
589 overallStatusTextView.setTextColor(getColor(R.color.red_text))
591 // Set the stingray dialog type integer.
592 overallStatusDialogTypeInt = WebViewDialog.ANTIQUATED_NETWORK
593 } else if ((voiceNetworkSecurityStatus == ProtocolHelper.INSECURE) || (dataNetworkSecurityStatus == ProtocolHelper.INSECURE)) { // This is an insecure network.
594 // Populate the image view.
595 overallStatusImageView.setImageDrawable(AppCompatResources.getDrawable(applicationContext, R.drawable.insecure))
598 overallStatusTextView.text = getString(R.string.insecure_protocols)
600 // Set the text color.
601 overallStatusTextView.setTextColor(getColor(R.color.yellow_text))
603 // Set the stingray dialog type integer.
604 overallStatusDialogTypeInt = WebViewDialog.STINGRAY
605 } else { // This is a secure network.
606 // Populate the image view.
607 overallStatusImageView.setImageDrawable(AppCompatResources.getDrawable(applicationContext, R.drawable.secure))
610 overallStatusTextView.text = getString(R.string.secure_protocols)
612 // Set the text color.
613 overallStatusTextView.setTextColor(getColor(R.color.blue_text))
615 // Set the stingray dialog type integer.
616 overallStatusDialogTypeInt = WebViewDialog.STINGRAY
619 // Set the overall status click listener.
620 overallStatusCardView.setOnClickListener {
621 // Instantiate the stingray dialog fragment.
622 val stingrayDialogFragment = WebViewDialog().type(overallStatusDialogTypeInt)
624 // Show the alert dialog.
625 stingrayDialogFragment.show(supportFragmentManager, getString(R.string.stingrays))