Use storage access framework (#469)

* use storage access framework

* UserPreference: Add back warning about using SDCard root directory

* UserPreference: Fix IDE warnings

Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com>
This commit is contained in:
Hussein Al Abry 2019-04-25 04:41:32 +01:00 committed by Harsh Shandilya
parent f272e4dde2
commit e54010906f
6 changed files with 204 additions and 245 deletions

View file

@ -74,7 +74,6 @@ dependencies {
implementation("com.google.android.material:material:1.0.0") implementation("com.google.android.material:material:1.0.0")
implementation("androidx.annotation:annotation:1.0.2") implementation("androidx.annotation:annotation:1.0.2")
implementation("org.sufficientlysecure:openpgp-api:12.0") implementation("org.sufficientlysecure:openpgp-api:12.0")
implementation("com.nononsenseapps:filepicker:2.4.2")
implementation("org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r") { implementation("org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r") {
exclude(group = "org.apache.httpcomponents", module = "httpclient") exclude(group = "org.apache.httpcomponents", module = "httpclient")
} }

View file

@ -68,15 +68,6 @@
<meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.zeapo.pwdstore.PasswordStore" /> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.zeapo.pwdstore.PasswordStore" />
</activity> </activity>
<activity
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
android:label="@string/app_name"
android:theme="@style/FilePickerTheme">
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".crypto.PgpActivity" <activity android:name=".crypto.PgpActivity"
android:parentActivityName=".PasswordStore"/> android:parentActivityName=".PasswordStore"/>
<activity android:name=".SelectFolderActivity" /> <activity android:name=".SelectFolderActivity" />

View file

@ -1,46 +1,40 @@
package com.zeapo.pwdstore package com.zeapo.pwdstore
import android.Manifest
import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.AccessibilityServiceInfo
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.preference.CheckBoxPreference import android.preference.CheckBoxPreference
import android.preference.Preference import android.preference.Preference
import android.preference.PreferenceFragment import android.preference.PreferenceFragment
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.provider.DocumentsContract
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.documentfile.provider.DocumentFile
import androidx.core.content.ContextCompat
import com.google.android.material.snackbar.Snackbar
import com.nononsenseapps.filepicker.FilePickerActivity
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
import com.zeapo.pwdstore.crypto.PgpActivity import com.zeapo.pwdstore.crypto.PgpActivity
import com.zeapo.pwdstore.git.GitActivity import com.zeapo.pwdstore.git.GitActivity
import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PasswordRepository
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.openintents.openpgp.util.OpenPgpUtils import org.openintents.openpgp.util.OpenPgpUtils
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat import java.time.LocalDateTime
import java.util.ArrayList import java.time.format.DateTimeFormatter
import java.util.Date import java.util.*
import java.util.Locale
class UserPreference : AppCompatActivity() { class UserPreference : AppCompatActivity() {
private lateinit var prefsFragment: PrefsFragment private lateinit var prefsFragment: PrefsFragment
class PrefsFragment : PreferenceFragment() { class PrefsFragment : PreferenceFragment() {
@ -61,7 +55,7 @@ class UserPreference : AppCompatActivity() {
} }
findPreference("ssh_key").onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference("ssh_key").onPreferenceClickListener = Preference.OnPreferenceClickListener {
callingActivity.getSshKeyWithPermissions(sharedPreferences.getBoolean("use_android_file_picker", false)) callingActivity.getSshKey()
true true
} }
@ -115,7 +109,7 @@ class UserPreference : AppCompatActivity() {
FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext)) FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext))
PasswordRepository.closeRepository() PasswordRepository.closeRepository()
} catch (e: Exception) { } catch (e: Exception) {
//TODO Handle the diffent cases of exceptions //TODO Handle the different cases of exceptions
} }
sharedPreferences.edit().putBoolean("repository_initialized", false).apply() sharedPreferences.edit().putBoolean("repository_initialized", false).apply()
@ -164,10 +158,13 @@ class UserPreference : AppCompatActivity() {
true true
} }
findPreference("export_passwords").onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference("export_passwords").apply {
callingActivity.exportPasswordsWithPermissions() isEnabled = sharedPreferences.getBoolean("repository_initialized", false)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
callingActivity.exportPasswords()
true true
} }
}
findPreference("general_show_time").onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? -> findPreference("general_show_time").onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
try { try {
@ -217,10 +214,9 @@ class UserPreference : AppCompatActivity() {
} }
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.applicationContext)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
when (intent?.getStringExtra("operation")) { when (intent?.getStringExtra("operation")) {
"get_ssh_key" -> getSshKeyWithPermissions(sharedPreferences.getBoolean("use_android_file_picker", false)) "get_ssh_key" -> getSshKey()
"make_ssh_key" -> makeSshKey(false) "make_ssh_key" -> makeSshKey(false)
"git_external" -> selectExternalGitRepository() "git_external" -> selectExternalGitRepository()
} }
@ -232,24 +228,12 @@ class UserPreference : AppCompatActivity() {
} }
fun selectExternalGitRepository() { fun selectExternalGitRepository() {
val activity = this
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(this.resources.getString(R.string.external_repository_dialog_title)) .setTitle(this.resources.getString(R.string.external_repository_dialog_title))
.setMessage(this.resources.getString(R.string.external_repository_dialog_text)) .setMessage(this.resources.getString(R.string.external_repository_dialog_text))
.setPositiveButton(R.string.dialog_ok) { _, _ -> .setPositiveButton(R.string.dialog_ok) { _, _ ->
// This always works val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
val i = Intent(activity.applicationContext, FilePickerActivity::class.java) startActivityForResult(Intent.createChooser(i, "Choose Directory"), SELECT_GIT_DIRECTORY)
// This works if you defined the intent filter
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
startActivityForResult(i, SELECT_GIT_DIRECTORY)
}.setNegativeButton(R.string.dialog_cancel, null).show() }.setNegativeButton(R.string.dialog_cancel, null).show()
} }
@ -257,103 +241,33 @@ class UserPreference : AppCompatActivity() {
// Handle action bar item clicks here. The action bar will // Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long // automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml. // as you specify a parent activity in AndroidManifest.xml.
val id = item.itemId return when (item.itemId) {
when (id) {
android.R.id.home -> { android.R.id.home -> {
setResult(Activity.RESULT_OK) setResult(Activity.RESULT_OK)
finish() finish()
return true true
} }
else -> super.onOptionsItemSelected(item)
} }
return super.onOptionsItemSelected(item)
} }
/** /**
* Opens a file explorer to import the private key * Opens a file explorer to import the private key
*/ */
fun getSshKeyWithPermissions(useDefaultPicker: Boolean) = runWithPermissions( private fun getSshKey() {
requestedPermission = Manifest.permission.READ_EXTERNAL_STORAGE, val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY, addCategory(Intent.CATEGORY_OPENABLE)
reason = "We need access to the sd-card to import the ssh-key" type = "*/*"
) {
getSshKey(useDefaultPicker)
}
/**
* Opens a file explorer to import the private key
*/
private fun getSshKey(useDefaultPicker: Boolean) {
val intent = if (useDefaultPicker) {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.setType("*/*")
} else {
// This always works
val intent = Intent(applicationContext, FilePickerActivity::class.java)
// Set these depending on your use case. These are the defaults.
intent.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
intent.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false)
intent.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE)
intent.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
} }
startActivityForResult(intent, IMPORT_SSH_KEY) startActivityForResult(intent, IMPORT_SSH_KEY)
} }
/**
* Run a function after checking that the permissions have been requested
*
* @param requestedPermission The permission to request
* @param requestCode The code passed to onRequestPermissionsResult
* @param reason The text to be shown to the user to explain why we're requesting this permission
* @param body The function to run
*/
private fun runWithPermissions(requestedPermission: String, requestCode: Int, reason: String, body: () -> Unit) {
if (ContextCompat.checkSelfPermission(this, requestedPermission) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, requestedPermission)) {
val snack = Snackbar.make(prefsFragment.view, reason, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.dialog_ok) {
ActivityCompat.requestPermissions(this, arrayOf(requestedPermission), requestCode)
}
snack.show()
val view = snack.view
val tv = view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
tv.setTextColor(Color.WHITE)
tv.maxLines = 10
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this, arrayOf(requestedPermission), requestCode)
}
} else {
body()
}
}
/**
* Exports the passwords after requesting permissions
*/
fun exportPasswordsWithPermissions() = runWithPermissions(
requestedPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
reason = "We need access to the sd-card to export the passwords"
) {
exportPasswords()
}
/** /**
* Exports the passwords * Exports the passwords
*/ */
private fun exportPasswords() { private fun exportPasswords() {
val i = Intent(applicationContext, FilePickerActivity::class.java) val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(Intent.createChooser(i, "Choose Directory"), EXPORT_PASSWORDS)
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
startActivityForResult(i, EXPORT_PASSWORDS)
} }
/** /**
@ -370,11 +284,25 @@ class UserPreference : AppCompatActivity() {
@Throws(IOException::class) @Throws(IOException::class)
private fun copySshKey(uri: Uri) { private fun copySshKey(uri: Uri) {
val sshKey = this.contentResolver.openInputStream(uri) // TODO: Check if valid SSH Key before import
if (sshKey != null) { val sshKeyInputStream = contentResolver.openInputStream(uri)
val privateKey = IOUtils.toByteArray(sshKey) if (sshKeyInputStream != null) {
FileUtils.writeByteArrayToFile(File(filesDir.toString() + "/.ssh_key"), privateKey)
sshKey.close() val internalKeyFile = File("""$filesDir/.ssh_key""")
if (internalKeyFile.exists()) {
internalKeyFile.delete()
internalKeyFile.createNewFile()
}
val sshKeyOutputSteam = internalKeyFile.outputStream()
sshKeyInputStream.copyTo(sshKeyOutputSteam, 1024)
sshKeyInputStream.close()
sshKeyOutputSteam.close()
} else { } else {
Toast.makeText(this, getString(R.string.ssh_key_does_not_exist), Toast.LENGTH_LONG).show() Toast.makeText(this, getString(R.string.ssh_key_does_not_exist), Toast.LENGTH_LONG).show()
} }
@ -408,6 +336,7 @@ class UserPreference : AppCompatActivity() {
val uri: Uri = data.data ?: throw IOException("Unable to open file") val uri: Uri = data.data ?: throw IOException("Unable to open file")
copySshKey(uri) copySshKey(uri)
Toast.makeText( Toast.makeText(
this, this,
this.resources.getString(R.string.ssh_key_success_dialog_title), this.resources.getString(R.string.ssh_key_success_dialog_title),
@ -417,9 +346,8 @@ class UserPreference : AppCompatActivity() {
prefs.edit().putBoolean("use_generated_key", false).apply() prefs.edit().putBoolean("use_generated_key", false).apply()
//delete the public key from generation // Delete the public key from generation
val file = File(filesDir.toString() + "/.ssh_key.pub") File("""$filesDir/.ssh_key.pub""").delete()
file.delete()
setResult(Activity.RESULT_OK) setResult(Activity.RESULT_OK)
finish() finish()
@ -427,9 +355,8 @@ class UserPreference : AppCompatActivity() {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(this.resources.getString(R.string.ssh_key_error_dialog_title)) .setTitle(this.resources.getString(R.string.ssh_key_error_dialog_title))
.setMessage(this.resources.getString(R.string.ssh_key_error_dialog_text) + e.message) .setMessage(this.resources.getString(R.string.ssh_key_error_dialog_text) + e.message)
.setPositiveButton(this.resources.getString(R.string.dialog_ok)) { _, _ -> .setPositiveButton(this.resources.getString(R.string.dialog_ok), null)
// pass .show()
}.show()
} }
} }
EDIT_GIT_INFO -> { EDIT_GIT_INFO -> {
@ -438,62 +365,114 @@ class UserPreference : AppCompatActivity() {
SELECT_GIT_DIRECTORY -> { SELECT_GIT_DIRECTORY -> {
val uri = data.data val uri = data.data
if (uri?.path == Environment.getExternalStorageDirectory().path) { Log.d(TAG, "Selected repository URI is $uri")
// the user wants to use the root of the sdcard as a store... // TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile
val docId = DocumentsContract.getTreeDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val repoPath = "${Environment.getExternalStorageDirectory()}/${split[1]}"
Log.d(TAG, "Selected repository path is $repoPath")
if (Environment.getExternalStorageDirectory().path == repoPath) {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle("SD-Card root selected") .setTitle(getString(R.string.sdcard_root_warning_title))
.setMessage( .setMessage(getString(R.string.sdcard_root_warning_message))
"You have selected the root of your sdcard for the store. " +
"This is extremely dangerous and you will lose your data " +
"as its content will, eventually, be deleted"
)
.setPositiveButton("Remove everything") { _, _ -> .setPositiveButton("Remove everything") { _, _ ->
PreferenceManager.getDefaultSharedPreferences(applicationContext) PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit() .edit()
.putString("git_external_repo", uri?.path) .putString("git_external_repo", uri?.path)
.apply() .apply()
}.setNegativeButton(R.string.dialog_cancel, null).show() }.setNegativeButton(R.string.dialog_cancel, null).show()
} else { }
PreferenceManager.getDefaultSharedPreferences(applicationContext) PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit() .edit()
.putString("git_external_repo", uri?.path) .putString("git_external_repo", repoPath)
.apply() .apply()
} }
}
EXPORT_PASSWORDS -> { EXPORT_PASSWORDS -> {
val uri = data.data val uri = data.data
val repositoryDirectory = PasswordRepository.getRepositoryDirectory(applicationContext)
val fmtOut = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.US) if (uri != null) {
val date = Date() val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri)
val passwordNow = "/password_store_" + fmtOut.format(date)
val targetDirectory = File(uri?.path + passwordNow) if (targetDirectory != null) {
if (repositoryDirectory != null) { exportPasswords(targetDirectory)
try {
FileUtils.copyDirectory(repositoryDirectory, targetDirectory, true)
} catch (e: IOException) {
Log.d("PWD_EXPORT", "Exception happened : " + e.message)
} }
} }
} }
else -> {
}
} }
} }
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { /**
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.applicationContext) * Exports passwords to the given directory.
when (requestCode) { *
REQUEST_EXTERNAL_STORAGE_SSH_KEY -> { * Recursively copies the existing password store to an external directory.
// If request is cancelled, the result arrays are empty. *
if (grantResults.isNotEmpty() && PackageManager.PERMISSION_GRANTED in grantResults) { * @param targetDirectory directory to copy password directory to.
getSshKey(sharedPreferences.getBoolean("use_android_file_picker", false)) */
private fun exportPasswords(targetDirectory: DocumentFile) {
val repositoryDirectory = PasswordRepository.getRepositoryDirectory(applicationContext)
val sourcePassDir = DocumentFile.fromFile(repositoryDirectory)
Log.d(TAG, "Copying ${repositoryDirectory.path} to $targetDirectory")
val dateString = 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")
if (passDir != null) {
copyDirToDir(sourcePassDir, passDir)
}
}
/**
* Copies a password file to a given directory.
*
* Note: this does not preserve last modified time.
*
* @param passwordFile password file to copy.
* @param targetDirectory target directory to copy password.
*/
private fun copyFileToDir(passwordFile: DocumentFile, targetDirectory: DocumentFile) {
val sourceInputStream = contentResolver.openInputStream(passwordFile.uri)
val name = passwordFile.name
val targetPasswordFile = targetDirectory.createFile("application/octet-stream", name!!)
if (targetPasswordFile?.exists() == true) {
val destOutputStream = contentResolver.openOutputStream(targetPasswordFile.uri)
if (destOutputStream != null && sourceInputStream != null) {
sourceInputStream.copyTo(destOutputStream, 1024)
sourceInputStream.close()
destOutputStream.close()
} }
} }
REQUEST_EXTERNAL_STORAGE_EXPORT_PWD -> {
if (grantResults.isNotEmpty() && PackageManager.PERMISSION_GRANTED in grantResults) {
exportPasswords()
} }
/**
* Recursively copies a directory to a destination.
*
* @param sourceDirectory directory to copy from.
* @param sourceDirectory directory to copy to.
*/
private fun copyDirToDir(sourceDirectory: DocumentFile, targetDirectory: DocumentFile) {
sourceDirectory.listFiles().forEach { file ->
if (file.isDirectory) {
// Create new directory and recurse
val newDir = targetDirectory.createDirectory(file.name!!)
copyDirToDir(file, newDir!!)
} else {
copyFileToDir(file, targetDirectory)
} }
} }
} }
@ -505,7 +484,6 @@ class UserPreference : AppCompatActivity() {
private const val SELECT_GIT_DIRECTORY = 4 private const val SELECT_GIT_DIRECTORY = 4
private const val EXPORT_PASSWORDS = 5 private const val EXPORT_PASSWORDS = 5
private const val EDIT_GIT_CONFIG = 6 private const val EDIT_GIT_CONFIG = 6
private const val REQUEST_EXTERNAL_STORAGE_SSH_KEY = 50 private const val TAG = "UserPreference"
private const val REQUEST_EXTERNAL_STORAGE_EXPORT_PWD = 51
} }
} }

View file

@ -256,4 +256,6 @@
<string name="no_ssh_api_provider">No SSH API provider found. Is OpenKeychain installed?</string> <string name="no_ssh_api_provider">No SSH API provider found. Is OpenKeychain installed?</string>
<string name="ssh_api_pending_intent_failed">SSH API pending intent failed</string> <string name="ssh_api_pending_intent_failed">SSH API pending intent failed</string>
<string name="ssh_api_unknown_error">Unknown SSH API Error</string> <string name="ssh_api_unknown_error">Unknown SSH API Error</string>
<string name="sdcard_root_warning_title">SD-Card root selected</string>
<string name="sdcard_root_warning_message">You have selected the root of your sdcard for the store. This is extremely dangerous and you will lose your data as its content will, eventually, be deleted</string>
</resources> </resources>

View file

@ -14,17 +14,6 @@
<item name="background">@color/blue_grey_700</item> <item name="background">@color/blue_grey_700</item>
</style> </style>
<!-- You can also inherit from NNF_BaseTheme.Light -->
<style name="FilePickerTheme" parent="NNF_BaseTheme">
<!-- Set these to match your theme -->
<item name="colorPrimary">@color/blue_grey_500</item>
<item name="colorPrimaryDark">@color/blue_grey_700</item>
<item name="colorAccent">@color/teal_A700</item>
<!-- Need to set this also to style create folder dialog -->
<item name="alertDialogTheme">@style/FilePickerAlertDialogTheme</item>
</style>
<style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert"> <style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
<item name="colorPrimary">@color/blue_grey_500</item> <item name="colorPrimary">@color/blue_grey_500</item>
<item name="colorPrimaryDark">@color/blue_grey_700</item> <item name="colorPrimaryDark">@color/blue_grey_700</item>

View file

@ -28,7 +28,7 @@ tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
resolutionStrategy { resolutionStrategy {
componentSelection { componentSelection {
all { all {
val blacklistedGroups = listOf("com.nononsenseapps", "commons-io", "org.eclipse.jgit") val blacklistedGroups = listOf("commons-io", "org.eclipse.jgit")
val rejected = listOf("alpha", "beta", "rc", "cr", "m", "preview") val rejected = listOf("alpha", "beta", "rc", "cr", "m", "preview")
.map { qualifier -> Regex("(?i).*[.-]$qualifier[.\\d-]*") } .map { qualifier -> Regex("(?i).*[.-]$qualifier[.\\d-]*") }
.any { it.matches(candidate.version) && blacklistedGroups.contains(candidate.group) } .any { it.matches(candidate.version) && blacklistedGroups.contains(candidate.group) }