Add the URL entries to the navigation menu.
[PrivacyCell.git] / app / src / main / java / com / stoutner / privacycell / activities / PrivacyCell.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 Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacycell.activities
21
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
36
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
45
46 import com.google.android.material.navigation.NavigationView
47
48 import com.stoutner.privacycell.R
49 import com.stoutner.privacycell.dialogs.PhonePermissionDialog
50 import com.stoutner.privacycell.dialogs.WebViewDialog
51
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
57
58     // Declare the views.
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
68
69     override fun onCreate(savedInstanceState: Bundle?) {
70         // Run the default commands.
71         super.onCreate(savedInstanceState)
72
73         // Set the content view.
74         setContentView(R.layout.privacy_cell_drawerlayout)
75
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)
88
89         // Get handles for the context and the telephony manager.
90         context = this
91         telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
92
93         // Set the support action bar.
94         setSupportActionBar(toolbar)
95
96         // Get a handle for the action bar.
97         val actionBar = supportActionBar!!
98
99         // Set a custom view on the action bar.
100         actionBar.setCustomView(R.layout.app_bar_textview)
101
102         // Display the custom view.
103         actionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
104
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)
107
108         // Listen for touches on the navigation menu.
109         navigationView.setNavigationItemSelectedListener(this)
110
111         // Add a drawer listener.
112         drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
113             override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
114                 // Do nothing.
115             }
116
117             override fun onDrawerOpened(drawerView: View) {
118                 // Do nothing.
119             }
120
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()
124             }
125
126             override fun onDrawerStateChanged(newState: Int) {
127                 // Do nothing.
128             }
129         })
130     }
131
132     override fun onPostCreate(savedInstanceState: Bundle?) {
133         // Run the default commands.
134         super.onPostCreate(savedInstanceState)
135
136         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
137         actionBarDrawerToggle.syncState()
138     }
139
140     override fun onResume() {
141         // Run the default commands.
142         super.onResume()
143
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()
155
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))
158                 }
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)
162             }
163         }
164     }
165
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)
172
173                 // Show the alert dialog.
174                 permissionsDialogFragment.show(supportFragmentManager, getString(R.string.permissions))
175             }
176
177             R.id.privacy_policy -> {  // Privacy Policy.
178                 // Instantiate the privacy policy dialog fragment.
179                 val privacyPolicyDialogFragment = WebViewDialog().type(WebViewDialog.PRIVACY_POLICY)
180
181                 // Show the alert dialog.
182                 privacyPolicyDialogFragment.show(supportFragmentManager, getString(R.string.privacy_policy))
183             }
184
185             R.id.changelog -> {  // Changelog.
186                 // Instantiate the changelog dialog fragment.
187                 val changelogDialogFragment = WebViewDialog().type(WebViewDialog.CHANGELOG)
188
189                 // Show the alert dialog.
190                 changelogDialogFragment.show(supportFragmentManager, getString(R.string.changelog))
191             }
192
193             R.id.licenses -> {  // Licenses.
194                 // Instantiate the licenses dialog fragment.
195                 val licensesDialogFragment = WebViewDialog().type(WebViewDialog.LICENSES)
196
197                 // Show the alert dialog.
198                 licensesDialogFragment.show(supportFragmentManager, getString(R.string.licenses))
199             }
200
201             R.id.contributors -> {  // Contributors.
202                 // Instantiate the contributors dialog fragment.
203                 val contributorsDialogFragment = WebViewDialog().type(WebViewDialog.CONTRIBUTORS)
204
205                 // Show the alert dialog.
206                 contributorsDialogFragment.show(supportFragmentManager, getString(R.string.contributors))
207             }
208
209             R.id.news -> {  // News.
210                 // Create a news URL intent.
211                 val newsUrlIntent = Intent(Intent.ACTION_VIEW)
212
213                 // Add the URL to the intent.
214                 newsUrlIntent.data = Uri.parse("https://www.stoutner.com/category/privacy-cell/")
215
216                 // Make it so.
217                 startActivity(newsUrlIntent)
218             }
219
220             R.id.roadmap -> {  // Roadmap.
221                 // Create a roadmap URL intent.
222                 val roadmapUrlIntent = Intent(Intent.ACTION_VIEW)
223
224                 // Add the URL to the intent.
225                 roadmapUrlIntent.data = Uri.parse("https://www.stoutner.com/category/privacy-cell-roadmap/")
226
227                 // Make it so.
228                 startActivity(roadmapUrlIntent)
229             }
230
231             R.id.bug_tracker -> {  // Bug tracker.
232                 // Create a bug tracker URL intent.
233                 val bugTrackerUrlIntent = Intent(Intent.ACTION_VIEW)
234
235                 // Add the URL to the intent.
236                 bugTrackerUrlIntent.data = Uri.parse("https://redmine.stoutner.com/projects/privacy-cell/issues")
237
238                 // Make it so.
239                 startActivity(bugTrackerUrlIntent)
240             }
241
242             R.id.forum -> {  // Forum.
243                 // Create a forum URL intent.
244                 val forumUrlIntent = Intent(Intent.ACTION_VIEW)
245
246                 // Add the URL to the intent.
247                 forumUrlIntent.data = Uri.parse("https://redmine.stoutner.com/projects/privacy-cell/boards")
248
249                 // Make it so.
250                 startActivity(forumUrlIntent)
251             }
252
253             R.id.donations -> {  // Donations.
254                 // Create a donations URL intent.
255                 val donationsUrlIntent = Intent(Intent.ACTION_VIEW)
256
257                 // Add the URL to the intent.
258                 donationsUrlIntent.data = Uri.parse("https://www.stoutner.com/donations/")
259
260                 // Make it so.
261                 startActivity(donationsUrlIntent)
262             }
263         }
264
265         // Close the navigation drawer.
266         drawerLayout.closeDrawer(GravityCompat.START)
267
268         // Consume the click.
269         return true
270     }
271
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)
275     }
276
277     override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
278         // Run the default commands.
279         super.onRequestPermissionsResult(requestCode, permissions, grantResults)
280
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)
290             }
291         }
292     }
293
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))
302
303                     // Set the text.
304                     secureFromStingrayTextView.text = getString(R.string.secure_from_stingray)
305
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))
311
312                     // Set the text.
313                     secureFromStingrayTextView.text = getString(R.string.not_secure_from_stingray)
314
315                     // Set the text color.
316                     secureFromStingrayTextView.setTextColor(getColor(R.color.red_text))
317
318                     // Get the strings that correspond to the network information.
319                     val dataNetworkType = getNetworkType(telephonyDisplayInfo.networkType)
320                     val additionalNetworkInfo = getAdditionalNetworkInfo(telephonyDisplayInfo.overrideNetworkType)
321
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]
327                 }
328             }
329
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]
333
334                 // Get the voice network type.
335                 val voiceNetworkType = getNetworkType(networkRegistrationInfo.accessNetworkTechnology)
336
337                 // Populate the voice network text views.
338                 voiceNetworkTextView.text = getString(R.string.voice_network, voiceNetworkType[0])
339                 voiceNetworkDetailsTextView.text = voiceNetworkType[1]
340             }
341         }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED or PhoneStateListener.LISTEN_SERVICE_STATE)
342     }
343
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), "")
368         }
369     }
370
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), "")
380         }
381     }
382 }