diff --git a/mailbox-android/src/main/AndroidManifest.xml b/mailbox-android/src/main/AndroidManifest.xml index 30bcdd515ab75017ad760198f5ecfed04d71b2bf..84f503a7181790a82856f0ab9be5e433104d6370 100644 --- a/mailbox-android/src/main/AndroidManifest.xml +++ b/mailbox-android/src/main/AndroidManifest.xml @@ -22,8 +22,6 @@ android:supportsRtl="true" android:theme="@style/Theme.BriarMailbox"> - <service android:name=".android.MailboxService" /> - <activity android:name=".android.ui.MainActivity" android:exported="true" @@ -34,14 +32,29 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + <activity + android:name=".android.ui.settings.SettingsActivity" + android:exported="false" + android:label="@string/prefs_title" /> <activity android:name=".android.ui.WipeCompleteActivity" android:excludeFromRecents="true" android:launchMode="singleInstance" - android:process=":briar_mailbox_wipe_complete" /> + android:process=":briar_mailbox_wipe_complete" + android:theme="@style/Theme.BriarMailbox.NoActionBar" /> + <activity + android:name=".android.ui.StartupFailureActivity" + android:excludeFromRecents="true" + android:exported="false" + android:finishOnTaskLaunch="true" + android:label="@string/startup_failed_activity_title" + android:launchMode="singleInstance" + android:process=":briar_mailbox_startup_failure" + android:theme="@style/Theme.BriarMailbox.NoActionBar" /> - <receiver android:name=".core.system.AlarmReceiver" /> + <service android:name=".android.MailboxService" /> + <receiver android:name=".core.system.AlarmReceiver" /> <receiver android:name=".android.StartReceiver" android:exported="false"> @@ -50,15 +63,6 @@ <action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> </intent-filter> </receiver> - - <activity - android:name=".android.ui.StartupFailureActivity" - android:excludeFromRecents="true" - android:exported="false" - android:finishOnTaskLaunch="true" - android:label="@string/startup_failed_activity_title" - android:launchMode="singleInstance" - android:process=":briar_mailbox_startup_failure" /> </application> -</manifest> +</manifest> \ No newline at end of file diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt index fb99d10f6622c005c622ff8e6757df693e19aec4..86fc899458490504c0f4b2a99a03a3c2d0dd5f1c 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt @@ -23,12 +23,18 @@ import android.Manifest.permission.POST_NOTIFICATIONS import android.content.Intent import android.os.Build.VERSION.SDK_INT import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import androidx.activity.result.contract.ActivityResultContracts.RequestPermission import androidx.activity.viewModels +import androidx.annotation.UiThread import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.PermissionChecker.PERMISSION_GRANTED import androidx.core.content.PermissionChecker.checkSelfPermission +import androidx.core.view.MenuProvider +import androidx.lifecycle.Lifecycle.State.RESUMED import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import dagger.hilt.android.AndroidEntryPoint @@ -57,12 +63,13 @@ import org.briarproject.mailbox.android.StatusManager.Stopped import org.briarproject.mailbox.android.StatusManager.Stopping import org.briarproject.mailbox.android.StatusManager.Undecided import org.briarproject.mailbox.android.StatusManager.Wiping +import org.briarproject.mailbox.android.ui.settings.SettingsActivity import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.NOT_STARTED import org.briarproject.mailbox.core.util.LogUtils.info import org.slf4j.LoggerFactory.getLogger @AndroidEntryPoint -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(), MenuProvider { companion object { private val LOG = getLogger(MainActivity::class.java) @@ -87,6 +94,8 @@ class MainActivity : AppCompatActivity() { LOG.info("onCreate()") setContentView(R.layout.activity_main) + addMenuProvider(this, this, RESUMED) + LOG.info { "do we have a saved instance state? " + (savedInstanceState != null) } hadBeenStartedOnSave = savedInstanceState?.getBoolean(BUNDLE_LIFECYCLE_HAS_STARTED) ?: false @@ -111,6 +120,19 @@ class MainActivity : AppCompatActivity() { } } + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.main_actions, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (menuItem.itemId == R.id.action_settings) { + startActivity(Intent(this, SettingsActivity::class.java)) + return true + } + return false + } + + @UiThread private fun onAppStateChanged(state: MailboxAppState) { // Catch the situation where we come back to the activity after a remote wipe has happened // while the app was in the background and gets restored from the recent app list after @@ -122,21 +144,20 @@ class MainActivity : AppCompatActivity() { startActivity(Intent(this, WipeCompleteActivity::class.java)) return } + val currentDestId = nav.currentDestination?.id when (state) { Undecided -> supportActionBar?.hide() // hide action bar until we need it - NeedOnboarding -> { + NeedOnboarding -> if (currentDestId == R.id.initFragment) { supportActionBar?.hide() - if (nav.currentDestination?.id == R.id.initFragment) - nav.navigate(actionGlobalOnboardingContainer()) + nav.navigate(actionGlobalOnboardingContainer()) } - NeedsDozeExemption -> { + NeedsDozeExemption -> if (currentDestId != R.id.doNotKillMeFragment) { supportActionBar?.hide() - if (nav.currentDestination?.id != R.id.doNotKillMeFragment) - nav.navigate(actionGlobalDoNotKillMeFragment()) + nav.navigate(actionGlobalDoNotKillMeFragment()) } NotStarted -> { askForNotificationPermission() - supportActionBar?.hide() + supportActionBar?.show() nav.navigate(actionGlobalStartupFragment()) } // It is important to navigate here from various fragments. The normal case is @@ -144,47 +165,40 @@ class MainActivity : AppCompatActivity() { // However, when the service got killed and the app has been restored with a different // UI state such as the qr code screen or the status screen, then we also want to // navigate to the startup fragment. - is Starting -> { - supportActionBar?.hide() - if (nav.currentDestination?.id != R.id.startupFragment) - nav.navigate(actionGlobalStartupFragment()) + is Starting -> if (currentDestId != R.id.startupFragment) { + supportActionBar?.show() + nav.navigate(actionGlobalStartupFragment()) } - is StartedSettingUp -> { + is StartedSettingUp -> if (currentDestId != R.id.qrCodeFragment && + currentDestId != R.id.qrCodeLinkFragment + ) { supportActionBar?.show() - if (nav.currentDestination?.id != R.id.qrCodeFragment && - nav.currentDestination?.id != R.id.qrCodeLinkFragment - ) nav.navigate(actionGlobalQrCodeFragment()) + nav.navigate(actionGlobalQrCodeFragment()) } - StartedSetupComplete -> { - if (nav.currentDestination?.id == R.id.qrCodeFragment) { - supportActionBar?.hide() - nav.navigate(actionGlobalSetupCompleteFragment()) - } else if (nav.currentDestination?.id != R.id.statusFragment && - nav.currentDestination?.id != R.id.setupCompleteFragment - ) { - supportActionBar?.show() - nav.navigate(actionGlobalStatusFragment()) - } + StartedSetupComplete -> if (currentDestId == R.id.qrCodeFragment) { + supportActionBar?.hide() + nav.navigate(actionGlobalSetupCompleteFragment()) + } else if (currentDestId != R.id.statusFragment && + currentDestId != R.id.setupCompleteFragment + ) { + supportActionBar?.show() + nav.navigate(actionGlobalStatusFragment()) } - ErrorNoNetwork -> { + ErrorNoNetwork -> if (currentDestId != R.id.noNetworkFragment) { supportActionBar?.hide() - if (nav.currentDestination?.id != R.id.noNetworkFragment) - nav.navigate(actionGlobalNoNetworkFragment()) + nav.navigate(actionGlobalNoNetworkFragment()) } - ErrorClockSkew -> { + ErrorClockSkew -> if (currentDestId != R.id.clockSkewFragment) { supportActionBar?.hide() - if (nav.currentDestination?.id != R.id.clockSkewFragment) - nav.navigate(actionGlobalClockSkewFragment()) + nav.navigate(actionGlobalClockSkewFragment()) } - Stopping -> { + Stopping -> if (currentDestId != R.id.stoppingFragment) { supportActionBar?.hide() - if (nav.currentDestination?.id != R.id.stoppingFragment) - nav.navigate(actionGlobalStoppingFragment()) + nav.navigate(actionGlobalStoppingFragment()) } - Wiping -> { + Wiping -> if (nav.currentDestination?.id != R.id.wipingFragment) { supportActionBar?.hide() - if (nav.currentDestination?.id != R.id.wipingFragment) - nav.navigate(actionGlobalWipingFragment()) + nav.navigate(actionGlobalWipingFragment()) } Stopped -> {} // nothing to do but needs to be exhaustive for Kotlin 1.7 } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/settings/SettingsActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/settings/SettingsActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..b81c7bf49687ee665a9c909fa834d35850c252bc --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/settings/SettingsActivity.kt @@ -0,0 +1,41 @@ +package org.briarproject.mailbox.android.ui.settings + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import androidx.annotation.CallSuper +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.MenuProvider +import androidx.lifecycle.Lifecycle.State.RESUMED +import dagger.hilt.android.AndroidEntryPoint +import org.briarproject.mailbox.databinding.ActivitySettingsBinding + +@AndroidEntryPoint +class SettingsActivity : AppCompatActivity(), MenuProvider { + + private lateinit var binding: ActivitySettingsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivitySettingsBinding.inflate(layoutInflater) + setContentView(binding.root) + + // only needed for up/back navigation in action bar + addMenuProvider(this, this, RESUMED) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + } + + @CallSuper + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (menuItem.itemId == android.R.id.home) { + onBackPressedDispatcher.onBackPressed() + return true + } + return false + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/settings/SettingsFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/settings/SettingsFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..17c386bb4fe8e168c598626febb7a3607ed182d0 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/settings/SettingsFragment.kt @@ -0,0 +1,19 @@ +package org.briarproject.mailbox.android.ui.settings + +import android.os.Bundle +import androidx.fragment.app.activityViewModels +import androidx.preference.PreferenceFragmentCompat +import dagger.hilt.android.AndroidEntryPoint +import org.briarproject.mailbox.R +import org.briarproject.mailbox.android.ui.MailboxViewModel + +@AndroidEntryPoint +class SettingsFragment : PreferenceFragmentCompat() { + + private val viewModel: MailboxViewModel by activityViewModels() + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences, rootKey) + } + +} diff --git a/mailbox-android/src/main/res/layout/activity_settings.xml b/mailbox-android/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..ccd386d2e631617c3e1bb618572ef5773eb09196 --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_settings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/fragmentContainer" + android:name="org.briarproject.mailbox.android.ui.settings.SettingsFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".android.ui.settings.SettingsActivity" /> diff --git a/mailbox-android/src/main/res/menu/main_actions.xml b/mailbox-android/src/main/res/menu/main_actions.xml new file mode 100644 index 0000000000000000000000000000000000000000..ff816139fee668d8d7e7844eeff36d7194ddce34 --- /dev/null +++ b/mailbox-android/src/main/res/menu/main_actions.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/action_settings" + android:title="@string/prefs_title" + app:showAsAction="never" /> +</menu> diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml index d6cdf41f190048688dd6d2ebfa62f90e0b0854fb..db8d0c7ffafce946455050fccf7f24b9e7aa3e8f 100644 --- a/mailbox-android/src/main/res/values/strings.xml +++ b/mailbox-android/src/main/res/values/strings.xml @@ -79,4 +79,17 @@ <string name="startup_failed_lifecycle_reuse">Mailbox has already stopped.\n\nPlease close the app and try again.</string> <string name="startup_failed_clock_error">Mailbox was unable to start because your device\'s clock is wrong.\n\nPlease set your device\'s clock to the right time and try again.</string> + <!-- Preference --> + <string name="prefs_title">Settings</string> + <string name="prefs_tor_category_title">Tor circumvention settings</string> + <string name="prefs_tor_auto_title">Automatic bridge selection</string> + <string name="prefs_tor_auto_summary">Bridges get chosen based on your location</string> + <string name="prefs_tor_bridges_title">Use bridges</string> + <string name="prefs_bridges_category_title">Bridge types</string> + <string name="prefs_bridges_snowflake_title">Snowflake</string> + <string name="prefs_bridges_vanilla_title">Vanilla</string> + <string name="prefs_bridges_obfs4_title">Obfs4</string> + <string name="prefs_bridges_obfs_builtin_title">Obfs4 from Tor Browser</string> + <string name="prefs_bridges_meek_title">Meek</string> + </resources> diff --git a/mailbox-android/src/main/res/values/themes.xml b/mailbox-android/src/main/res/values/themes.xml index 892878df15c459ee8d1946233793b8365691433f..1f8f089329e8268de6d91a5b6444067bef1bd513 100644 --- a/mailbox-android/src/main/res/values/themes.xml +++ b/mailbox-android/src/main/res/values/themes.xml @@ -19,4 +19,10 @@ <style name="Theme.BriarMailbox.Dialog.Destructive" parent="Theme.BriarMailbox.Dialog"> <item name="buttonBarPositiveButtonStyle">@style/TextButtonDestructive</item> </style> + + <style name="Theme.BriarMailbox.NoActionBar" parent="Theme.BriarMailboxBase"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + </style> + </resources> diff --git a/mailbox-android/src/main/res/xml/preferences.xml b/mailbox-android/src/main/res/xml/preferences.xml new file mode 100644 index 0000000000000000000000000000000000000000..16929e09e32360020710017ffc587ea37f93a245 --- /dev/null +++ b/mailbox-android/src/main/res/xml/preferences.xml @@ -0,0 +1,65 @@ +<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"> + + <PreferenceCategory + app:iconSpaceReserved="false" + app:title="@string/prefs_tor_category_title"> + + <SwitchPreferenceCompat + app:defaultValue="true" + app:disableDependentsState="true" + app:iconSpaceReserved="false" + app:key="pref_auto" + app:summary="@string/prefs_tor_auto_summary" + app:title="@string/prefs_tor_auto_title" /> + + <SwitchPreferenceCompat + app:defaultValue="false" + app:dependency="pref_auto" + app:iconSpaceReserved="false" + app:key="pref_use_bridges" + app:title="@string/prefs_tor_bridges_title" /> + + </PreferenceCategory> + + <PreferenceCategory + app:iconSpaceReserved="false" + app:title="@string/prefs_bridges_category_title"> + + <SwitchPreferenceCompat + app:defaultValue="false" + app:dependency="pref_use_bridges" + app:iconSpaceReserved="false" + app:key="pref_bridges_snowflake" + app:title="@string/prefs_bridges_snowflake_title" /> + + <SwitchPreferenceCompat + app:defaultValue="false" + app:dependency="pref_use_bridges" + app:iconSpaceReserved="false" + app:key="pref_bridges_meek" + app:title="@string/prefs_bridges_meek_title" /> + + <SwitchPreferenceCompat + app:defaultValue="false" + app:dependency="pref_use_bridges" + app:iconSpaceReserved="false" + app:key="pref_bridges_obfs4" + app:title="@string/prefs_bridges_obfs4_title" /> + + <SwitchPreferenceCompat + app:defaultValue="false" + app:dependency="pref_use_bridges" + app:iconSpaceReserved="false" + app:key="pref_bridges_obfs_builtin" + app:title="@string/prefs_bridges_obfs_builtin_title" /> + + <SwitchPreferenceCompat + app:defaultValue="false" + app:dependency="pref_use_bridges" + app:iconSpaceReserved="false" + app:key="pref_bridges_vanilla" + app:title="@string/prefs_bridges_vanilla_title" /> + + </PreferenceCategory> + +</PreferenceScreen>