🔥 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 {
applicationId "ch.dissem.apps.${appName.toLowerCase()}"
minSdkVersion 19
minSdkVersion 21
targetSdkVersion 27
versionCode 23
versionName "1.0-rc1"

View File

@ -175,20 +175,7 @@
<!-- Receive Wi-Fi connection state changes -->
<receiver
android:name=".listener.WifiReceiver"
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">
android:name=".service.StartServiceReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>

View File

@ -4,7 +4,6 @@ import android.content.Intent
import android.os.Bundle
import android.support.v4.app.NavUtils
import android.view.MenuItem
import ch.dissem.bitmessage.entity.Conversation
import ch.dissem.bitmessage.entity.Plaintext

View File

@ -18,8 +18,6 @@ package ch.dissem.apps.abit
import android.app.Activity
import android.content.*
import android.os.Build
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Bundle
import android.os.IBinder
import android.support.v4.app.Fragment
@ -264,7 +262,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
private fun connectivityChangeListener() =
OnPreferenceChangeListener { _, _ ->
context?.let { ctx ->
if (Build.VERSION.SDK_INT >= LOLLIPOP && Preferences.isFullNodeActive(ctx)) {
if (Preferences.isFullNodeActive(ctx)) {
NetworkUtils.scheduleNodeStart(ctx)
}
}

View File

@ -149,7 +149,7 @@ class ConversationAdapter internal constructor(
linksClickable = 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 {
adapter = labelAdapter
layoutManager = GridLayoutManager(itemView.context, 2)

View File

@ -2,7 +2,6 @@ package ch.dissem.apps.abit.adapter
import android.content.Context
import android.content.res.ColorStateList
import android.os.Build
import android.support.annotation.ColorInt
import android.support.v7.widget.RecyclerView
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.bitmessage.entity.valueobject.Label
import com.mikepenz.iconics.view.IconicsImageView
import org.jetbrains.anko.backgroundColor
class LabelAdapter internal constructor(private val ctx: Context, labels: Collection<Label>) :
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)!!
fun setBackground(@ColorInt color: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
itemView.backgroundTintList = ColorStateList.valueOf(color)
} else {
itemView.backgroundColor = color
}
itemView.backgroundTintList = ColorStateList.valueOf(color)
}
}
}

View File

@ -17,7 +17,6 @@
package ch.dissem.apps.abit.dialog
import android.app.Activity
import android.os.Build
import android.os.Bundle
import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.util.NetworkUtils
@ -37,9 +36,7 @@ class FullNodeDialogActivity : Activity() {
finish()
}
dismiss.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
NetworkUtils.scheduleNodeStart(applicationContext)
}
NetworkUtils.scheduleNodeStart(applicationContext)
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.IntentFilter
import android.net.ConnectivityManager
import android.os.BatteryManager
import android.os.Handler
import ch.dissem.apps.abit.notification.NetworkNotification
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.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class StartupNodeOnWifiService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
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_SYNC
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.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
@ -43,11 +44,11 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
private val bmc = Singleton.getBitmessageContext(context)
override fun onPerformSync(
account: Account,
extras: Bundle,
authority: String,
provider: ContentProviderClient,
syncResult: SyncResult
account: Account,
extras: Bundle,
authority: String,
provider: ContentProviderClient,
syncResult: SyncResult
) {
try {
if (account == ACCOUNT_SYNC) {
@ -75,16 +76,15 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
}
val trustedNode = Preferences.getTrustedNode(context)
if (trustedNode == null) {
LOG.info("Trusted node not available, disabling synchronization")
stopSync(context)
NetworkUtils.scheduleNodeStart(context)
return
}
LOG.info("Synchronization started")
bmc.synchronize(
trustedNode,
Preferences.getTrustedNodePort(context),
Preferences.getTimeoutInSeconds(context),
true
trustedNode,
Preferences.getTrustedNodePort(context),
Preferences.getTimeoutInSeconds(context),
true
)
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
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 reader = ProofOfWorkRequest.Reader(identity)
val powRepo = Singleton.getProofOfWorkRepository(context)
@ -113,12 +114,13 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
val target = cryptography().getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
val cryptoMsg = CryptoCustomMessage(
ProofOfWorkRequest(identity, initialHash, CALCULATE, target))
ProofOfWorkRequest(identity, initialHash, CALCULATE, target)
)
cryptoMsg.signAndEncrypt(identity, signingKey)
val response = bmc.send(
trustedNode,
Preferences.getTrustedNodePort(context),
cryptoMsg
trustedNode,
Preferences.getTrustedNodePort(context),
cryptoMsg
)
if (response.isError) {
LOG.error("Server responded with error: ${String(response.getData())}")

View File

@ -17,8 +17,6 @@
package ch.dissem.apps.abit.util
import android.content.Context
import android.support.annotation.DrawableRes
import android.support.annotation.StringRes
import ch.dissem.apps.abit.R
import ch.dissem.bitmessage.entity.Plaintext
import java.io.IOException

View File

@ -6,10 +6,7 @@ import android.app.job.JobScheduler
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.support.annotation.RequiresApi
import android.support.v4.content.ContextCompat
import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
import ch.dissem.apps.abit.service.BitmessageService
import ch.dissem.apps.abit.service.StartupNodeOnWifiService
@ -20,36 +17,19 @@ object NetworkUtils {
fun enableNode(ctx: Context, ask: Boolean = true) {
Preferences.setFullNodeActive(ctx, true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Preferences.isConnectionAllowed(ctx) || !ask) {
scheduleNodeStart(ctx)
} else {
askForConnection(ctx)
}
if (Preferences.isConnectionAllowed(ctx) || !ask) {
scheduleNodeStart(ctx)
} else {
if (Preferences.isWifiOnly(ctx)) {
if (Preferences.isConnectionAllowed(ctx)) {
doStartBitmessageService(ctx)
MainActivity.updateNodeSwitch()
} else if (ask) {
askForConnection(ctx)
}
} else {
doStartBitmessageService(ctx)
MainActivity.updateNodeSwitch()
// Ask for connection
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)
}
}
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) {
ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java))
}
@ -59,7 +39,6 @@ object NetworkUtils {
ctx.stopService(Intent(ctx, BitmessageService::class.java))
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun scheduleNodeStart(ctx: Context) {
val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
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>
<color android:color="@color/bg_swipe_item_trash"/>
</item>
<item android:right="16dp">
<bitmap
android:gravity="right|center_vertical"
android:src="@drawable/ic_item_swipe_trash"/>
</item>
<item
android:drawable="@drawable/ic_item_swipe_trash"
android:gravity="right|center_vertical"
android:right="16dp"/>
</layer-list>

View File

@ -18,9 +18,8 @@
<item>
<color android:color="@color/bg_swipe_item_archive"/>
</item>
<item android:left="16dp">
<bitmap
android:gravity="left|center_vertical"
android:src="@drawable/ic_item_swipe_archive"/>
</item>
<item
android:drawable="@drawable/ic_item_swipe_archive"
android:gravity="left|center_vertical"
android:left="16dp"/>
</layer-list>

View File

@ -1,14 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#ccffffff" />
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle">
<stroke android:color="@android:color/white" />
</shape>
</item>
<item android:drawable="@android:color/transparent" />
</selector>
<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

@ -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
android:defaultValue="false"
android:key="require_charging"
android:enabled="@bool/is_post_api_21"
android:summary="@string/require_charging_summary"
android:title="@string/require_charging" />
<SwitchPreferenceCompat