diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aea1919..998a495 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ android:installLocation="auto"> + @@ -27,6 +28,12 @@ + + + + + + val databaseFolder = Constants.getDatabaseFolder(requireContext()) if (createNewIdentity(databaseFolder, identityName, password)) { - (binder as LoginActivity.ActivityLauncher).launch(identityName) + (binder as LoginActivity.ActivityLauncher).launch() success = true } } diff --git a/app/src/main/java/sushi/hardcore/aira/LoginActivity.kt b/app/src/main/java/sushi/hardcore/aira/LoginActivity.kt index 33b8809..bc5036b 100644 --- a/app/src/main/java/sushi/hardcore/aira/LoginActivity.kt +++ b/app/src/main/java/sushi/hardcore/aira/LoginActivity.kt @@ -9,21 +9,18 @@ import sushi.hardcore.aira.background_service.AIRAService import java.io.File class LoginActivity : AppCompatActivity() { - private external fun getIdentityName(databaseFolder: String): String? - companion object { const val NAME_ARG = "identityName" const val BINDER_ARG = "binder" - private external fun initLogging() - init { - System.loadLibrary("aira") - initLogging() - } + } + + init { + AIRADatabase.init() } inner class ActivityLauncher: Binder() { - fun launch(identityName: String) { - startMainActivity(identityName) + fun launch() { + startMainActivity() } } @@ -38,13 +35,13 @@ class LoginActivity : AppCompatActivity() { } } val isProtected = AIRADatabase.isIdentityProtected(databaseFolder) - val name = getIdentityName(databaseFolder) + val name = AIRADatabase.getIdentityName(databaseFolder) if (AIRAService.isServiceRunning) { - startMainActivity(null) + startMainActivity() } else if (name != null && !isProtected) { if (AIRADatabase.loadIdentity(databaseFolder, null)) { AIRADatabase.clearCache() - startMainActivity(name) + startMainActivity() } else { Toast.makeText(this, R.string.identity_load_failed, Toast.LENGTH_SHORT).show() } @@ -62,11 +59,10 @@ class LoginActivity : AppCompatActivity() { } } - private fun startMainActivity(identityName: String?) { + private fun startMainActivity() { val mainActivityIntent = Intent(this, MainActivity::class.java) mainActivityIntent.action = intent.action mainActivityIntent.putExtras(intent) - mainActivityIntent.putExtra(NAME_ARG, identityName) startActivity(mainActivityIntent) finish() } diff --git a/app/src/main/java/sushi/hardcore/aira/LoginFragment.kt b/app/src/main/java/sushi/hardcore/aira/LoginFragment.kt index a9ad56b..d39760a 100644 --- a/app/src/main/java/sushi/hardcore/aira/LoginFragment.kt +++ b/app/src/main/java/sushi/hardcore/aira/LoginFragment.kt @@ -42,7 +42,7 @@ class LoginFragment : Fragment() { binding.buttonLogin.setOnClickListener { if (AIRADatabase.loadIdentity(databaseFolder, binding.editPassword.text.toString().toByteArray())) { AIRADatabase.clearCache() - (binder as LoginActivity.ActivityLauncher).launch(name) + (binder as LoginActivity.ActivityLauncher).launch() } else { Toast.makeText(activity, R.string.identity_load_failed, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/sushi/hardcore/aira/MainActivity.kt b/app/src/main/java/sushi/hardcore/aira/MainActivity.kt index b167067..bccb3d1 100644 --- a/app/src/main/java/sushi/hardcore/aira/MainActivity.kt +++ b/app/src/main/java/sushi/hardcore/aira/MainActivity.kt @@ -95,11 +95,6 @@ class MainActivity : ServiceBoundActivity() { setContentView(binding.root) setSupportActionBar(binding.toolbar.toolbar) - val identityName = intent.getStringExtra(LoginActivity.NAME_ARG) - identityName?.let { - initToolbar(it) - } - val openedToShareFile = intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE onlineSessionAdapter = SessionAdapter(this) @@ -157,12 +152,10 @@ class MainActivity : ServiceBoundActivity() { airaService.uiCallbacks = uiCallbacks airaService.isAppInBackground = false refreshSessions() - if (AIRAService.isServiceRunning) { - airaService.identityName?.let { initToolbar(it) } - } else { - airaService.identityName = identityName + if (!AIRAService.isServiceRunning) { startService(serviceIntent) } + initToolbar(airaService.identityName) } override fun onServiceDisconnected(name: ComponentName?) {} } diff --git a/app/src/main/java/sushi/hardcore/aira/SettingsActivity.kt b/app/src/main/java/sushi/hardcore/aira/SettingsActivity.kt index a7401a4..e9307ab 100644 --- a/app/src/main/java/sushi/hardcore/aira/SettingsActivity.kt +++ b/app/src/main/java/sushi/hardcore/aira/SettingsActivity.kt @@ -27,6 +27,7 @@ import sushi.hardcore.aira.utils.StringUtils class SettingsActivity: AppCompatActivity() { class MySettingsFragment(private val activity: AppCompatActivity): PreferenceFragmentCompat() { + private lateinit var databaseFolder: String private lateinit var airaService: AIRAService private val avatarPicker = AvatarPicker(activity) { picker, avatar -> if (::airaService.isInitialized) { @@ -37,6 +38,7 @@ class SettingsActivity: AppCompatActivity() { displayAvatar(avatar) } private lateinit var identityAvatarPreference: Preference + private lateinit var startAtBootSwitch: SwitchPreferenceCompat override fun onAttach(context: Context) { super.onAttach(context) @@ -45,10 +47,13 @@ class SettingsActivity: AppCompatActivity() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences, rootKey) + databaseFolder = Constants.getDatabaseFolder(activity) findPreference("identityAvatar")?.let { identityAvatarPreference = it } + startAtBootSwitch = findPreference("startAtBoot")!! + updateStartAtBootSwitch(AIRADatabase.isIdentityProtected(databaseFolder)) val paddingPreference = findPreference("psecPadding") paddingPreference?.isPersistent = false - AIRADatabase.getIdentityAvatar(Constants.getDatabaseFolder(activity))?.let { avatar -> + AIRADatabase.getIdentityAvatar(databaseFolder)?.let { avatar -> displayAvatar(avatar) } Intent(activity, AIRAService::class.java).also { serviceIntent -> @@ -68,9 +73,9 @@ class SettingsActivity: AppCompatActivity() { avatarPicker.launch() } val dialogBinding = ChangeAvatarDialogBinding.inflate(layoutInflater) - val avatar = AIRADatabase.getIdentityAvatar(Constants.getDatabaseFolder(activity)) + val avatar = AIRADatabase.getIdentityAvatar(databaseFolder) if (avatar == null) { - dialogBinding.avatar.setTextAvatar(airaService.identityName!!) + dialogBinding.avatar.setTextAvatar(airaService.identityName) } else { dialogBinding.avatar.setImageAvatar(avatar) dialogBuilder.setNegativeButton(R.string.remove) { _, _ -> @@ -112,7 +117,7 @@ class SettingsActivity: AppCompatActivity() { findPreference("identityPassword")?.setOnPreferenceClickListener { val dialogView = layoutInflater.inflate(R.layout.dialog_password, null) val oldPasswordEditText = dialogView.findViewById(R.id.old_password) - val isIdentityProtected = AIRADatabase.isIdentityProtected(Constants.getDatabaseFolder(activity)) + val isIdentityProtected = AIRADatabase.isIdentityProtected(databaseFolder) if (!isIdentityProtected) { oldPasswordEditText.visibility = View.GONE } @@ -123,24 +128,24 @@ class SettingsActivity: AppCompatActivity() { .setTitle(R.string.change_password) .setPositiveButton(R.string.ok) { _, _ -> val newPassword = newPasswordEditText.text.toString().toByteArray() - if (newPassword.isEmpty()) { - if (isIdentityProtected) { //don't change password if identity is not protected and new password is blank - changePassword(isIdentityProtected, oldPasswordEditText, null) + val newPasswordConfirm = newPasswordConfirmEditText.text.toString().toByteArray() + if (newPassword.contentEquals(newPasswordConfirm)) { + if (newPassword.isEmpty()) { + if (isIdentityProtected) { //don't change password if identity is not protected and new password is blank + changePassword(isIdentityProtected, oldPasswordEditText, null) + } + } else { + changePassword(isIdentityProtected, oldPasswordEditText, newPassword) } } else { - val newPasswordConfirm = newPasswordConfirmEditText.text.toString().toByteArray() - if (newPassword.contentEquals(newPasswordConfirm)) { - changePassword(isIdentityProtected, oldPasswordEditText, newPassword) - } else { - AlertDialog.Builder(activity, R.style.CustomAlertDialog) - .setMessage(R.string.password_mismatch) - .setTitle(R.string.error) - .setPositiveButton(R.string.ok, null) - .show() - } - newPassword.fill(0) - newPasswordConfirm.fill(0) + AlertDialog.Builder(activity, R.style.CustomAlertDialog) + .setMessage(R.string.password_mismatch) + .setTitle(R.string.error) + .setPositiveButton(R.string.ok, null) + .show() } + newPassword.fill(0) + newPasswordConfirm.fill(0) } .setNegativeButton(R.string.cancel, null) .show() @@ -188,7 +193,13 @@ class SettingsActivity: AppCompatActivity() { } else { null } - if (!AIRADatabase.changePassword(Constants.getDatabaseFolder(activity), oldPassword, newPassword)) { + if (AIRADatabase.changePassword(databaseFolder, oldPassword, newPassword)) { + val isNowIdentityProtected = newPassword != null + updateStartAtBootSwitch(isNowIdentityProtected) + if (isIdentityProtected && !isNowIdentityProtected ) { + startAtBootSwitch.isChecked = true + } + } else { AlertDialog.Builder(activity, R.style.CustomAlertDialog) .setMessage(R.string.change_password_failed) .setTitle(R.string.error) @@ -197,6 +208,15 @@ class SettingsActivity: AppCompatActivity() { } oldPassword?.fill(0) } + + private fun updateStartAtBootSwitch(isIdentityProtected: Boolean) { + startAtBootSwitch.isEnabled = !isIdentityProtected + startAtBootSwitch.summary = getString(if (isIdentityProtected) { + R.string.start_at_boot_summary_identity_protected + } else { + R.string.start_at_boot_summary + }) + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { diff --git a/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt b/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt index 6655a65..af9293b 100644 --- a/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt +++ b/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt @@ -89,6 +89,7 @@ class AIRAService : Service() { } override fun onServiceLost(serviceInfo: NsdServiceInfo?) {} } + lateinit var identityName: String val savedMsgs = mutableMapOf>() val pendingMsgs = mutableMapOf>() val savedNames = mutableMapOf() @@ -96,7 +97,6 @@ class AIRAService : Service() { val notSeen = mutableListOf() var uiCallbacks: UiCallbacks? = null var isAppInBackground = true - var identityName: String? = null inner class AIRABinder : Binder() { fun getService(): AIRAService = this@AIRAService @@ -500,14 +500,12 @@ class AIRAService : Service() { } } MESSAGE_SEND_NAME -> { - identityName?.let { - val tellingName = Protocol.name(it) - for (session in sessions.values) { - try { - session.encryptAndSend(tellingName, usePadding) - } catch (e: SocketException) { - e.printStackTrace() - } + val tellingName = Protocol.name(identityName) + for (session in sessions.values) { + try { + session.encryptAndSend(tellingName, usePadding) + } catch (e: SocketException) { + e.printStackTrace() } } } @@ -540,6 +538,7 @@ class AIRAService : Service() { } } } + identityName = AIRADatabase.getIdentityName(Constants.getDatabaseFolder(this))!! val contactList = AIRADatabase.loadContacts() if (contactList == null) { contacts = HashMap(0) @@ -773,9 +772,7 @@ class AIRAService : Service() { } } Protocol.ASK_PROFILE_INFO -> { - identityName?.let { name -> - session.encryptAndSend(Protocol.name(name), usePadding) - } + session.encryptAndSend(Protocol.name(identityName), usePadding) AIRADatabase.getIdentityAvatar(Constants.getDatabaseFolder(this))?.let { avatar -> session.encryptAndSend(Protocol.avatar(avatar), usePadding) } diff --git a/app/src/main/java/sushi/hardcore/aira/background_service/SystemBroadcastReceiver.kt b/app/src/main/java/sushi/hardcore/aira/background_service/SystemBroadcastReceiver.kt new file mode 100644 index 0000000..0599d67 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/aira/background_service/SystemBroadcastReceiver.kt @@ -0,0 +1,36 @@ +package sushi.hardcore.aira.background_service + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.preference.PreferenceManager +import sushi.hardcore.aira.AIRADatabase +import sushi.hardcore.aira.Constants + +class SystemBroadcastReceiver: BroadcastReceiver() { + init { + AIRADatabase.init() + } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_BOOT_COMPLETED) { + if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("startAtBoot", true) && !AIRAService.isServiceRunning) { + val databaseFolder = Constants.getDatabaseFolder(context) + val isProtected = AIRADatabase.isIdentityProtected(databaseFolder) + val name = AIRADatabase.getIdentityName(databaseFolder) + if (name != null && !isProtected) { + if (AIRADatabase.loadIdentity(databaseFolder, null)) { + AIRADatabase.clearCache() + val serviceIntent = Intent(context, AIRAService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(serviceIntent) + } else { + context.startService(serviceIntent) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/native/Cargo.toml b/app/src/main/native/Cargo.toml index ed0e178..5ac18c9 100644 --- a/app/src/main/native/Cargo.toml +++ b/app/src/main/native/Cargo.toml @@ -11,21 +11,21 @@ jni = { version = "0.19", default-features = false } crate-type = ["dylib"] [dependencies] -rand = "0.8.3" -rand-7 = {package = "rand", version = "0.7.3"} -lazy_static = "1.4.0" -rusqlite = { version = "0.25.1", features = ["bundled"] } +rand = "0.8" +rand-7 = {package = "rand", version = "0.7"} +lazy_static = "1.4" +rusqlite = { version = "0.25", features = ["bundled"] } ed25519-dalek = "1" #for singing x25519-dalek = "1.1" #for shared secret -sha2 = "0.9.3" -hkdf = "0.11.0" -aes-gcm = "0.9.0" #PSEC -aes-gcm-siv = "0.10.0" #Database -hmac = "0.11.0" -hex = "0.4.3" -strum_macros = "0.20.1" #display enums +sha2 = "0.9" +hkdf = "0.11" +aes-gcm = "0.9" #PSEC +aes-gcm-siv = "0.10" #Database +hmac = "0.11" +hex = "0.4" +strum_macros = "0.21" #display enums uuid = { version = "0.8", features = ["v4"] } -scrypt = "0.7.0" -zeroize = "1.2.0" -log = "0.4.14" -android_log = "0.1.3" +scrypt = "0.7" +zeroize = "1.4" +log = "0.4" +android_log = "0.1" diff --git a/app/src/main/native/src/lib.rs b/app/src/main/native/src/lib.rs index 101151f..806fd33 100644 --- a/app/src/main/native/src/lib.rs +++ b/app/src/main/native/src/lib.rs @@ -53,7 +53,7 @@ fn slice_to_jvalue<'a>(env: JNIEnv, input: &'a [u8]) -> JValue<'a> { } #[no_mangle] -pub extern fn Java_sushi_hardcore_aira_LoginActivity_00024Companion_initLogging(_: JNIEnv, _: JClass) -> jboolean { +pub extern fn Java_sushi_hardcore_aira_AIRADatabase_initLogging(_: JNIEnv, _: JClass) -> jboolean { bool_to_jboolean(android_log::init("AIRA Native").is_ok()) } @@ -76,7 +76,7 @@ pub extern fn Java_sushi_hardcore_aira_CreateIdentityFragment_createNewIdentity( #[no_mangle] -pub extern fn Java_sushi_hardcore_aira_LoginActivity_getIdentityName(env: JNIEnv, _: JClass, database_folder: JString) -> jobject { +pub extern fn Java_sushi_hardcore_aira_AIRADatabase_getIdentityName(env: JNIEnv, _: JClass, database_folder: JString) -> jobject { *match Identity::get_identity_name(&jstring_to_string(env, database_folder)) { Ok(name) => *env.new_string(name).unwrap(), Err(e) => { diff --git a/app/src/main/res/drawable/ic_blur.xml b/app/src/main/res/drawable/ic_blur.xml index 64429b3..c8a5f4b 100644 --- a/app/src/main/res/drawable/ic_blur.xml +++ b/app/src/main/res/drawable/ic_blur.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_shuttle.xml b/app/src/main/res/drawable/ic_shuttle.xml new file mode 100644 index 0000000..ae7360f --- /dev/null +++ b/app/src/main/res/drawable/ic_shuttle.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7e511e4..2fd14b1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -104,6 +104,10 @@ Pending messages: Sending pending messages… Stop + App + Start AIRA service at boot + If disabled, you won\'t receive messages until you open the app manually. + Only available if identity is not protected by a password. Send file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index e44f790..6bb4bb2 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -34,6 +34,17 @@ + + + + + +