Start at boot feature
This commit is contained in:
parent
fc46326deb
commit
eac227085d
@ -5,6 +5,7 @@
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove"/>
|
||||
@ -27,6 +28,12 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".background_service.SystemBroadcastReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity android:name=".ChatActivity" android:theme="@style/Theme.AIRA.NoActionBar"/>
|
||||
<activity android:name=".MainActivity" android:theme="@style/Theme.AIRA.NoActionBar"/>
|
||||
<activity
|
||||
|
@ -3,7 +3,9 @@ package sushi.hardcore.aira
|
||||
import sushi.hardcore.aira.background_service.Contact
|
||||
|
||||
object AIRADatabase {
|
||||
external fun initLogging(): Boolean
|
||||
external fun isIdentityProtected(databaseFolder: String): Boolean
|
||||
external fun getIdentityName(databaseFolder: String): String?
|
||||
external fun loadIdentity(databaseFolder: String, password: ByteArray?): Boolean
|
||||
external fun addContact(name: String, avatarUuid: String?, publicKey: ByteArray): Contact?
|
||||
external fun removeContact(uuid: String): Boolean
|
||||
@ -30,6 +32,11 @@ object AIRADatabase {
|
||||
external fun removeIdentityAvatar(databaseFolder: String): Boolean
|
||||
external fun getIdentityAvatar(databaseFolder: String): ByteArray?
|
||||
|
||||
fun init() {
|
||||
System.loadLibrary("aira")
|
||||
initLogging()
|
||||
}
|
||||
|
||||
fun loadAvatar(avatarUuid: String?): ByteArray? {
|
||||
return avatarUuid?.let {
|
||||
getAvatar(it)
|
||||
|
@ -80,7 +80,7 @@ class CreateIdentityFragment(private val activity: AppCompatActivity) : Fragment
|
||||
bundle.getBinder(LoginActivity.BINDER_ARG)?.let { binder ->
|
||||
val databaseFolder = Constants.getDatabaseFolder(requireContext())
|
||||
if (createNewIdentity(databaseFolder, identityName, password)) {
|
||||
(binder as LoginActivity.ActivityLauncher).launch(identityName)
|
||||
(binder as LoginActivity.ActivityLauncher).launch()
|
||||
success = true
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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?) {}
|
||||
}
|
||||
|
@ -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<Preference>("identityAvatar")?.let { identityAvatarPreference = it }
|
||||
startAtBootSwitch = findPreference("startAtBoot")!!
|
||||
updateStartAtBootSwitch(AIRADatabase.isIdentityProtected(databaseFolder))
|
||||
val paddingPreference = findPreference<SwitchPreferenceCompat>("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<Preference>("identityPassword")?.setOnPreferenceClickListener {
|
||||
val dialogView = layoutInflater.inflate(R.layout.dialog_password, null)
|
||||
val oldPasswordEditText = dialogView.findViewById<EditText>(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 {
|
||||
|
@ -89,6 +89,7 @@ class AIRAService : Service() {
|
||||
}
|
||||
override fun onServiceLost(serviceInfo: NsdServiceInfo?) {}
|
||||
}
|
||||
lateinit var identityName: String
|
||||
val savedMsgs = mutableMapOf<Int, MutableList<ChatItem>>()
|
||||
val pendingMsgs = mutableMapOf<Int, MutableList<ByteArray>>()
|
||||
val savedNames = mutableMapOf<Int, String>()
|
||||
@ -96,7 +97,6 @@ class AIRAService : Service() {
|
||||
val notSeen = mutableListOf<Int>()
|
||||
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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM6,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM6,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM3,9.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM6,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM21,10.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM14,7c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM14,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM3,13.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,20.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM10,7c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM10,12.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM18,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM21,13.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM14,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM14,20.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,8.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM10,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM14,12.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM14,8.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5z"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M6,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM6,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM6,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM3,9.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM6,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM21,10.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM14,7c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM14,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM3,13.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,20.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM10,7c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM10,12.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM18,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM18,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM21,13.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM14,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM14,20.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,8.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM10,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM14,12.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM14,8.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5z"/>
|
||||
</vector>
|
||||
|
30
app/src/main/res/drawable/ic_shuttle.xml
Normal file
30
app/src/main/res/drawable/ic_shuttle.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512.004"
|
||||
android:viewportHeight="512.004">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m130.239,138.268 l-44.358,3.427c-12.343,0.954 -23.336,7.423 -30.162,17.748l-51.157,77.372c-5.177,7.83 -6,17.629 -2.203,26.213 3.798,8.584 11.603,14.566 20.878,16.003l40.615,6.29c9.501,-50.42 32.245,-100.716 66.387,-147.053z"/>
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m226.682,448.151 l6.291,40.615c1.437,9.275 7.419,17.08 16.002,20.877 3.571,1.58 7.351,2.36 11.112,2.36 5.283,0 10.529,-1.539 15.102,-4.563l77.374,-51.156c10.325,-6.827 16.794,-17.821 17.746,-30.162l3.427,-44.358c-46.338,34.143 -96.633,56.887 -147.054,66.387z"/>
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m211.407,420c1.41,0 2.828,-0.116 4.243,-0.352 21.124,-3.532 41.484,-9.482 60.906,-17.27l-166.93,-166.93c-7.788,19.421 -13.738,39.781 -17.27,60.906 -1.392,8.327 1.401,16.81 7.37,22.78l93.144,93.144c4.956,4.955 11.645,7.722 18.537,7.722z"/>
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m471.178,227.003c40.849,-78.974 42.362,-162.43 40.227,-201.57 -0.731,-13.411 -11.423,-24.103 -24.835,-24.834 -6.373,-0.348 -13.926,-0.599 -22.439,-0.599 -43.766,0 -113.017,6.629 -179.131,40.826 -52.542,27.177 -121.439,87.018 -162.087,165.66 0.48,0.375 0.949,0.773 1.391,1.215l180,180c0.442,0.442 0.839,0.91 1.214,1.39 78.642,-40.649 138.483,-109.546 165.66,-162.088zM297.698,108.24c29.241,-29.241 76.822,-29.244 106.065,0 14.166,14.165 21.967,33 21.967,53.033s-7.801,38.868 -21.967,53.033c-14.619,14.619 -33.829,21.93 -53.032,21.932 -19.209,0.001 -38.41,-7.309 -53.033,-21.932 -14.166,-14.165 -21.968,-33 -21.968,-53.033s7.802,-38.868 21.968,-53.033z"/>
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m318.911,193.092c17.545,17.545 46.095,17.546 63.64,0 8.499,-8.5 13.18,-19.8 13.18,-31.82s-4.681,-23.32 -13.18,-31.819c-8.772,-8.773 -20.296,-13.159 -31.82,-13.159 -11.523,0 -23.047,4.386 -31.819,13.159 -8.499,8.499 -13.181,19.799 -13.181,31.819s4.681,23.321 13.18,31.82z"/>
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m15.305,421.938c3.839,0 7.678,-1.464 10.606,-4.394l48.973,-48.973c5.858,-5.858 5.858,-15.355 0,-21.213 -5.857,-5.858 -15.355,-5.858 -21.213,0l-48.973,48.973c-5.858,5.858 -5.858,15.355 0,21.213 2.929,2.929 6.768,4.394 10.607,4.394z"/>
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m119.765,392.239c-5.857,-5.858 -15.355,-5.858 -21.213,0l-94.155,94.155c-5.858,5.858 -5.858,15.355 0,21.213 2.929,2.929 6.768,4.393 10.607,4.393s7.678,-1.464 10.606,-4.394l94.154,-94.154c5.859,-5.858 5.859,-15.355 0.001,-21.213z"/>
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="m143.432,437.12 l-48.972,48.973c-5.858,5.858 -5.858,15.355 0,21.213 2.929,2.929 6.768,4.394 10.606,4.394s7.678,-1.464 10.606,-4.394l48.973,-48.973c5.858,-5.858 5.858,-15.355 0,-21.213 -5.857,-5.858 -15.355,-5.858 -21.213,0z"/>
|
||||
</vector>
|
@ -104,6 +104,10 @@
|
||||
<string name="pending_messages">Pending messages:</string>
|
||||
<string name="sending_pending_messages">Sending pending messages…</string>
|
||||
<string name="stop">Stop</string>
|
||||
<string name="app">App</string>
|
||||
<string name="start_at_boot">Start AIRA service at boot</string>
|
||||
<string name="start_at_boot_summary">If disabled, you won\'t receive messages until you open the app manually.</string>
|
||||
<string name="start_at_boot_summary_identity_protected">Only available if identity is not protected by a password.</string>
|
||||
|
||||
<!--accessibility strings-->
|
||||
<string name="send_file">Send file</string>
|
||||
|
@ -34,6 +34,17 @@
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/app">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="startAtBoot"
|
||||
android:defaultValue="true"
|
||||
android:title="@string/start_at_boot"
|
||||
android:summary="@string/start_at_boot_summary"
|
||||
android:icon="@drawable/ic_shuttle"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/security">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
|
Loading…
Reference in New Issue
Block a user