feat: raise min SDK to 26

Autofill is only available on API 26 and above and I do not want to deal with
bugs on these older Android releases.
This commit is contained in:
Harsh Shandilya 2023-05-12 20:28:36 +05:30
parent ee9d77eafd
commit ec696c1d8d
No known key found for this signature in database
27 changed files with 63 additions and 164 deletions

View file

@ -35,7 +35,8 @@ All notable changes to this project will be documented in this file.
- **BREAKING**: The app's package name has been changed to `app.passwordstore` so users are aware that this is a new project with no compatibility guarantees with Password Store 1.x.y. - **BREAKING**: The app's package name has been changed to `app.passwordstore` so users are aware that this is a new project with no compatibility guarantees with Password Store 1.x.y.
- **BREAKING**: Introduce a new PGP backend powered by [PGPainless](https://github.com/pgpainless/pgpainless) which completely replaces OpenKeychain - **BREAKING**: Introduce a new PGP backend powered by [PGPainless](https://github.com/pgpainless/pgpainless) which completely replaces OpenKeychain
- **BREAKING**: Accessibility autofill has been removed completely due to being buggy, insecure and lacking in features. Upgrade to Android 8 or preferably later to gain access to our advanced Autofill implementation. - **BREAKING**: Accessibility autofill has been removed completely due to being buggy, insecure and lacking in features. Upgrade to Android 8 or preferably later to gain access to our advanced Autofill implementation.
- **BREAKING***: Support for stores outside the hidden app directory has been removed due to technical restrictions, see [this issue](https://github.com/Android-Password-Store/Android-Password-Store/issues/1849) for details. - **BREAKING**: Support for stores outside the hidden app directory has been removed due to technical restrictions, see [this issue](https://github.com/Android-Password-Store/Android-Password-Store/issues/1849) for details.
- **BREAKING**: The app's minimum supported Android version has been raised to Android Oreo (API level 26).
- The settings UI has been completely re-done to dramatically improve discoverability and navigation for users - The settings UI has been completely re-done to dramatically improve discoverability and navigation for users
- Using the `git://` protocol in the server URL now presents an explicit discouragement rather than a generic error - Using the `git://` protocol in the server URL now presents an explicit discouragement rather than a generic error
- Encrypted data is no longer ASCII armored, bringing it in line with `pass` - Encrypted data is no longer ASCII armored, bringing it in line with `pass`

View file

@ -6,11 +6,8 @@ package app.passwordstore.autofill.oreo.ui
import android.content.Context import android.content.Context
import android.content.IntentSender import android.content.IntentSender
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@RequiresApi(Build.VERSION_CODES.O)
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
class AutofillSmsActivity : AppCompatActivity() { class AutofillSmsActivity : AppCompatActivity() {

View file

@ -96,9 +96,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
.detectLeakedRegistrationObjects() .detectLeakedRegistrationObjects()
.detectLeakedSqlLiteObjects() .detectLeakedSqlLiteObjects()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder.detectContentUriWithoutPermission()
builder.detectContentUriWithoutPermission()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.detectCredentialProtectedWhileLocked().detectImplicitDirectBoot() builder.detectCredentialProtectedWhileLocked().detectImplicitDirectBoot()

View file

@ -11,7 +11,6 @@ import android.content.IntentSender
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import androidx.annotation.RequiresApi
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.passwordstore.data.crypto.GPGPassphraseCache import app.passwordstore.data.crypto.GPGPassphraseCache
import app.passwordstore.data.passfile.PasswordEntry import app.passwordstore.data.passfile.PasswordEntry
@ -41,7 +40,6 @@ import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
import logcat.logcat import logcat.logcat
@RequiresApi(Build.VERSION_CODES.O)
@AndroidEntryPoint @AndroidEntryPoint
class AutofillDecryptActivity : BasePgpActivity() { class AutofillDecryptActivity : BasePgpActivity() {

View file

@ -4,7 +4,6 @@
*/ */
package app.passwordstore.ui.autofill package app.passwordstore.ui.autofill
import android.annotation.TargetApi
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -43,7 +42,6 @@ import kotlinx.coroutines.flow.onEach
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
import logcat.logcat import logcat.logcat
@TargetApi(Build.VERSION_CODES.O)
@AndroidEntryPoint @AndroidEntryPoint
class AutofillFilterView : AppCompatActivity() { class AutofillFilterView : AppCompatActivity() {

View file

@ -4,7 +4,6 @@
*/ */
package app.passwordstore.ui.autofill package app.passwordstore.ui.autofill
import android.annotation.TargetApi
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -33,7 +32,6 @@ import com.github.michaelbull.result.runCatching
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
import logcat.logcat import logcat.logcat
@TargetApi(Build.VERSION_CODES.O)
class AutofillPublisherChangedActivity : AppCompatActivity() { class AutofillPublisherChangedActivity : AppCompatActivity() {
companion object { companion object {

View file

@ -8,11 +8,9 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentSender import android.content.IntentSender
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import app.passwordstore.data.repo.PasswordRepository import app.passwordstore.data.repo.PasswordRepository
@ -29,7 +27,6 @@ import java.io.File
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
import logcat.logcat import logcat.logcat
@RequiresApi(Build.VERSION_CODES.O)
@AndroidEntryPoint @AndroidEntryPoint
class AutofillSaveActivity : AppCompatActivity() { class AutofillSaveActivity : AppCompatActivity() {

View file

@ -57,7 +57,7 @@ open class BasePgpActivity : AppCompatActivity() {
val name: String by unsafeLazy { File(fullPath).nameWithoutExtension } val name: String by unsafeLazy { File(fullPath).nameWithoutExtension }
/** Action to invoke if [keyImportAction] succeeds. */ /** Action to invoke if [keyImportAction] succeeds. */
var onKeyImport: (() -> Unit)? = null private var onKeyImport: (() -> Unit)? = null
private val keyImportAction = private val keyImportAction =
registerForActivityResult(StartActivityForResult()) { registerForActivityResult(StartActivityForResult()) {
if (it.resultCode == RESULT_OK) { if (it.resultCode == RESULT_OK) {
@ -94,10 +94,8 @@ open class BasePgpActivity : AppCompatActivity() {
) { ) {
val clipboard = clipboard ?: return val clipboard = clipboard ?: return
val clip = ClipData.newPlainText("pgp_handler_result_pm", text) val clip = ClipData.newPlainText("pgp_handler_result_pm", text)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { clip.description.extras =
clip.description.extras = PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) }
PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) }
}
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)
if (showSnackbar && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { if (showSnackbar && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
snackbar(message = resources.getString(snackbarTextRes)) snackbar(message = resources.getString(snackbarTextRes))
@ -144,11 +142,7 @@ open class BasePgpActivity : AppCompatActivity() {
action = ClipboardService.ACTION_START action = ClipboardService.ACTION_START
putExtra(ClipboardService.EXTRA_NOTIFICATION_TIME, clearAfter) putExtra(ClipboardService.EXTRA_NOTIFICATION_TIME, clearAfter)
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(service)
startForegroundService(service)
} else {
startService(service)
}
} }
} }

View file

@ -8,9 +8,7 @@ package app.passwordstore.ui.settings
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.provider.Settings import android.provider.Settings
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -35,11 +33,9 @@ class AutofillSettings(private val activity: FragmentActivity) : SettingsProvide
private val isAutofillServiceEnabled: Boolean private val isAutofillServiceEnabled: Boolean
get() { get() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
return activity.autofillManager?.hasEnabledAutofillServices() == true return activity.autofillManager?.hasEnabledAutofillServices() == true
} }
@RequiresApi(Build.VERSION_CODES.O)
private fun showAutofillDialog(pref: SwitchPreference) { private fun showAutofillDialog(pref: SwitchPreference) {
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
when (event) { when (event) {
@ -95,10 +91,8 @@ class AutofillSettings(private val activity: FragmentActivity) : SettingsProvide
builder.apply { builder.apply {
switch(PreferenceKeys.AUTOFILL_ENABLE) { switch(PreferenceKeys.AUTOFILL_ENABLE) {
titleRes = R.string.pref_autofill_enable_title titleRes = R.string.pref_autofill_enable_title
visible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
defaultValue = isAutofillServiceEnabled defaultValue = isAutofillServiceEnabled
onClick { onClick {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return@onClick true
if (isAutofillServiceEnabled) { if (isAutofillServiceEnabled) {
activity.autofillManager?.disableAutofillServices() activity.autofillManager?.disableAutofillServices()
} else { } else {

View file

@ -6,7 +6,6 @@
package app.passwordstore.ui.settings package app.passwordstore.ui.settings
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.os.Build
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -97,10 +96,8 @@ class GeneralSettings(private val activity: FragmentActivity) : SettingsProvider
} }
} }
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { activity.getSystemService<ShortcutManager>()?.apply {
activity.getSystemService<ShortcutManager>()?.apply { removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
}
} }
false false
} }

View file

@ -8,7 +8,6 @@ package app.passwordstore.ui.settings
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -47,11 +46,7 @@ class MiscSettings(activity: FragmentActivity) : SettingsProvider {
putExtra("uri", uri) putExtra("uri", uri)
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { activity.startForegroundService(service)
activity.startForegroundService(service)
} else {
activity.startService(service)
}
} }
} }

View file

@ -8,7 +8,6 @@ package app.passwordstore.ui.settings
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
@ -172,10 +171,8 @@ class RepositorySettings(
} }
.onFailure { it.message?.let { message -> activity.snackbar(message = message) } } .onFailure { it.message?.let { message -> activity.snackbar(message = message) } }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { activity.getSystemService<ShortcutManager>()?.apply {
activity.getSystemService<ShortcutManager>()?.apply { removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
}
} }
activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) } activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) }
dialogInterface.cancel() dialogInterface.cancel()

View file

@ -5,8 +5,6 @@
package app.passwordstore.util.autofill package app.passwordstore.util.autofill
import android.content.Context import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import app.passwordstore.data.passfile.PasswordEntry import app.passwordstore.data.passfile.PasswordEntry
import app.passwordstore.util.extensions.getString import app.passwordstore.util.extensions.getString
import app.passwordstore.util.extensions.sharedPrefs import app.passwordstore.util.extensions.sharedPrefs
@ -101,7 +99,6 @@ enum class DirectoryStructure(val value: String) {
?: file.nameWithoutExtension ?: file.nameWithoutExtension
} }
@RequiresApi(Build.VERSION_CODES.O)
fun getSaveFolderName(sanitizedIdentifier: String, username: String?) = fun getSaveFolderName(sanitizedIdentifier: String, username: String?) =
when (this) { when (this) {
EncryptedUsername -> "/" EncryptedUsername -> "/"

View file

@ -12,7 +12,6 @@ import android.service.autofill.Dataset
import android.service.autofill.FillCallback import android.service.autofill.FillCallback
import android.service.autofill.FillResponse import android.service.autofill.FillResponse
import android.service.autofill.SaveInfo import android.service.autofill.SaveInfo
import androidx.annotation.RequiresApi
import app.passwordstore.autofill.oreo.ui.AutofillSmsActivity import app.passwordstore.autofill.oreo.ui.AutofillSmsActivity
import app.passwordstore.ui.autofill.AutofillDecryptActivity import app.passwordstore.ui.autofill.AutofillDecryptActivity
import app.passwordstore.ui.autofill.AutofillFilterView import app.passwordstore.ui.autofill.AutofillFilterView
@ -32,7 +31,6 @@ import logcat.LogPriority.ERROR
import logcat.asLog import logcat.asLog
import logcat.logcat import logcat.logcat
@RequiresApi(Build.VERSION_CODES.O)
class AutofillResponseBuilder class AutofillResponseBuilder
@AssistedInject @AssistedInject
constructor( constructor(

View file

@ -21,7 +21,6 @@ import android.util.TypedValue
import android.view.View import android.view.View
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.annotation.RequiresApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
@ -39,7 +38,7 @@ import logcat.logcat
/** Get an instance of [AutofillManager]. Only available on Android Oreo and above */ /** Get an instance of [AutofillManager]. Only available on Android Oreo and above */
val Context.autofillManager: AutofillManager? val Context.autofillManager: AutofillManager?
@RequiresApi(Build.VERSION_CODES.O) get() = getSystemService() get() = getSystemService()
/** Get an instance of [ClipboardManager] */ /** Get an instance of [ClipboardManager] */
val Context.clipboard val Context.clipboard

View file

@ -14,7 +14,6 @@ import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import app.passwordstore.R import app.passwordstore.R
@ -40,11 +39,7 @@ class ClipboardService : Service() {
when (intent.action) { when (intent.action) {
ACTION_CLEAR -> { ACTION_CLEAR -> {
clearClipboard() clearClipboard()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground(STOP_FOREGROUND_REMOVE)
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION") stopForeground(true)
}
stopSelf() stopSelf()
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
@ -60,11 +55,7 @@ class ClipboardService : Service() {
withContext(Dispatchers.IO) { startTimer(time) } withContext(Dispatchers.IO) { startTimer(time) }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
clearClipboard() clearClipboard()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground(STOP_FOREGROUND_REMOVE)
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION") stopForeground(true)
}
stopSelf() stopSelf()
} }
} }
@ -122,52 +113,23 @@ class ClipboardService : Service() {
val clearTimeMs = clearTime * 1000L val clearTimeMs = clearTime * 1000L
val clearIntent = Intent(this, ClipboardService::class.java).apply { action = ACTION_CLEAR } val clearIntent = Intent(this, ClipboardService::class.java).apply { action = ACTION_CLEAR }
val pendingIntent = val pendingIntent =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { PendingIntent.getForegroundService(
PendingIntent.getForegroundService( this,
this, 0,
0, clearIntent,
clearIntent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE } else {
} else { PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT }
} )
) val notification = createNotification(pendingIntent, clearTimeMs)
} else {
PendingIntent.getService(
this,
0,
clearIntent,
PendingIntent.FLAG_UPDATE_CURRENT,
)
}
val notification =
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) {
createNotificationApi23(pendingIntent)
} else {
createNotificationApi24(pendingIntent, clearTimeMs)
}
createNotificationChannel() createNotificationChannel()
startForeground(1, notification) startForeground(1, notification)
} }
private fun createNotificationApi23(pendingIntent: PendingIntent): Notification { private fun createNotification(pendingIntent: PendingIntent, clearTimeMs: Long): Notification {
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.tap_clear_clipboard))
.setSmallIcon(R.drawable.ic_action_secure_24dp)
.setContentIntent(pendingIntent)
.setUsesChronometer(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build()
}
@RequiresApi(Build.VERSION_CODES.N)
private fun createNotificationApi24(
pendingIntent: PendingIntent,
clearTimeMs: Long
): Notification {
return NotificationCompat.Builder(this, CHANNEL_ID) return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.tap_clear_clipboard)) .setContentText(getString(R.string.tap_clear_clipboard))
@ -182,19 +144,17 @@ class ClipboardService : Service() {
} }
private fun createNotificationChannel() { private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val serviceChannel =
val serviceChannel = NotificationChannel(
NotificationChannel( CHANNEL_ID,
CHANNEL_ID, getString(R.string.app_name),
getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW
NotificationManager.IMPORTANCE_LOW )
) val manager = getSystemService<NotificationManager>()
val manager = getSystemService<NotificationManager>() if (manager != null) {
if (manager != null) { manager.createNotificationChannel(serviceChannel)
manager.createNotificationChannel(serviceChannel) } else {
} else { logcat { "Failed to create notification channel" }
logcat { "Failed to create notification channel" }
}
} }
} }

View file

@ -13,7 +13,6 @@ import android.service.autofill.FillRequest
import android.service.autofill.FillResponse import android.service.autofill.FillResponse
import android.service.autofill.SaveCallback import android.service.autofill.SaveCallback
import android.service.autofill.SaveRequest import android.service.autofill.SaveRequest
import androidx.annotation.RequiresApi
import app.passwordstore.BuildConfig import app.passwordstore.BuildConfig
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.ui.autofill.AutofillSaveActivity import app.passwordstore.ui.autofill.AutofillSaveActivity
@ -37,7 +36,6 @@ import javax.inject.Inject
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
import logcat.logcat import logcat.logcat
@RequiresApi(Build.VERSION_CODES.O)
@AndroidEntryPoint @AndroidEntryPoint
class OreoAutofillService : AutofillService() { class OreoAutofillService : AutofillService() {

View file

@ -10,7 +10,6 @@ import android.app.NotificationManager
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
@ -20,8 +19,6 @@ import app.passwordstore.R
import app.passwordstore.data.repo.PasswordRepository import app.passwordstore.data.repo.PasswordRepository
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Calendar
import java.util.TimeZone
import logcat.logcat import logcat.logcat
class PasswordExportService : Service() { class PasswordExportService : Service() {
@ -65,13 +62,7 @@ class PasswordExportService : Service() {
logcat { "Copying ${repositoryDirectory.path} to $targetDirectory" } logcat { "Copying ${repositoryDirectory.path} to $targetDirectory" }
val dateString = val dateString = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
} else {
String.format("%tFT%<tRZ", Calendar.getInstance(TimeZone.getTimeZone("Z")))
}
val passDir = targetDirectory.createDirectory("password_store_$dateString") val passDir = targetDirectory.createDirectory("password_store_$dateString")
if (passDir != null) { if (passDir != null) {
@ -136,19 +127,17 @@ class PasswordExportService : Service() {
} }
private fun createNotificationChannel() { private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val serviceChannel =
val serviceChannel = NotificationChannel(
NotificationChannel( CHANNEL_ID,
CHANNEL_ID, getString(R.string.app_name),
getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW
NotificationManager.IMPORTANCE_LOW )
) val manager = getSystemService<NotificationManager>()
val manager = getSystemService<NotificationManager>() if (manager != null) {
if (manager != null) { manager.createNotificationChannel(serviceChannel)
manager.createNotificationChannel(serviceChannel) } else {
} else { logcat { "Failed to create notification channel" }
logcat { "Failed to create notification channel" }
}
} }
} }

View file

@ -10,8 +10,6 @@ import android.content.Intent
import android.content.pm.ShortcutInfo import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.data.password.PasswordItem import app.passwordstore.data.password.PasswordItem
@ -42,7 +40,6 @@ constructor(
* [MAX_SHORTCUT_COUNT] and older items are removed by a simple LRU sweep. * [MAX_SHORTCUT_COUNT] and older items are removed by a simple LRU sweep.
*/ */
fun addDynamicShortcut(item: PasswordItem, intent: Intent) { fun addDynamicShortcut(item: PasswordItem, intent: Intent) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return
val shortcutManager: ShortcutManager = context.getSystemService() ?: return val shortcutManager: ShortcutManager = context.getSystemService() ?: return
val shortcut = buildShortcut(item, intent) val shortcut = buildShortcut(item, intent)
val shortcuts = shortcutManager.dynamicShortcuts val shortcuts = shortcutManager.dynamicShortcuts
@ -67,7 +64,6 @@ constructor(
* a no-op if the user's default launcher does not support pinned shortcuts. * a no-op if the user's default launcher does not support pinned shortcuts.
*/ */
fun addPinnedShortcut(item: PasswordItem, intent: Intent) { fun addPinnedShortcut(item: PasswordItem, intent: Intent) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val shortcutManager: ShortcutManager = context.getSystemService() ?: return val shortcutManager: ShortcutManager = context.getSystemService() ?: return
if (!shortcutManager.isRequestPinShortcutSupported) { if (!shortcutManager.isRequestPinShortcutSupported) {
logcat { "addPinnedShortcut: pin shortcuts unsupported" } logcat { "addPinnedShortcut: pin shortcuts unsupported" }
@ -78,7 +74,6 @@ constructor(
} }
/** Creates a [ShortcutInfo] from [item] and assigns [intent] to it. */ /** Creates a [ShortcutInfo] from [item] and assigns [intent] to it. */
@RequiresApi(Build.VERSION_CODES.N_MR1)
private fun buildShortcut(item: PasswordItem, intent: Intent): ShortcutInfo { private fun buildShortcut(item: PasswordItem, intent: Intent): ShortcutInfo {
return ShortcutInfo.Builder(context, item.fullPathToParent) return ShortcutInfo.Builder(context, item.fullPathToParent)
.setShortLabel(item.toString()) .setShortLabel(item.toString())
@ -93,7 +88,6 @@ constructor(
* data, which ensures that the get/set dance in [addDynamicShortcut] does not cause invalidation * data, which ensures that the get/set dance in [addDynamicShortcut] does not cause invalidation
* of icon assets, resulting in invisible icons in all but the newest launcher shortcut. * of icon assets, resulting in invisible icons in all but the newest launcher shortcut.
*/ */
@RequiresApi(Build.VERSION_CODES.N_MR1)
private fun rebuildShortcut(shortcut: ShortcutInfo): ShortcutInfo { private fun rebuildShortcut(shortcut: ShortcutInfo): ShortcutInfo {
// Non-null assertions are fine since we know these values aren't null. // Non-null assertions are fine since we know these values aren't null.
return ShortcutInfo.Builder(context, shortcut.id) return ShortcutInfo.Builder(context, shortcut.id)

View file

@ -14,7 +14,6 @@ import android.content.IntentSender
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.passwordstore.databinding.ActivityOreoAutofillSmsBinding import app.passwordstore.databinding.ActivityOreoAutofillSmsBinding
@ -52,7 +51,6 @@ suspend fun <T> Task<T>.suspendableAwait() =
} }
} }
@RequiresApi(Build.VERSION_CODES.O)
class AutofillSmsActivity : AppCompatActivity() { class AutofillSmsActivity : AppCompatActivity() {
companion object { companion object {

View file

@ -2,6 +2,8 @@
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
*/ */
@file:Suppress("UnstableApiUsage")
plugins { plugins {
id("com.github.android-password-store.published-android-library") id("com.github.android-password-store.published-android-library")
id("com.github.android-password-store.kotlin-android") id("com.github.android-password-store.kotlin-android")
@ -9,7 +11,10 @@ plugins {
} }
android { android {
defaultConfig { consumerProguardFiles("consumer-rules.pro") } defaultConfig {
minSdk = 23
consumerProguardFiles("consumer-rules.pro")
}
sourceSets { getByName("test") { resources.srcDir("src/main/assets") } } sourceSets { getByName("test") { resources.srcDir("src/main/assets") } }
namespace = "com.github.androidpasswordstore.autofillparser" namespace = "com.github.androidpasswordstore.autofillparser"
} }

View file

@ -19,14 +19,8 @@ object AndroidCommon {
project.extensions.configure<TestedExtension> { project.extensions.configure<TestedExtension> {
setCompileSdkVersion(33) setCompileSdkVersion(33)
defaultConfig { defaultConfig {
minSdk = 23 minSdk = 26
targetSdk = 31 targetSdk = 33
}
sourceSets {
named("main") { java.srcDirs("src/main/kotlin") }
named("test") { java.srcDirs("src/test/kotlin") }
named("androidTest") { java.srcDirs("src/androidTest/kotlin") }
} }
packagingOptions { packagingOptions {
@ -47,7 +41,8 @@ object AndroidCommon {
animationsDisabled = true animationsDisabled = true
unitTests.isReturnDefaultValues = true unitTests.isReturnDefaultValues = true
} }
project.tasks.withType<Test> {
project.tasks.withType<Test>().configureEach {
jvmArgs( jvmArgs(
"--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED", "--add-opens=java.base/java.util=ALL-UNNAMED",

View file

@ -23,7 +23,9 @@ class PublishedAndroidLibraryPlugin : Plugin<Project> {
} }
project.extensions.configure<MavenPublishBaseExtension> { project.extensions.configure<MavenPublishBaseExtension> {
publishToMavenCentral(SonatypeHost.DEFAULT, true) publishToMavenCentral(SonatypeHost.DEFAULT, true)
signAllPublications() if (project.providers.environmentVariable("CI").isPresent) {
signAllPublications()
}
} }
project.extensions.configure<MetalavaExtension> { project.extensions.configure<MetalavaExtension> {
documentation.set(Documentation.PUBLIC) documentation.set(Documentation.PUBLIC)