🔥 Drop support for API 19 (KitKat)

I'm fed up with all the extra effort for supporting an outdated
Android version. Apologies for the few users that are now excluded,
but I feel like it's just not worth the hassle.
This commit is contained in:
Christian Basler 2018-06-13 22:03:06 +02:00
parent 8b89d81970
commit c6e29c056b
23 changed files with 48 additions and 220 deletions

View File

@ -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"

View File

@ -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>

View File

@ -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

View File

@ -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
@ -264,7 +262,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)
} }
} }

View File

@ -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)

View File

@ -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
}
} }
} }
} }

View File

@ -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()
} }
} }

View File

@ -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)
}
}
}
}

View File

@ -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

View File

@ -14,7 +14,6 @@ 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)

View File

@ -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,15 @@ 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") NetworkUtils.scheduleNodeStart(context)
stopSync(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 +104,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 +114,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())}")

View File

@ -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

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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