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.pm.PackageManager
25 import android.os.Bundle
26 import android.telephony.PhoneStateListener
27 import android.telephony.ServiceState
28 import android.telephony.TelephonyDisplayInfo
29 import android.telephony.TelephonyManager
30 import android.view.MenuItem
31 import android.view.View
32 import android.widget.ImageView
33 import android.widget.TextView
35 import androidx.appcompat.app.ActionBar
36 import androidx.appcompat.app.ActionBarDrawerToggle
37 import androidx.appcompat.app.AppCompatActivity
38 import androidx.appcompat.content.res.AppCompatResources
39 import androidx.appcompat.widget.Toolbar
40 import androidx.core.app.ActivityCompat
41 import androidx.core.view.GravityCompat
42 import androidx.drawerlayout.widget.DrawerLayout
44 import com.google.android.material.navigation.NavigationView
46 import com.stoutner.privacycell.R
47 import com.stoutner.privacycell.dialogs.PhonePermissionDialog
48 import com.stoutner.privacycell.dialogs.WebViewDialog
50 class PrivacyCell : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, PhonePermissionDialog.StoragePermissionDialogListener {
51 // Declare the class variables.
52 private lateinit var context: Context
53 private lateinit var telephonyManager: TelephonyManager
54 private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
57 private lateinit var drawerLayout: DrawerLayout
58 private lateinit var secureFromStingrayImageView: ImageView
59 private lateinit var secureFromStingrayTextView: TextView
60 private lateinit var voiceNetworkTextView: TextView
61 private lateinit var voiceNetworkDetailsTextView: TextView
62 private lateinit var dataNetworkTextView: TextView
63 private lateinit var dataNetworkDetailsTextView: TextView
64 private lateinit var additionalNetworkInfoTextView: TextView
65 private lateinit var additionalNetworkInfoDetailsTextView: TextView
67 override fun onCreate(savedInstanceState: Bundle?) {
68 // Run the default commands.
69 super.onCreate(savedInstanceState)
71 // Set the content view.
72 setContentView(R.layout.privacy_cell_drawerlayout)
74 // Get handles for the views.
75 drawerLayout = findViewById(R.id.drawerlayout)
76 val toolbar = findViewById<Toolbar>(R.id.toolbar)
77 secureFromStingrayImageView = findViewById(R.id.secure_from_stingray_imageview)
78 secureFromStingrayTextView = findViewById(R.id.secure_from_stingray_textview)
79 voiceNetworkTextView = findViewById(R.id.voice_network)
80 voiceNetworkDetailsTextView = findViewById(R.id.voice_network_details)
81 dataNetworkTextView = findViewById(R.id.data_network)
82 dataNetworkDetailsTextView = findViewById(R.id.data_network_details)
83 additionalNetworkInfoTextView = findViewById(R.id.additional_network_info)
84 additionalNetworkInfoDetailsTextView = findViewById(R.id.additional_network_info_details)
85 val navigationView = findViewById<NavigationView>(R.id.navigationview)
87 // Get handles for the context and the telephony manager.
89 telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
91 // Set the support action bar.
92 setSupportActionBar(toolbar)
94 // Get a handle for the action bar.
95 val actionBar = supportActionBar!!
97 // Set a custom view on the action bar.
98 actionBar.setCustomView(R.layout.app_bar_textview)
100 // Display the custom view.
101 actionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
103 // Define a hamburger icon at the start of the app bar. It will be populated in `onPostCreate()`.
104 actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer)
106 // Listen for touches on the navigation menu.
107 navigationView.setNavigationItemSelectedListener(this)
109 // Add a drawer listener.
110 drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
111 override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
115 override fun onDrawerOpened(drawerView: View) {
119 override fun onDrawerClosed(drawerView: View) {
120 // Reset the drawer icon when the drawer is closed. Otherwise, it is an arrow if the drawer is open when the app is restarted.
121 actionBarDrawerToggle.syncState()
124 override fun onDrawerStateChanged(newState: Int) {
130 override fun onPostCreate(savedInstanceState: Bundle?) {
131 // Run the default commands.
132 super.onPostCreate(savedInstanceState)
134 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
135 actionBarDrawerToggle.syncState()
138 override fun onResume() {
139 // Run the default commands.
142 // 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.
143 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
144 // Populate Privacy Cell.
145 populatePrivacyCell()
146 } else { // The phone permission has not been granted.
147 // Check if the user has previously denied the storage permission.
148 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_PHONE_STATE)) { // Show a dialog explaining the request first.
149 // Check to see if a phone permission dialog is already displayed. This happens if the app is restarted when the dialog is shown.
150 if (supportFragmentManager.findFragmentByTag(getString(R.string.phone_permission)) == null) { // No dialog is currently shown.
151 // Instantiate the phone permission dialog fragment.
152 val phonePermissionDialogFragment = PhonePermissionDialog()
154 // Show the phone permission alert dialog. The permission will be requested when the dialog is closed.
155 phonePermissionDialogFragment.show(supportFragmentManager, getString(R.string.phone_permission))
157 } else { // Show the permission request directly.
158 // Request the read phone state permission. There is only one permission request in the app, so it has a request code of 0.
159 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 0)
164 override fun onNavigationItemSelected(menuItem: MenuItem) : Boolean {
165 // Get the menu item ID.
166 val menuItemId = menuItem.itemId
168 // Run the commands that correspond to the selected menu item.
169 if (menuItemId == R.id.permissions) { // Permissions.
170 // Instantiate the permissions dialog fragment.
171 val permissionsDialogFragment = WebViewDialog().type(WebViewDialog.PERMISSIONS)
173 // Show the permissions alert dialog.
174 permissionsDialogFragment.show(supportFragmentManager, getString(R.string.permissions))
175 } else if (menuItemId == R.id.privacy_policy) { // Privacy Policy.
176 // Instantiate the privacy policy dialog fragment.
177 val privacyPolicyDialogFragment = WebViewDialog().type(WebViewDialog.PRIVACY_POLICY)
179 // Show the privacy policy alert dialog.
180 privacyPolicyDialogFragment.show(supportFragmentManager, getString(R.string.privacy_policy))
181 } else if (menuItemId == R.id.changelog) { // Changelog.
182 // Instantiate the changelog dialog fragment.
183 val changelogDialogFragment = WebViewDialog().type(WebViewDialog.CHANGELOG)
185 // Show the changelog alert dialog.
186 changelogDialogFragment.show(supportFragmentManager, getString(R.string.changelog))
189 // Close the navigation drawer.
190 drawerLayout.closeDrawer(GravityCompat.START)
192 // Consume the click.
196 override fun onCloseStoragePermissionDialog() {
197 // Request the read phone state permission. There is only one permission request in the app, so it has a request code of 0.
198 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 0)
201 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
202 // Run the default commands.
203 super.onRequestPermissionsResult(requestCode, permissions, grantResults)
205 //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).
206 if (grantResults.isNotEmpty()) {
207 // Check to see if the read phone state permission was granted. If the dialog was canceled the grant results will be empty.
208 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The read phone state permission was granted.
209 // Populate Privacy Cell.
210 populatePrivacyCell()
211 } else { // The read phone state permission was denied.
212 // Display the phone permission text on the main activity.
213 secureFromStingrayTextView.text = getString(R.string.phone_permission_text)
218 private fun populatePrivacyCell() {
219 // Listen to changes in the cell network state.
220 telephonyManager.listen(object : PhoneStateListener() {
221 override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
222 // Populate the stingray security information. <https://source.android.com/devices/tech/connect/acts-5g-testing>
223 if (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_NR) { // This is a secure 5G NR SA network.
224 // Populate the image view.
225 secureFromStingrayImageView.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.secure_5g_nr_sa))
228 secureFromStingrayTextView.text = getString(R.string.secure_from_stingray)
230 // Set the text color.
231 secureFromStingrayTextView.setTextColor(getColor(R.color.blue_text))
232 } else { // This is not a secure 5G NR SA network.
233 // Populate the image view.
234 secureFromStingrayImageView.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.not_secure))
237 secureFromStingrayTextView.text = getString(R.string.not_secure_from_stingray)
239 // Set the text color.
240 secureFromStingrayTextView.setTextColor(getColor(R.color.red_text))
242 // Get the strings that correspond to the network information.
243 val dataNetworkType = getNetworkType(telephonyDisplayInfo.networkType)
244 val additionalNetworkInfo = getAdditionalNetworkInfo(telephonyDisplayInfo.overrideNetworkType)
246 // Populate the data network text views.
247 dataNetworkTextView.text = getString(R.string.data_network, dataNetworkType[0])
248 dataNetworkDetailsTextView.text = dataNetworkType[1]
249 additionalNetworkInfoTextView.text = getString(R.string.additional_network_info, additionalNetworkInfo[0])
250 additionalNetworkInfoDetailsTextView.text = additionalNetworkInfo[1]
254 override fun onServiceStateChanged(serviceState: ServiceState) {
255 // 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).
256 val networkRegistrationInfo = serviceState.networkRegistrationInfoList[1]
258 // Get the voice network type.
259 val voiceNetworkType = getNetworkType(networkRegistrationInfo.accessNetworkTechnology)
261 // Populate the voice network text views.
262 voiceNetworkTextView.text = getString(R.string.voice_network, voiceNetworkType[0])
263 voiceNetworkDetailsTextView.text = voiceNetworkType[1]
265 }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED or PhoneStateListener.LISTEN_SERVICE_STATE)
268 private fun getNetworkType(networkType: Int) : Array<String> {
269 // Return the string that corresponds to the network type.
270 return when(networkType) {
271 TelephonyManager.NETWORK_TYPE_UNKNOWN -> arrayOf(getString(R.string.unknown), "")
272 TelephonyManager.NETWORK_TYPE_GPRS -> arrayOf(getString(R.string.gprs), getString(R.string.gprs_detal))
273 TelephonyManager.NETWORK_TYPE_EDGE -> arrayOf(getString(R.string.edge), getString(R.string.edge_detail))
274 TelephonyManager.NETWORK_TYPE_UMTS -> arrayOf(getString(R.string.umts), getString(R.string.umts_detail))
275 TelephonyManager.NETWORK_TYPE_CDMA -> arrayOf(getString(R.string.cdma), getString(R.string.cdma_detail))
276 TelephonyManager.NETWORK_TYPE_EVDO_0 -> arrayOf(getString(R.string.evdo_0), getString(R.string.evdo_0_detail))
277 TelephonyManager.NETWORK_TYPE_EVDO_A -> arrayOf(getString(R.string.evdo_a), getString(R.string.evdo_a_detail))
278 TelephonyManager.NETWORK_TYPE_1xRTT -> arrayOf(getString(R.string.rtt), getString(R.string.rtt_detail))
279 TelephonyManager.NETWORK_TYPE_HSDPA -> arrayOf(getString(R.string.hsdpa), getString(R.string.hsdpa_detail))
280 TelephonyManager.NETWORK_TYPE_HSUPA -> arrayOf(getString(R.string.hsupa), getString(R.string.hsupa_detail))
281 TelephonyManager.NETWORK_TYPE_HSPA -> arrayOf(getString(R.string.hspa), getString(R.string.hspa_detail))
282 TelephonyManager.NETWORK_TYPE_IDEN -> arrayOf(getString(R.string.iden), getString(R.string.iden_detail))
283 TelephonyManager.NETWORK_TYPE_EVDO_B -> arrayOf(getString(R.string.evdo_b), getString(R.string.evdo_b_detail))
284 TelephonyManager.NETWORK_TYPE_LTE -> arrayOf(getString(R.string.lte), getString(R.string.lte_detail))
285 TelephonyManager.NETWORK_TYPE_EHRPD -> arrayOf(getString(R.string.ehrpd), getString(R.string.ehrpd_detail))
286 TelephonyManager.NETWORK_TYPE_HSPAP -> arrayOf(getString(R.string.hspap), getString(R.string.hspap_detail))
287 TelephonyManager.NETWORK_TYPE_GSM -> arrayOf(getString(R.string.gsm), getString(R.string.gsm_detail))
288 TelephonyManager.NETWORK_TYPE_TD_SCDMA -> arrayOf(getString(R.string.td_scdma), getString(R.string.td_scdma_detail))
289 TelephonyManager.NETWORK_TYPE_IWLAN -> arrayOf(getString(R.string.iwlan), getString(R.string.iwlan_detail))
290 TelephonyManager.NETWORK_TYPE_NR -> arrayOf(getString(R.string.nr), getString(R.string.nr_detail))
291 else -> arrayOf(getString(R.string.error), "")
295 private fun getAdditionalNetworkInfo(overrideNetworkType: Int) : Array<String> {
296 // Return the string that corresponds to the override network type.
297 return when(overrideNetworkType) {
298 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE -> arrayOf(getString(R.string.none), "")
299 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA -> arrayOf(getString(R.string.lte_ca), getString(R.string.lte_ca_detail))
300 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO -> arrayOf(getString(R.string.lte_advanced_pro), getString(R.string.lte_advanced_pro_detail))
301 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> arrayOf(getString(R.string.lte_nr_nsa), getString(R.string.lte_nr_nsa_detail))
302 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> arrayOf(getString(R.string.lte_nr_nsa_mmwave), getString(R.string.lte_nr_nsa_mmwave_detail))
303 else -> arrayOf(getString(R.string.error), "")