Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
6878f80a54 | |||
a01f116065 | |||
c6e29c056b |
@ -21,7 +21,7 @@ android {
|
|||||||
}
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "ch.dissem.apps.${appName.toLowerCase()}"
|
applicationId "ch.dissem.apps.${appName.toLowerCase()}"
|
||||||
minSdkVersion 19
|
minSdkVersion 21
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode 23
|
versionCode 23
|
||||||
versionName "1.0-rc1"
|
versionName "1.0-rc1"
|
||||||
|
@ -175,20 +175,7 @@
|
|||||||
|
|
||||||
<!-- Receive Wi-Fi connection state changes -->
|
<!-- Receive Wi-Fi connection state changes -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".listener.WifiReceiver"
|
android:name=".service.StartServiceReceiver">
|
||||||
android:enabled="@bool/is_pre_api_21">
|
|
||||||
<intent-filter>
|
|
||||||
<!-- This is bad for battery life, but needed on older devices to check
|
|
||||||
if WiFi is available. Let's be honest, the whole app is bad for
|
|
||||||
battery life. -->
|
|
||||||
<action
|
|
||||||
android:name="android.net.conn.CONNECTIVITY_CHANGE"
|
|
||||||
tools:ignore="BatteryLife" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
<receiver
|
|
||||||
android:name=".service.StartServiceReceiver"
|
|
||||||
android:enabled="@bool/is_post_api_21">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
@ -179,6 +179,8 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
.setDelay(1000)
|
.setDelay(1000)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncAdapter.startSync(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <F> changeList(listFragment: F) where F : Fragment, F : ListHolder<*> {
|
private fun <F> changeList(listFragment: F) where F : Fragment, F : ListHolder<*> {
|
||||||
|
@ -4,7 +4,6 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.NavUtils
|
import android.support.v4.app.NavUtils
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import ch.dissem.bitmessage.entity.Conversation
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,8 +18,6 @@ package ch.dissem.apps.abit
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Build
|
|
||||||
import android.os.Build.VERSION_CODES.LOLLIPOP
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.support.v4.app.Fragment
|
import android.support.v4.app.Fragment
|
||||||
@ -191,21 +189,10 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||||
when (key) {
|
when (key) {
|
||||||
PREFERENCE_TRUSTED_NODE -> toggleSyncTrustedNode(sharedPreferences)
|
|
||||||
PREFERENCE_SERVER_POW -> toggleSyncServerPOW(sharedPreferences)
|
PREFERENCE_SERVER_POW -> toggleSyncServerPOW(sharedPreferences)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleSyncTrustedNode(sharedPreferences: SharedPreferences) {
|
|
||||||
val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null)
|
|
||||||
val ctx = context ?: throw IllegalStateException("No context available")
|
|
||||||
if (node != null) {
|
|
||||||
SyncAdapter.startSync(ctx)
|
|
||||||
} else {
|
|
||||||
SyncAdapter.stopSync(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toggleSyncServerPOW(sharedPreferences: SharedPreferences) {
|
private fun toggleSyncServerPOW(sharedPreferences: SharedPreferences) {
|
||||||
val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null)
|
val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null)
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
@ -264,7 +251,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
private fun connectivityChangeListener() =
|
private fun connectivityChangeListener() =
|
||||||
OnPreferenceChangeListener { _, _ ->
|
OnPreferenceChangeListener { _, _ ->
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
if (Build.VERSION.SDK_INT >= LOLLIPOP && Preferences.isFullNodeActive(ctx)) {
|
if (Preferences.isFullNodeActive(ctx)) {
|
||||||
NetworkUtils.scheduleNodeStart(ctx)
|
NetworkUtils.scheduleNodeStart(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.apps.abit.adapter
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.util.PRNGFixes
|
|
||||||
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
class AndroidCryptography : SpongyCryptography() {
|
|
||||||
init {
|
|
||||||
PRNGFixes.apply()
|
|
||||||
}
|
|
||||||
}
|
|
@ -149,7 +149,7 @@ class ConversationAdapter internal constructor(
|
|||||||
linksClickable = true
|
linksClickable = true
|
||||||
setTextIsSelectable(true)
|
setTextIsSelectable(true)
|
||||||
}
|
}
|
||||||
val labelAdapter = LabelAdapter(itemView.context, emptySet<Label>())
|
val labelAdapter = LabelAdapter(itemView.context, emptySet())
|
||||||
val labels = itemView.findViewById<RecyclerView>(R.id.labels)!!.apply {
|
val labels = itemView.findViewById<RecyclerView>(R.id.labels)!!.apply {
|
||||||
adapter = labelAdapter
|
adapter = labelAdapter
|
||||||
layoutManager = GridLayoutManager(itemView.context, 2)
|
layoutManager = GridLayoutManager(itemView.context, 2)
|
||||||
|
@ -2,7 +2,6 @@ package ch.dissem.apps.abit.adapter
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Build
|
|
||||||
import android.support.annotation.ColorInt
|
import android.support.annotation.ColorInt
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -15,7 +14,6 @@ import ch.dissem.apps.abit.util.getIcon
|
|||||||
import ch.dissem.apps.abit.util.getText
|
import ch.dissem.apps.abit.util.getText
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import com.mikepenz.iconics.view.IconicsImageView
|
import com.mikepenz.iconics.view.IconicsImageView
|
||||||
import org.jetbrains.anko.backgroundColor
|
|
||||||
|
|
||||||
class LabelAdapter internal constructor(private val ctx: Context, labels: Collection<Label>) :
|
class LabelAdapter internal constructor(private val ctx: Context, labels: Collection<Label>) :
|
||||||
RecyclerView.Adapter<LabelAdapter.ViewHolder>() {
|
RecyclerView.Adapter<LabelAdapter.ViewHolder>() {
|
||||||
@ -50,11 +48,7 @@ class LabelAdapter internal constructor(private val ctx: Context, labels: Collec
|
|||||||
var label = itemView.findViewById<TextView>(R.id.label)!!
|
var label = itemView.findViewById<TextView>(R.id.label)!!
|
||||||
|
|
||||||
fun setBackground(@ColorInt color: Int) {
|
fun setBackground(@ColorInt color: Int) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
itemView.backgroundTintList = ColorStateList.valueOf(color)
|
||||||
itemView.backgroundTintList = ColorStateList.valueOf(color)
|
|
||||||
} else {
|
|
||||||
itemView.backgroundColor = color
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package ch.dissem.apps.abit.dialog
|
package ch.dissem.apps.abit.dialog
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
import ch.dissem.apps.abit.util.NetworkUtils
|
||||||
@ -37,9 +36,7 @@ class FullNodeDialogActivity : Activity() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
dismiss.setOnClickListener {
|
dismiss.setOnClickListener {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
NetworkUtils.scheduleNodeStart(applicationContext)
|
||||||
NetworkUtils.scheduleNodeStart(applicationContext)
|
|
||||||
}
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.apps.abit.listener
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
|
||||||
import org.jetbrains.anko.connectivityManager
|
|
||||||
|
|
||||||
class WifiReceiver : BroadcastReceiver() {
|
|
||||||
override fun onReceive(ctx: Context, intent: Intent) {
|
|
||||||
if ("android.net.conn.CONNECTIVITY_CHANGE" == intent.action) {
|
|
||||||
val bmc = Singleton.getBitmessageContext(ctx)
|
|
||||||
if (Preferences.isFullNodeActive(ctx) && !bmc.isRunning() && !(Preferences.isWifiOnly(ctx) && ctx.connectivityManager.isActiveNetworkMetered)) {
|
|
||||||
NetworkUtils.doStartBitmessageService(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,7 +22,6 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.os.BatteryManager
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import ch.dissem.apps.abit.notification.NetworkNotification
|
import ch.dissem.apps.abit.notification.NetworkNotification
|
||||||
import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID
|
import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID
|
||||||
|
@ -20,7 +20,6 @@ import android.content.Context
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import ch.dissem.apps.abit.MainActivity
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.apps.abit.adapter.AndroidCryptography
|
|
||||||
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter
|
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter
|
||||||
import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine
|
import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine
|
||||||
import ch.dissem.apps.abit.listener.MessageListener
|
import ch.dissem.apps.abit.listener.MessageListener
|
||||||
@ -29,6 +28,7 @@ import ch.dissem.apps.abit.repository.*
|
|||||||
import ch.dissem.apps.abit.util.Constants
|
import ch.dissem.apps.abit.util.Constants
|
||||||
import ch.dissem.apps.abit.util.Observable
|
import ch.dissem.apps.abit.util.Observable
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
@ -112,7 +112,7 @@ object Singleton {
|
|||||||
ServerPowEngine(ctx),
|
ServerPowEngine(ctx),
|
||||||
ServicePowEngine(ctx)
|
ServicePowEngine(ctx)
|
||||||
)
|
)
|
||||||
cryptography = AndroidCryptography()
|
cryptography = SpongyCryptography()
|
||||||
nodeRegistry = AndroidNodeRegistry(sqlHelper)
|
nodeRegistry = AndroidNodeRegistry(sqlHelper)
|
||||||
inventory = AndroidInventory(sqlHelper)
|
inventory = AndroidInventory(sqlHelper)
|
||||||
addressRepo = AndroidAddressRepository(sqlHelper)
|
addressRepo = AndroidAddressRepository(sqlHelper)
|
||||||
|
@ -14,8 +14,8 @@ import ch.dissem.apps.abit.util.Preferences
|
|||||||
*
|
*
|
||||||
* And stops it when the preconditions for the job (unmetered network) aren't met anymore.
|
* And stops it when the preconditions for the job (unmetered network) aren't met anymore.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
class StartupNodeOnWifiService : JobService() {
|
class StartupNodeOnWifiService : JobService() {
|
||||||
|
|
||||||
override fun onStartJob(params: JobParameters?): Boolean {
|
override fun onStartJob(params: JobParameters?): Boolean {
|
||||||
val bmc = Singleton.getBitmessageContext(this)
|
val bmc = Singleton.getBitmessageContext(this)
|
||||||
if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) {
|
if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) {
|
||||||
@ -29,4 +29,5 @@ class StartupNodeOnWifiService : JobService() {
|
|||||||
* depending on Android version.
|
* depending on Android version.
|
||||||
*/
|
*/
|
||||||
override fun onStopJob(params: JobParameters?) = Preferences.isFullNodeActive(this)
|
override fun onStopJob(params: JobParameters?) = Preferences.isFullNodeActive(this)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import ch.dissem.apps.abit.service.Singleton
|
|||||||
import ch.dissem.apps.abit.synchronization.Authenticator.Companion.ACCOUNT_POW
|
import ch.dissem.apps.abit.synchronization.Authenticator.Companion.ACCOUNT_POW
|
||||||
import ch.dissem.apps.abit.synchronization.Authenticator.Companion.ACCOUNT_SYNC
|
import ch.dissem.apps.abit.synchronization.Authenticator.Companion.ACCOUNT_SYNC
|
||||||
import ch.dissem.apps.abit.synchronization.StubProvider.Companion.AUTHORITY
|
import ch.dissem.apps.abit.synchronization.StubProvider.Companion.AUTHORITY
|
||||||
|
import ch.dissem.apps.abit.util.NetworkUtils
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
import ch.dissem.apps.abit.util.Preferences
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
|
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
|
||||||
@ -43,11 +44,11 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
private val bmc = Singleton.getBitmessageContext(context)
|
private val bmc = Singleton.getBitmessageContext(context)
|
||||||
|
|
||||||
override fun onPerformSync(
|
override fun onPerformSync(
|
||||||
account: Account,
|
account: Account,
|
||||||
extras: Bundle,
|
extras: Bundle,
|
||||||
authority: String,
|
authority: String,
|
||||||
provider: ContentProviderClient,
|
provider: ContentProviderClient,
|
||||||
syncResult: SyncResult
|
syncResult: SyncResult
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
if (account == ACCOUNT_SYNC) {
|
if (account == ACCOUNT_SYNC) {
|
||||||
@ -75,16 +76,16 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
}
|
}
|
||||||
val trustedNode = Preferences.getTrustedNode(context)
|
val trustedNode = Preferences.getTrustedNode(context)
|
||||||
if (trustedNode == null) {
|
if (trustedNode == null) {
|
||||||
LOG.info("Trusted node not available, disabling synchronization")
|
// As Abit tends to get killed by the system, let's leverage the sync mechanism to start it again:
|
||||||
stopSync(context)
|
NetworkUtils.scheduleNodeStart(context)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
LOG.info("Synchronization started")
|
LOG.info("Synchronization started")
|
||||||
bmc.synchronize(
|
bmc.synchronize(
|
||||||
trustedNode,
|
trustedNode,
|
||||||
Preferences.getTrustedNodePort(context),
|
Preferences.getTrustedNodePort(context),
|
||||||
Preferences.getTimeoutInSeconds(context),
|
Preferences.getTimeoutInSeconds(context),
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
LOG.info("Synchronization finished")
|
LOG.info("Synchronization finished")
|
||||||
}
|
}
|
||||||
@ -104,7 +105,8 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
// If the Bitmessage context acts as a full node, synchronization isn't necessary
|
// If the Bitmessage context acts as a full node, synchronization isn't necessary
|
||||||
LOG.info("Looking for completed POW")
|
LOG.info("Looking for completed POW")
|
||||||
|
|
||||||
val privateKey = identity.privateKey?.privateEncryptionKey ?: throw IllegalStateException("Identity without private key")
|
val privateKey =
|
||||||
|
identity.privateKey?.privateEncryptionKey ?: throw IllegalStateException("Identity without private key")
|
||||||
val signingKey = cryptography().createPublicKey(identity.publicDecryptionKey)
|
val signingKey = cryptography().createPublicKey(identity.publicDecryptionKey)
|
||||||
val reader = ProofOfWorkRequest.Reader(identity)
|
val reader = ProofOfWorkRequest.Reader(identity)
|
||||||
val powRepo = Singleton.getProofOfWorkRepository(context)
|
val powRepo = Singleton.getProofOfWorkRepository(context)
|
||||||
@ -113,12 +115,13 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
|
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
|
||||||
val target = cryptography().getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
val target = cryptography().getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||||
val cryptoMsg = CryptoCustomMessage(
|
val cryptoMsg = CryptoCustomMessage(
|
||||||
ProofOfWorkRequest(identity, initialHash, CALCULATE, target))
|
ProofOfWorkRequest(identity, initialHash, CALCULATE, target)
|
||||||
|
)
|
||||||
cryptoMsg.signAndEncrypt(identity, signingKey)
|
cryptoMsg.signAndEncrypt(identity, signingKey)
|
||||||
val response = bmc.send(
|
val response = bmc.send(
|
||||||
trustedNode,
|
trustedNode,
|
||||||
Preferences.getTrustedNodePort(context),
|
Preferences.getTrustedNodePort(context),
|
||||||
cryptoMsg
|
cryptoMsg
|
||||||
)
|
)
|
||||||
if (response.isError) {
|
if (response.isError) {
|
||||||
LOG.error("Server responded with error: ${String(response.getData())}")
|
LOG.error("Server responded with error: ${String(response.getData())}")
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
package ch.dissem.apps.abit.util
|
package ch.dissem.apps.abit.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.annotation.DrawableRes
|
|
||||||
import android.support.annotation.StringRes
|
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -6,10 +6,7 @@ import android.app.job.JobScheduler
|
|||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
|
||||||
import android.support.annotation.RequiresApi
|
|
||||||
import android.support.v4.content.ContextCompat
|
import android.support.v4.content.ContextCompat
|
||||||
import ch.dissem.apps.abit.MainActivity
|
|
||||||
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
|
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
|
||||||
import ch.dissem.apps.abit.service.BitmessageService
|
import ch.dissem.apps.abit.service.BitmessageService
|
||||||
import ch.dissem.apps.abit.service.StartupNodeOnWifiService
|
import ch.dissem.apps.abit.service.StartupNodeOnWifiService
|
||||||
@ -20,36 +17,19 @@ object NetworkUtils {
|
|||||||
fun enableNode(ctx: Context, ask: Boolean = true) {
|
fun enableNode(ctx: Context, ask: Boolean = true) {
|
||||||
Preferences.setFullNodeActive(ctx, true)
|
Preferences.setFullNodeActive(ctx, true)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Preferences.isConnectionAllowed(ctx) || !ask) {
|
||||||
if (Preferences.isConnectionAllowed(ctx) || !ask) {
|
scheduleNodeStart(ctx)
|
||||||
scheduleNodeStart(ctx)
|
|
||||||
} else {
|
|
||||||
askForConnection(ctx)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (Preferences.isWifiOnly(ctx)) {
|
// Ask for connection
|
||||||
if (Preferences.isConnectionAllowed(ctx)) {
|
val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java)
|
||||||
doStartBitmessageService(ctx)
|
if (ctx !is Activity) {
|
||||||
MainActivity.updateNodeSwitch()
|
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
} else if (ask) {
|
ctx.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
||||||
askForConnection(ctx)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doStartBitmessageService(ctx)
|
|
||||||
MainActivity.updateNodeSwitch()
|
|
||||||
}
|
}
|
||||||
|
ctx.startActivity(dialogIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun askForConnection(ctx: Context) {
|
|
||||||
val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java)
|
|
||||||
if (ctx !is Activity) {
|
|
||||||
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
ctx.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
|
||||||
}
|
|
||||||
ctx.startActivity(dialogIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun doStartBitmessageService(ctx: Context) {
|
fun doStartBitmessageService(ctx: Context) {
|
||||||
ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java))
|
ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java))
|
||||||
}
|
}
|
||||||
@ -59,7 +39,6 @@ object NetworkUtils {
|
|||||||
ctx.stopService(Intent(ctx, BitmessageService::class.java))
|
ctx.stopService(Intent(ctx, BitmessageService::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
fun scheduleNodeStart(ctx: Context) {
|
fun scheduleNodeStart(ctx: Context) {
|
||||||
val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
||||||
val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java)
|
val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java)
|
||||||
|
@ -1,345 +0,0 @@
|
|||||||
/*
|
|
||||||
* This software is provided 'as-is', without any express or implied
|
|
||||||
* warranty. In no event will Google be held liable for any damages
|
|
||||||
* arising from the use of this software.
|
|
||||||
*
|
|
||||||
* Permission is granted to anyone to use this software for any purpose,
|
|
||||||
* including commercial applications, and to alter it and redistribute it
|
|
||||||
* freely, as long as the origin is not misrepresented.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.apps.abit.util;
|
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Process;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.Provider;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.SecureRandomSpi;
|
|
||||||
import java.security.Security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fixes for the output of the default PRNG having low entropy.
|
|
||||||
* <p/>
|
|
||||||
* The fixes need to be applied via {@link #apply()} before any use of Java
|
|
||||||
* Cryptography Architecture primitives. A good place to invoke them is in the
|
|
||||||
* application's {@code onCreate}.
|
|
||||||
*
|
|
||||||
* @see <a href="http://android-developers.blogspot.ch/2013/08/some-securerandom-thoughts.html">
|
|
||||||
* http://android-developers.blogspot.ch/2013/08/some-securerandom-thoughts.html</a>
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
public final class PRNGFixes {
|
|
||||||
|
|
||||||
private static final int VERSION_CODE_JELLY_BEAN = 16;
|
|
||||||
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
|
|
||||||
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
|
|
||||||
getBuildFingerprintAndDeviceSerial();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hidden constructor to prevent instantiation.
|
|
||||||
*/
|
|
||||||
private PRNGFixes() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies all fixes.
|
|
||||||
*
|
|
||||||
* @throws SecurityException if a fix is needed but could not be applied.
|
|
||||||
*/
|
|
||||||
public static void apply() {
|
|
||||||
applyOpenSSLFix();
|
|
||||||
installLinuxPRNGSecureRandom();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
|
|
||||||
* fix is not needed.
|
|
||||||
*
|
|
||||||
* @throws SecurityException if the fix is needed but could not be applied.
|
|
||||||
*/
|
|
||||||
private static void applyOpenSSLFix() throws SecurityException {
|
|
||||||
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|
|
||||||
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
|
|
||||||
// No need to apply the fix
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Mix in the device- and invocation-specific seed.
|
|
||||||
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
|
||||||
.getMethod("RAND_seed", byte[].class)
|
|
||||||
.invoke(null, (Object) generateSeed());
|
|
||||||
|
|
||||||
// Mix output of Linux PRNG into OpenSSL's PRNG
|
|
||||||
int bytesRead = (Integer) Class.forName(
|
|
||||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
|
||||||
.getMethod("RAND_load_file", String.class, long.class)
|
|
||||||
.invoke(null, "/dev/urandom", 1024);
|
|
||||||
if (bytesRead != 1024) {
|
|
||||||
throw new IOException(
|
|
||||||
"Unexpected number of bytes read from Linux PRNG: "
|
|
||||||
+ bytesRead);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
|
|
||||||
* default. Does nothing if the implementation is already the default or if
|
|
||||||
* there is not need to install the implementation.
|
|
||||||
*
|
|
||||||
* @throws SecurityException if the fix is needed but could not be applied.
|
|
||||||
*/
|
|
||||||
private static void installLinuxPRNGSecureRandom()
|
|
||||||
throws SecurityException {
|
|
||||||
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
|
||||||
// No need to apply the fix
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install a Linux PRNG-based SecureRandom implementation as the
|
|
||||||
// default, if not yet installed.
|
|
||||||
Provider[] secureRandomProviders =
|
|
||||||
Security.getProviders("SecureRandom.SHA1PRNG");
|
|
||||||
if ((secureRandomProviders == null)
|
|
||||||
|| (secureRandomProviders.length < 1)
|
|
||||||
|| (!LinuxPRNGSecureRandomProvider.class.equals(
|
|
||||||
secureRandomProviders[0].getClass()))) {
|
|
||||||
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that new SecureRandom() and
|
|
||||||
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
|
|
||||||
// by the Linux PRNG-based SecureRandom implementation.
|
|
||||||
SecureRandom rng1 = new SecureRandom();
|
|
||||||
if (!LinuxPRNGSecureRandomProvider.class.equals(
|
|
||||||
rng1.getProvider().getClass())) {
|
|
||||||
throw new SecurityException(
|
|
||||||
"new SecureRandom() backed by wrong Provider: "
|
|
||||||
+ rng1.getProvider().getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
SecureRandom rng2;
|
|
||||||
try {
|
|
||||||
rng2 = SecureRandom.getInstance("SHA1PRNG");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new SecurityException("SHA1PRNG not available", e);
|
|
||||||
}
|
|
||||||
if (!LinuxPRNGSecureRandomProvider.class.equals(
|
|
||||||
rng2.getProvider().getClass())) {
|
|
||||||
throw new SecurityException(
|
|
||||||
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
|
|
||||||
+ " Provider: " + rng2.getProvider().getClass());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@code Provider} of {@code SecureRandom} engines which pass through
|
|
||||||
* all requests to the Linux PRNG.
|
|
||||||
*/
|
|
||||||
private static class LinuxPRNGSecureRandomProvider extends Provider {
|
|
||||||
|
|
||||||
LinuxPRNGSecureRandomProvider() {
|
|
||||||
super("LinuxPRNG",
|
|
||||||
1.0,
|
|
||||||
"A Linux-specific random number provider that uses"
|
|
||||||
+ " /dev/urandom");
|
|
||||||
// Although /dev/urandom is not a SHA-1 PRNG, some apps
|
|
||||||
// explicitly request a SHA1PRNG SecureRandom and we thus need to
|
|
||||||
// prevent them from getting the default implementation whose output
|
|
||||||
// may have low entropy.
|
|
||||||
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
|
|
||||||
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
|
|
||||||
* ({@code /dev/urandom}).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("JavaDoc")
|
|
||||||
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
|
|
||||||
* are passed through to the Linux PRNG (/dev/urandom). Instances of
|
|
||||||
* this class seed themselves by mixing in the current time, PID, UID,
|
|
||||||
* build fingerprint, and hardware serial number (where available) into
|
|
||||||
* Linux PRNG.
|
|
||||||
*
|
|
||||||
* Concurrency: Read requests to the underlying Linux PRNG are
|
|
||||||
* serialized (on sLock) to ensure that multiple threads do not get
|
|
||||||
* duplicated PRNG output.
|
|
||||||
*/
|
|
||||||
|
|
||||||
private static final File URANDOM_FILE = new File("/dev/urandom");
|
|
||||||
|
|
||||||
private static final Object sLock = new Object();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input stream for reading from Linux PRNG or {@code null} if not yet
|
|
||||||
* opened.
|
|
||||||
*
|
|
||||||
* @GuardedBy("sLock")
|
|
||||||
*/
|
|
||||||
private static DataInputStream sUrandomIn;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output stream for writing to Linux PRNG or {@code null} if not yet
|
|
||||||
* opened.
|
|
||||||
*
|
|
||||||
* @GuardedBy("sLock")
|
|
||||||
*/
|
|
||||||
private static OutputStream sUrandomOut;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this engine instance has been seeded. This is needed because
|
|
||||||
* each instance needs to seed itself if the client does not explicitly
|
|
||||||
* seed it.
|
|
||||||
*/
|
|
||||||
private boolean mSeeded;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void engineSetSeed(byte[] bytes) {
|
|
||||||
try {
|
|
||||||
OutputStream out;
|
|
||||||
synchronized (sLock) {
|
|
||||||
out = getUrandomOutputStream();
|
|
||||||
}
|
|
||||||
out.write(bytes);
|
|
||||||
out.flush();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// On a small fraction of devices /dev/urandom is not writable.
|
|
||||||
// Log and ignore.
|
|
||||||
Log.w(PRNGFixes.class.getSimpleName(),
|
|
||||||
"Failed to mix seed into " + URANDOM_FILE);
|
|
||||||
} finally {
|
|
||||||
mSeeded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void engineNextBytes(byte[] bytes) {
|
|
||||||
if (!mSeeded) {
|
|
||||||
// Mix in the device- and invocation-specific seed.
|
|
||||||
engineSetSeed(generateSeed());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
DataInputStream in;
|
|
||||||
synchronized (sLock) {
|
|
||||||
in = getUrandomInputStream();
|
|
||||||
}
|
|
||||||
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
|
||||||
synchronized (in) {
|
|
||||||
in.readFully(bytes);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new SecurityException(
|
|
||||||
"Failed to read from " + URANDOM_FILE, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] engineGenerateSeed(int size) {
|
|
||||||
byte[] seed = new byte[size];
|
|
||||||
engineNextBytes(seed);
|
|
||||||
return seed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DataInputStream getUrandomInputStream() {
|
|
||||||
synchronized (sLock) {
|
|
||||||
if (sUrandomIn == null) {
|
|
||||||
// NOTE: Consider inserting a BufferedInputStream between
|
|
||||||
// DataInputStream and FileInputStream if you need higher
|
|
||||||
// PRNG output performance and can live with future PRNG
|
|
||||||
// output being pulled into this process prematurely.
|
|
||||||
try {
|
|
||||||
sUrandomIn = new DataInputStream(
|
|
||||||
new FileInputStream(URANDOM_FILE));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new SecurityException("Failed to open "
|
|
||||||
+ URANDOM_FILE + " for reading", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sUrandomIn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OutputStream getUrandomOutputStream() throws IOException {
|
|
||||||
synchronized (sLock) {
|
|
||||||
if (sUrandomOut == null) {
|
|
||||||
sUrandomOut = new FileOutputStream(URANDOM_FILE);
|
|
||||||
}
|
|
||||||
return sUrandomOut;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a device- and invocation-specific seed to be mixed into the
|
|
||||||
* Linux PRNG.
|
|
||||||
*/
|
|
||||||
private static byte[] generateSeed() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream seedBufferOut =
|
|
||||||
new DataOutputStream(seedBuffer);
|
|
||||||
seedBufferOut.writeLong(System.currentTimeMillis());
|
|
||||||
seedBufferOut.writeLong(System.nanoTime());
|
|
||||||
seedBufferOut.writeInt(Process.myPid());
|
|
||||||
seedBufferOut.writeInt(Process.myUid());
|
|
||||||
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
|
|
||||||
seedBufferOut.close();
|
|
||||||
return seedBuffer.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new SecurityException("Failed to generate seed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the hardware serial number of this device.
|
|
||||||
*
|
|
||||||
* @return serial number or {@code null} if not available.
|
|
||||||
*/
|
|
||||||
private static String getDeviceSerialNumber() {
|
|
||||||
// We're using the Reflection API because Build.SERIAL is only available
|
|
||||||
// since API Level 9 (Gingerbread, Android 2.3).
|
|
||||||
try {
|
|
||||||
return (String) Build.class.getField("SERIAL").get(null);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] getBuildFingerprintAndDeviceSerial() {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
String fingerprint = Build.FINGERPRINT;
|
|
||||||
if (fingerprint != null) {
|
|
||||||
result.append(fingerprint);
|
|
||||||
}
|
|
||||||
String serial = getDeviceSerialNumber();
|
|
||||||
if (serial != null) {
|
|
||||||
result.append(serial);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return result.toString().getBytes("UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException("UTF-8 encoding not supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2015 Haruki Hasegawa
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item>
|
|
||||||
<color android:color="@color/bg_swipe_item_trash"/>
|
|
||||||
</item>
|
|
||||||
<item
|
|
||||||
android:drawable="@drawable/ic_item_swipe_trash"
|
|
||||||
android:gravity="right|center_vertical"
|
|
||||||
android:right="16dp"/>
|
|
||||||
</layer-list>
|
|
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2015 Haruki Hasegawa
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item>
|
|
||||||
<color android:color="@color/bg_swipe_item_archive"/>
|
|
||||||
</item>
|
|
||||||
<item
|
|
||||||
android:drawable="@drawable/ic_item_swipe_archive"
|
|
||||||
android:gravity="left|center_vertical"
|
|
||||||
android:left="16dp"/>
|
|
||||||
</layer-list>
|
|
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:color="#ffffff">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@android:id/mask"
|
|
||||||
android:drawable="@android:color/white"/>
|
|
||||||
|
|
||||||
</ripple>
|
|
@ -18,9 +18,8 @@
|
|||||||
<item>
|
<item>
|
||||||
<color android:color="@color/bg_swipe_item_trash"/>
|
<color android:color="@color/bg_swipe_item_trash"/>
|
||||||
</item>
|
</item>
|
||||||
<item android:right="16dp">
|
<item
|
||||||
<bitmap
|
android:drawable="@drawable/ic_item_swipe_trash"
|
||||||
android:gravity="right|center_vertical"
|
android:gravity="right|center_vertical"
|
||||||
android:src="@drawable/ic_item_swipe_trash"/>
|
android:right="16dp"/>
|
||||||
</item>
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
@ -18,9 +18,8 @@
|
|||||||
<item>
|
<item>
|
||||||
<color android:color="@color/bg_swipe_item_archive"/>
|
<color android:color="@color/bg_swipe_item_archive"/>
|
||||||
</item>
|
</item>
|
||||||
<item android:left="16dp">
|
<item
|
||||||
<bitmap
|
android:drawable="@drawable/ic_item_swipe_archive"
|
||||||
android:gravity="left|center_vertical"
|
android:gravity="left|center_vertical"
|
||||||
android:src="@drawable/ic_item_swipe_archive"/>
|
android:left="16dp"/>
|
||||||
</item>
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<item android:state_pressed="true">
|
android:color="#ffffff">
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<solid android:color="#ccffffff" />
|
<item
|
||||||
</shape>
|
android:id="@android:id/mask"
|
||||||
</item>
|
android:drawable="@android:color/white"/>
|
||||||
<item android:state_focused="true">
|
|
||||||
<shape android:shape="rectangle">
|
</ripple>
|
||||||
<stroke android:color="@android:color/white" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
<item android:drawable="@android:color/transparent" />
|
|
||||||
</selector>
|
|
@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/material_showcase_button_bg"
|
|
||||||
android:text="@string/got_it"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textSize="13sp"
|
|
||||||
android:textStyle="bold">
|
|
||||||
</Button>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<bool name="is_pre_api_21">false</bool>
|
|
||||||
<bool name="is_post_api_21">true</bool>
|
|
||||||
</resources>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<bool name="is_pre_api_21">true</bool>
|
|
||||||
<bool name="is_post_api_21">false</bool>
|
|
||||||
</resources>
|
|
@ -34,7 +34,6 @@
|
|||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="require_charging"
|
android:key="require_charging"
|
||||||
android:enabled="@bool/is_post_api_21"
|
|
||||||
android:summary="@string/require_charging_summary"
|
android:summary="@string/require_charging_summary"
|
||||||
android:title="@string/require_charging" />
|
android:title="@string/require_charging" />
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
|
Reference in New Issue
Block a user