2 * Copyright © 2021 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
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.
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.
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.privacycell.activities
22 import android.Manifest
23 import android.content.Context
24 import android.content.Intent
25 import android.content.pm.PackageManager
26 import android.net.Uri
27 import android.os.Bundle
28 import android.telephony.PhoneStateListener
29 import android.telephony.ServiceState
30 import android.telephony.TelephonyDisplayInfo
31 import android.telephony.TelephonyManager
32 import android.view.MenuItem
33 import android.view.View
34 import android.widget.ImageView
35 import android.widget.TextView
37 import androidx.appcompat.app.ActionBar
38 import androidx.appcompat.app.ActionBarDrawerToggle
39 import androidx.appcompat.app.AppCompatActivity
40 import androidx.appcompat.content.res.AppCompatResources
41 import androidx.appcompat.widget.Toolbar
42 import androidx.core.app.ActivityCompat
43 import androidx.core.view.GravityCompat
44 import androidx.drawerlayout.widget.DrawerLayout
46 import com.google.android.material.navigation.NavigationView
48 import com.stoutner.privacycell.R
49 import com.stoutner.privacycell.dialogs.PhonePermissionDialog
50 import com.stoutner.privacycell.dialogs.WebViewDialog
52 class PrivacyCell : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, PhonePermissionDialog.StoragePermissionDialogListener {
53 // Declare the class variables.
54 private lateinit var context: Context
55 private lateinit var telephonyManager: TelephonyManager
56 private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
59 private lateinit var drawerLayout: DrawerLayout
60 private lateinit var secureFromStingrayImageView: ImageView
61 private lateinit var secureFromStingrayTextView: TextView
62 private lateinit var voiceNetworkTextView: TextView
63 private lateinit var voiceNetworkDetailsTextView: TextView
64 private lateinit var dataNetworkTextView: TextView
65 private lateinit var dataNetworkDetailsTextView: TextView
66 private lateinit var additionalNetworkInfoTextView: TextView
67 private lateinit var additionalNetworkInfoDetailsTextView: TextView
69 override fun onCreate(savedInstanceState: Bundle?) {
70 // Run the default commands.
71 super.onCreate(savedInstanceState)
73 // Set the content view.
74 setContentView(R.layout.privacy_cell_drawerlayout)
76 // Get handles for the views.
77 drawerLayout = findViewById(R.id.drawerlayout)
78 val toolbar = findViewById<Toolbar>(R.id.toolbar)
79 secureFromStingrayImageView = findViewById(R.id.secure_from_stingray_imageview)
80 secureFromStingrayTextView = findViewById(R.id.secure_from_stingray_textview)
81 voiceNetworkTextView = findViewById(R.id.voice_network)
82 voiceNetworkDetailsTextView = findViewById(R.id.voice_network_details)
83 dataNetworkTextView = findViewById(R.id.data_network)
84 dataNetworkDetailsTextView = findViewById(R.id.data_network_details)
85 additionalNetworkInfoTextView = findViewById(R.id.additional_network_info)
86 additionalNetworkInfoDetailsTextView = findViewById(R.id.additional_network_info_details)
87 val navigationView = findViewById<NavigationView>(R.id.navigationview)
89 // Get handles for the context and the telephony manager.
91 telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
93 // Set the support action bar.
94 setSupportActionBar(toolbar)
96 // Get a handle for the action bar.
97 val actionBar = supportActionBar!!
99 // Set a custom view on the action bar.
100 actionBar.setCustomView(R.layout.app_bar_textview)
102 // Display the custom view.
103 actionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
105 // Define a hamburger icon at the start of the app bar. It will be populated in `onPostCreate()`.
106 actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer)
108 // Listen for touches on the navigation menu.
109 navigationView.setNavigationItemSelectedListener(this)
111 // Add a drawer listener.
112 drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
113 override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
117 override fun onDrawerOpened(drawerView: View) {
121 override fun onDrawerClosed(drawerView: View) {
122 // Reset the drawer icon when the drawer is closed. Otherwise, it is an arrow if the drawer is open when the app is restarted.
123 actionBarDrawerToggle.syncState()
126 override fun onDrawerStateChanged(newState: Int) {
132 override fun onPostCreate(savedInstanceState: Bundle?) {
133 // Run the default commands.
134 super.onPostCreate(savedInstanceState)
136 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
137 actionBarDrawerToggle.syncState()
140 override fun onResume() {
141 // Run the default commands.
144 // Check to see if the read phone state permission has been granted. These commands need to be run on every resume so that the listener gets reassigned as it is automatically paused.
145 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
146 // Populate Privacy Cell.
147 populatePrivacyCell()
148 } else { // The phone permission has not been granted.
149 // Check if the user has previously denied the storage permission.
150 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_PHONE_STATE)) { // Show a dialog explaining the request first.
151 // Check to see if a phone permission dialog is already displayed. This happens if the app is restarted when the dialog is shown.
152 if (supportFragmentManager.findFragmentByTag(getString(R.string.phone_permission)) == null) { // No dialog is currently shown.
153 // Instantiate the phone permission dialog fragment.
154 val phonePermissionDialogFragment = PhonePermissionDialog()
156 // Show the phone permission alert dialog. The permission will be requested when the dialog is closed.
157 phonePermissionDialogFragment.show(supportFragmentManager, getString(R.string.phone_permission))
159 } else { // Show the permission request directly.
160 // Request the read phone state permission. There is only one permission request in the app, so it has a request code of 0.
161 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 0)
166 override fun onNavigationItemSelected(menuItem: MenuItem) : Boolean {
167 // Run the commands that correspond to the selected menu item.
168 when (menuItem.itemId) {
169 R.id.permissions -> { // Permissions.
170 // Instantiate the permissions dialog fragment.
171 val permissionsDialogFragment = WebViewDialog().type(WebViewDialog.PERMISSIONS)
173 // Show the alert dialog.
174 permissionsDialogFragment.show(supportFragmentManager, getString(R.string.permissions))
177 R.id.privacy_policy -> { // Privacy Policy.
178 // Instantiate the privacy policy dialog fragment.
179 val privacyPolicyDialogFragment = WebViewDialog().type(WebViewDialog.PRIVACY_POLICY)
181 // Show the alert dialog.
182 privacyPolicyDialogFragment.show(supportFragmentManager, getString(R.string.privacy_policy))
185 R.id.changelog -> { // Changelog.
186 // Instantiate the changelog dialog fragment.
187 val changelogDialogFragment = WebViewDialog().type(WebViewDialog.CHANGELOG)
189 // Show the alert dialog.
190 changelogDialogFragment.show(supportFragmentManager, getString(R.string.changelog))
193 R.id.licenses -> { // Licenses.
194 // Instantiate the licenses dialog fragment.
195 val licensesDialogFragment = WebViewDialog().type(WebViewDialog.LICENSES)
197 // Show the alert dialog.
198 licensesDialogFragment.show(supportFragmentManager, getString(R.string.licenses))
201 R.id.contributors -> { // Contributors.
202 // Instantiate the contributors dialog fragment.
203 val contributorsDialogFragment = WebViewDialog().type(WebViewDialog.CONTRIBUTORS)
205 // Show the alert dialog.
206 contributorsDialogFragment.show(supportFragmentManager, getString(R.string.contributors))
209 R.id.news -> { // News.
210 // Create a news URL intent.
211 val newsUrlIntent = Intent(Intent.ACTION_VIEW)
213 // Add the URL to the intent.
214 newsUrlIntent.data = Uri.parse("https://www.stoutner.com/category/privacy-cell/")
217 startActivity(newsUrlIntent)
220 R.id.roadmap -> { // Roadmap.
221 // Create a roadmap URL intent.
222 val roadmapUrlIntent = Intent(Intent.ACTION_VIEW)
224 // Add the URL to the intent.
225 roadmapUrlIntent.data = Uri.parse("https://www.stoutner.com/category/privacy-cell-roadmap/")
228 startActivity(roadmapUrlIntent)
231 R.id.bug_tracker -> { // Bug tracker.
232 // Create a bug tracker URL intent.
233 val bugTrackerUrlIntent = Intent(Intent.ACTION_VIEW)
235 // Add the URL to the intent.
236 bugTrackerUrlIntent.data = Uri.parse("https://redmine.stoutner.com/projects/privacy-cell/issues")
239 startActivity(bugTrackerUrlIntent)
242 R.id.forum -> { // Forum.
243 // Create a forum URL intent.
244 val forumUrlIntent = Intent(Intent.ACTION_VIEW)
246 // Add the URL to the intent.
247 forumUrlIntent.data = Uri.parse("https://redmine.stoutner.com/projects/privacy-cell/boards")
250 startActivity(forumUrlIntent)
253 R.id.donations -> { // Donations.
254 // Create a donations URL intent.
255 val donationsUrlIntent = Intent(Intent.ACTION_VIEW)
257 // Add the URL to the intent.
258 donationsUrlIntent.data = Uri.parse("https://www.stoutner.com/donations/")
261 startActivity(donationsUrlIntent)
265 // Close the navigation drawer.
266 drawerLayout.closeDrawer(GravityCompat.START)
268 // Consume the click.
272 override fun onCloseStoragePermissionDialog() {
273 // Request the read phone state permission. There is only one permission request in the app, so it has a request code of 0.
274 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 0)
277 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
278 // Run the default commands.
279 super.onRequestPermissionsResult(requestCode, permissions, grantResults)
281 //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).
282 if (grantResults.isNotEmpty()) {
283 // Check to see if the read phone state permission was granted. If the dialog was canceled the grant results will be empty.
284 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The read phone state permission was granted.
285 // Populate Privacy Cell.
286 populatePrivacyCell()
287 } else { // The read phone state permission was denied.
288 // Display the phone permission text on the main activity.
289 secureFromStingrayTextView.text = getString(R.string.phone_permission_text)
294 private fun populatePrivacyCell() {
295 // Listen to changes in the cell network state.
296 telephonyManager.listen(object : PhoneStateListener() {
297 override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
298 // Populate the stingray security information. <https://source.android.com/devices/tech/connect/acts-5g-testing>
299 if (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_NR) { // This is a secure 5G NR SA network.
300 // Populate the image view.
301 secureFromStingrayImageView.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.secure_5g_nr_sa))
304 secureFromStingrayTextView.text = getString(R.string.secure_from_stingray)
306 // Set the text color.
307 secureFromStingrayTextView.setTextColor(getColor(R.color.blue_text))
308 } else { // This is not a secure 5G NR SA network.
309 // Populate the image view.
310 secureFromStingrayImageView.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.not_secure))
313 secureFromStingrayTextView.text = getString(R.string.not_secure_from_stingray)
315 // Set the text color.
316 secureFromStingrayTextView.setTextColor(getColor(R.color.red_text))
318 // Get the strings that correspond to the network information.
319 val dataNetworkType = getNetworkType(telephonyDisplayInfo.networkType)
320 val additionalNetworkInfo = getAdditionalNetworkInfo(telephonyDisplayInfo.overrideNetworkType)
322 // Populate the data network text views.
323 dataNetworkTextView.text = getString(R.string.data_network, dataNetworkType[0])
324 dataNetworkDetailsTextView.text = dataNetworkType[1]
325 additionalNetworkInfoTextView.text = getString(R.string.additional_network_info, additionalNetworkInfo[0])
326 additionalNetworkInfoDetailsTextView.text = additionalNetworkInfo[1]
330 override fun onServiceStateChanged(serviceState: ServiceState) {
331 // 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).
332 val networkRegistrationInfo = serviceState.networkRegistrationInfoList[1]
334 // Get the voice network type.
335 val voiceNetworkType = getNetworkType(networkRegistrationInfo.accessNetworkTechnology)
337 // Populate the voice network text views.
338 voiceNetworkTextView.text = getString(R.string.voice_network, voiceNetworkType[0])
339 voiceNetworkDetailsTextView.text = voiceNetworkType[1]
341 }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED or PhoneStateListener.LISTEN_SERVICE_STATE)
344 private fun getNetworkType(networkType: Int) : Array<String> {
345 // Return the string that corresponds to the network type.
346 return when(networkType) {
347 TelephonyManager.NETWORK_TYPE_UNKNOWN -> arrayOf(getString(R.string.unknown), "")
348 TelephonyManager.NETWORK_TYPE_GPRS -> arrayOf(getString(R.string.gprs), getString(R.string.gprs_detal))
349 TelephonyManager.NETWORK_TYPE_EDGE -> arrayOf(getString(R.string.edge), getString(R.string.edge_detail))
350 TelephonyManager.NETWORK_TYPE_UMTS -> arrayOf(getString(R.string.umts), getString(R.string.umts_detail))
351 TelephonyManager.NETWORK_TYPE_CDMA -> arrayOf(getString(R.string.cdma), getString(R.string.cdma_detail))
352 TelephonyManager.NETWORK_TYPE_EVDO_0 -> arrayOf(getString(R.string.evdo_0), getString(R.string.evdo_0_detail))
353 TelephonyManager.NETWORK_TYPE_EVDO_A -> arrayOf(getString(R.string.evdo_a), getString(R.string.evdo_a_detail))
354 TelephonyManager.NETWORK_TYPE_1xRTT -> arrayOf(getString(R.string.rtt), getString(R.string.rtt_detail))
355 TelephonyManager.NETWORK_TYPE_HSDPA -> arrayOf(getString(R.string.hsdpa), getString(R.string.hsdpa_detail))
356 TelephonyManager.NETWORK_TYPE_HSUPA -> arrayOf(getString(R.string.hsupa), getString(R.string.hsupa_detail))
357 TelephonyManager.NETWORK_TYPE_HSPA -> arrayOf(getString(R.string.hspa), getString(R.string.hspa_detail))
358 TelephonyManager.NETWORK_TYPE_IDEN -> arrayOf(getString(R.string.iden), getString(R.string.iden_detail))
359 TelephonyManager.NETWORK_TYPE_EVDO_B -> arrayOf(getString(R.string.evdo_b), getString(R.string.evdo_b_detail))
360 TelephonyManager.NETWORK_TYPE_LTE -> arrayOf(getString(R.string.lte), getString(R.string.lte_detail))
361 TelephonyManager.NETWORK_TYPE_EHRPD -> arrayOf(getString(R.string.ehrpd), getString(R.string.ehrpd_detail))
362 TelephonyManager.NETWORK_TYPE_HSPAP -> arrayOf(getString(R.string.hspap), getString(R.string.hspap_detail))
363 TelephonyManager.NETWORK_TYPE_GSM -> arrayOf(getString(R.string.gsm), getString(R.string.gsm_detail))
364 TelephonyManager.NETWORK_TYPE_TD_SCDMA -> arrayOf(getString(R.string.td_scdma), getString(R.string.td_scdma_detail))
365 TelephonyManager.NETWORK_TYPE_IWLAN -> arrayOf(getString(R.string.iwlan), getString(R.string.iwlan_detail))
366 TelephonyManager.NETWORK_TYPE_NR -> arrayOf(getString(R.string.nr), getString(R.string.nr_detail))
367 else -> arrayOf(getString(R.string.error), "")
371 private fun getAdditionalNetworkInfo(overrideNetworkType: Int) : Array<String> {
372 // Return the string that corresponds to the override network type.
373 return when(overrideNetworkType) {
374 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE -> arrayOf(getString(R.string.none), "")
375 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA -> arrayOf(getString(R.string.lte_ca), getString(R.string.lte_ca_detail))
376 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO -> arrayOf(getString(R.string.lte_advanced_pro), getString(R.string.lte_advanced_pro_detail))
377 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> arrayOf(getString(R.string.lte_nr_nsa), getString(R.string.lte_nr_nsa_detail))
378 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> arrayOf(getString(R.string.lte_nr_nsa_mmwave), getString(R.string.lte_nr_nsa_mmwave_detail))
379 else -> arrayOf(getString(R.string.error), "")