Merge branch 'Dev-ClayP-master' into develop

This commit is contained in:
J-Jamet
2025-09-01 18:19:11 +02:00
26 changed files with 238 additions and 829 deletions

View File

@@ -1,7 +1,6 @@
name: Bug Report name: Bug Report
description: Report a bug. description: Report a bug.
title: "" labels: ["bug"]
labels: bug
body: body:
- type: markdown - type: markdown
attributes: attributes:

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -1,7 +1,6 @@
name: Feature request name: Feature request
description: Suggest an idea. description: Suggest an idea.
title: "" labels: ["feature"]
labels: feature
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -9,7 +9,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 15 minSdkVersion 19
targetSdkVersion 34 targetSdkVersion 34
versionCode = 139 versionCode = 139
versionName = "4.1.7" versionName = "4.1.7"

View File

@@ -358,78 +358,41 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
val imageView = imageViewRef.get() ?: return val imageView = imageViewRef.get() ?: return
isViewTranslateAnimationRunning = true isViewTranslateAnimationRunning = true
imageView.run {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { val translationY = if (velY > 0) {
imageView.run { originalViewBounds.top + height - top
val translationY = if (velY > 0) {
originalViewBounds.top + height - top
} else {
originalViewBounds.top - height - top
}
animate()
.setDuration(dismissAnimationDuration)
.setInterpolator(dismissAnimationInterpolator)
.translationY(translationY.toFloat())
.setUpdateListener {
val amount = calcTranslationAmount()
changeBackgroundAlpha(amount)
onViewTranslateListener?.onViewTranslate(imageView, amount)
}
.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
}
override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView)
cleanup()
}
override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false
}
override fun onAnimationRepeat(p0: Animator) {
// no op
}
})
}
} else {
ObjectAnimator.ofFloat(imageView, View.TRANSLATION_Y, if (velY > 0) {
originalViewBounds.top + imageView.height - imageView.top
} else { } else {
originalViewBounds.top - imageView.height - imageView.top originalViewBounds.top - height - top
}.toFloat()).apply {
duration = dismissAnimationDuration
interpolator = dismissAnimationInterpolator
addUpdateListener {
val amount = calcTranslationAmount()
changeBackgroundAlpha(amount)
onViewTranslateListener?.onViewTranslate(imageView, amount)
}
addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
// no op
}
override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView)
cleanup()
}
override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false
}
override fun onAnimationRepeat(p0: Animator) {
// no op
}
})
start()
} }
animate()
.setDuration(dismissAnimationDuration)
.setInterpolator(dismissAnimationInterpolator)
.translationY(translationY.toFloat())
.setUpdateListener {
val amount = calcTranslationAmount()
changeBackgroundAlpha(amount)
onViewTranslateListener?.onViewTranslate(imageView, amount)
}
.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
}
override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView)
cleanup()
}
override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false
}
override fun onAnimationRepeat(p0: Animator) {
// no op
}
})
} }
} }
@@ -657,137 +620,76 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
private fun restoreViewTransform() { private fun restoreViewTransform() {
val imageView = imageViewRef.get() ?: return val imageView = imageViewRef.get() ?: return
imageView.run {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { animate()
imageView.run { .setDuration(restoreAnimationDuration)
animate() .setInterpolator(restoreAnimationInterpolator)
.setDuration(restoreAnimationDuration) .translationY((originalViewBounds.top - top).toFloat())
.setInterpolator(restoreAnimationInterpolator) .setUpdateListener {
.translationY((originalViewBounds.top - top).toFloat()) val amount = calcTranslationAmount()
.setUpdateListener { changeBackgroundAlpha(amount)
val amount = calcTranslationAmount() onViewTranslateListener?.onViewTranslate(this, amount)
changeBackgroundAlpha(amount) }
onViewTranslateListener?.onViewTranslate(this, amount) .setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
// no op
} }
.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
// no op
}
override fun onAnimationEnd(p0: Animator) { override fun onAnimationEnd(p0: Animator) {
onViewTranslateListener?.onRestore(imageView) onViewTranslateListener?.onRestore(imageView)
} }
override fun onAnimationCancel(p0: Animator) { override fun onAnimationCancel(p0: Animator) {
// no op // no op
} }
override fun onAnimationRepeat(p0: Animator) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
}
} else {
ObjectAnimator.ofFloat(imageView, View.TRANSLATION_Y, (originalViewBounds.top - imageView.top).toFloat()).apply {
duration = restoreAnimationDuration
interpolator = restoreAnimationInterpolator
addUpdateListener {
val amount = calcTranslationAmount()
changeBackgroundAlpha(amount)
onViewTranslateListener?.onViewTranslate(imageView, amount)
}
addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
// no op
}
override fun onAnimationEnd(p0: Animator) {
onViewTranslateListener?.onRestore(imageView)
}
override fun onAnimationCancel(p0: Animator) {
// no op
}
override fun onAnimationRepeat(p0: Animator) {
// no op
}
})
start()
}
} }
} }
private fun startDragToDismissAnimation() { private fun startDragToDismissAnimation() {
val imageView = imageViewRef.get() ?: return val imageView = imageViewRef.get() ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { imageView.run {
imageView.run { val translationY = if (y - initialY > 0) {
val translationY = if (y - initialY > 0) { originalViewBounds.top + height - top
originalViewBounds.top + height - top } else {
} else { originalViewBounds.top - height - top
originalViewBounds.top - height - top }
} animate()
animate() .setDuration(dismissAnimationDuration)
.setDuration(dismissAnimationDuration) .setInterpolator(AccelerateDecelerateInterpolator())
.setInterpolator(AccelerateDecelerateInterpolator()) .translationY(translationY.toFloat())
.translationY(translationY.toFloat()) .setUpdateListener {
.setUpdateListener { val amount = calcTranslationAmount()
val amount = calcTranslationAmount() changeBackgroundAlpha(amount)
changeBackgroundAlpha(amount) onViewTranslateListener?.onViewTranslate(this, amount)
onViewTranslateListener?.onViewTranslate(this, amount) }
.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
isViewTranslateAnimationRunning = true
} }
.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
isViewTranslateAnimationRunning = true
}
override fun onAnimationEnd(p0: Animator) { override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView) onViewTranslateListener?.onDismiss(imageView)
cleanup() cleanup()
} }
override fun onAnimationCancel(p0: Animator) { override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
} }
override fun onAnimationRepeat(p0: Animator) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
}
} else {
ObjectAnimator.ofFloat(imageView, View.TRANSLATION_Y, imageView.translationY).apply {
duration = dismissAnimationDuration
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener {
val amount = calcTranslationAmount()
changeBackgroundAlpha(amount)
onViewTranslateListener?.onViewTranslate(imageView, amount)
}
addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {
isViewTranslateAnimationRunning = true
}
override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView)
cleanup()
}
override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false
}
override fun onAnimationRepeat(p0: Animator) {
// no op
}
})
start()
}
} }
} }
private fun processFlingToDismiss(velocityY: Float) { private fun processFlingToDismiss(velocityY: Float) {

View File

@@ -78,9 +78,8 @@ class AboutActivity : StylishActivity() {
movementMethod = LinkMovementMethod.getInstance() movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_licence, DateTime().year), text = HtmlCompat.fromHtml(getString(R.string.html_about_licence, DateTime().year),
HtmlCompat.FROM_HTML_MODE_LEGACY) HtmlCompat.FROM_HTML_MODE_LEGACY)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { textDirection = View.TEXT_DIRECTION_ANY_RTL
textDirection = View.TEXT_DIRECTION_ANY_RTL
}
} }
findViewById<TextView>(R.id.activity_about_privacy_text).apply { findViewById<TextView>(R.id.activity_about_privacy_text).apply {

View File

@@ -604,16 +604,12 @@ class EntryEditActivity : DatabaseLockActivity(),
isVisible = isEnabled isVisible = isEnabled
} }
menu?.findItem(R.id.menu_add_attachment)?.apply { menu?.findItem(R.id.menu_add_attachment)?.apply {
// Attachment not compatible below KitKat
isEnabled = !mIsTemplate isEnabled = !mIsTemplate
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
isVisible = isEnabled isVisible = isEnabled
} }
menu?.findItem(R.id.menu_add_otp)?.apply { menu?.findItem(R.id.menu_add_otp)?.apply {
// OTP not compatible below KitKat
isEnabled = mAllowOTP isEnabled = mAllowOTP
&& !mIsTemplate && !mIsTemplate
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
isVisible = isEnabled isVisible = isEnabled
} }
return super.onPrepareOptionsMenu(menu) return super.onPrepareOptionsMenu(menu)

View File

@@ -70,7 +70,6 @@ import com.kunzisoft.keepass.utils.MagikeyboardUtil
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil.isContributingUser import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import com.kunzisoft.keepass.utils.UriUtil.openUrl import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.utils.allowCreateDocumentByStorageAccessFramework
import com.kunzisoft.keepass.utils.getParcelableCompat import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
@@ -330,13 +329,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
// Show open and create button or special mode // Show open and create button or special mode
when (mSpecialMode) { when (mSpecialMode) {
SpecialMode.DEFAULT -> { SpecialMode.DEFAULT -> {
if (packageManager.allowCreateDocumentByStorageAccessFramework()) { createDatabaseButtonView?.visibility = View.VISIBLE
// There is an activity which can handle this intent.
createDatabaseButtonView?.visibility = View.VISIBLE
} else{
// No Activity found that can handle this intent.
createDatabaseButtonView?.visibility = View.GONE
}
} }
else -> { else -> {
// Disable create button if in selection mode or request for autofill // Disable create button if in selection mode or request for autofill

View File

@@ -530,9 +530,8 @@ class NodesAdapter (
holder?.otpToken?.apply { holder?.otpToken?.apply {
text = otpElement?.tokenString text = otpElement?.tokenString
setTextSize(mTextSizeUnit, mOtpTokenTextDefaultDimension, mPrefSizeMultiplier) setTextSize(mTextSizeUnit, mOtpTokenTextDefaultDimension, mPrefSizeMultiplier)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { textDirection = View.TEXT_DIRECTION_LTR
textDirection = View.TEXT_DIRECTION_LTR
}
} }
holder?.otpContainer?.setOnClickListener { holder?.otpContainer?.setOnClickListener {
otpElement?.token?.let { token -> otpElement?.token?.let { token ->

View File

@@ -38,7 +38,6 @@ class App : MultiDexApplication() {
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver) ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver)
Stylish.load(this) Stylish.load(this)
PRNGFixes.apply()
} }
} }

View File

@@ -1,399 +0,0 @@
package com.kunzisoft.keepass.app;
/*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will Google be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, as long as the origin is not misrepresented.
*/
import android.os.Build;
import android.os.Process;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
import java.util.Locale;
/**
* Fixes for the output of the default PRNG having low entropy.
*
* The fixes need to be applied via {@link #apply()} before any use of Java
* Cryptography Architecture primitives. A good place to invoke them is in the
* application's {@code onCreate}.
*/
public final class PRNGFixes {
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
getBuildFingerprintAndDeviceSerial();
/** Hidden constructor to prevent instantiation. */
private PRNGFixes() {}
/**
* Applies all fixes.
*
* @throws SecurityException if a fix is needed but could not be applied.
*/
public static void apply() {
try {
if (supportedOnThisDevice()) {
applyOpenSSLFix();
installLinuxPRNGSecureRandom();
}
} catch (Exception e) {
// Do nothing, do the best we can to implement the workaround
}
}
private static boolean supportedOnThisDevice() {
// Blacklist on samsung devices
if (Build.MANUFACTURER.toLowerCase(Locale.ENGLISH).contains("samsung")) {
return false;
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
return false;
}
if (onSELinuxEnforce()) {
return false;
}
File urandom = new File("/dev/urandom");
// Test permissions
if ( !(urandom.canRead() && urandom.canWrite()) ) {
return false;
}
// Test actually writing to urandom
try {
FileOutputStream fos = new FileOutputStream(urandom);
fos.write(0);
} catch (Exception e) {
return false;
}
return true;
}
private static boolean onSELinuxEnforce() {
try {
ProcessBuilder builder = new ProcessBuilder("getenforce");
builder.redirectErrorStream(true);
java.lang.Process process = builder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
String output = reader.readLine();
if (output == null) {
return false;
}
return output.toLowerCase(Locale.US).startsWith("enforcing");
} catch (Exception e) {
return false;
}
}
/**
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
* fix is not needed.
*
* @throws SecurityException if the fix is needed but could not be applied.
*/
private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|| (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)) {
// No need to apply the fix
return;
}
try {
// Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) {
throw new IOException(
"Unexpected number of bytes read from Linux PRNG: "
+ bytesRead);
}
} catch (Exception e) {
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
}
}
/**
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
* default. Does nothing if the implementation is already the default or if
* there is not need to install the implementation.
*
* @throws SecurityException if the fix is needed but could not be applied.
*/
private static void installLinuxPRNGSecureRandom()
throws SecurityException {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
// No need to apply the fix
return;
}
// Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed.
Provider[] secureRandomProviders =
Security.getProviders("SecureRandom.SHA1PRNG");
if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1)
|| (!LinuxPRNGSecureRandomProvider.class.equals(
secureRandomProviders[0].getClass()))) {
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
}
// Assert that new SecureRandom() and
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
// by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom();
if (!LinuxPRNGSecureRandomProvider.class.equals(
rng1.getProvider().getClass())) {
throw new SecurityException(
"new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass());
}
SecureRandom rng2;
try {
rng2 = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException("SHA1PRNG not available", e);
}
if (!LinuxPRNGSecureRandomProvider.class.equals(
rng2.getProvider().getClass())) {
throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ " Provider: " + rng2.getProvider().getClass());
}
}
/**
* {@code Provider} of {@code SecureRandom} engines which pass through
* all requests to the Linux PRNG.
*/
private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG",
1.0,
"A Linux-specific random number provider that uses"
+ " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need to
// prevent them from getting the default implementation whose output
// may have low entropy.
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
/**
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
* ({@code /dev/urandom}).
*/
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
/*
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
* are passed through to the Linux PRNG (/dev/urandom). Instances of
* this class seed themselves by mixing in the current time, PID, UID,
* build fingerprint, and hardware serial number (where available) into
* Linux PRNG.
*
* Concurrency: Read requests to the underlying Linux PRNG are
* serialized (on sLock) to ensure that multiple threads do not get
* duplicated PRNG output.
*/
private static final File URANDOM_FILE = new File("/dev/urandom");
private static final Object sLock = new Object();
/**
* Input stream for reading from Linux PRNG or {@code null} if not yet
* opened.
*
* @GuardedBy("sLock")
*/
private static DataInputStream sUrandomIn;
/**
* Output stream for writing to Linux PRNG or {@code null} if not yet
* opened.
*
* @GuardedBy("sLock")
*/
private static OutputStream sUrandomOut;
/**
* Whether this engine instance has been seeded. This is needed because
* each instance needs to seed itself if the client does not explicitly
* seed it.
*/
private boolean mSeeded;
@Override
protected void engineSetSeed(byte[] bytes) {
try {
OutputStream out;
synchronized (sLock) {
out = getUrandomOutputStream();
}
out.write(bytes);
out.flush();
mSeeded = true;
} catch (IOException e) {
throw new SecurityException(
"Failed to mix seed into " + URANDOM_FILE, e);
}
}
@Override
protected void engineNextBytes(byte[] bytes) {
if (!mSeeded) {
// Mix in the device- and invocation-specific seed.
engineSetSeed(generateSeed());
}
try {
DataInputStream in;
synchronized (sLock) {
in = getUrandomInputStream();
}
synchronized (in) {
in.readFully(bytes);
}
} catch (IOException e) {
throw new SecurityException(
"Failed to read from " + URANDOM_FILE, e);
}
}
@Override
protected byte[] engineGenerateSeed(int size) {
byte[] seed = new byte[size];
engineNextBytes(seed);
return seed;
}
private DataInputStream getUrandomInputStream() {
synchronized (sLock) {
if (sUrandomIn == null) {
// NOTE: Consider inserting a BufferedInputStream between
// DataInputStream and FileInputStream if you need higher
// PRNG output performance and can live with future PRNG
// output being pulled into this process prematurely.
try {
sUrandomIn = new DataInputStream(
new FileInputStream(URANDOM_FILE));
} catch (IOException e) {
throw new SecurityException("Failed to open "
+ URANDOM_FILE + " for reading", e);
}
}
return sUrandomIn;
}
}
private OutputStream getUrandomOutputStream() {
synchronized (sLock) {
if (sUrandomOut == null) {
try {
sUrandomOut = new FileOutputStream(URANDOM_FILE);
} catch (IOException e) {
throw new SecurityException("Failed to open "
+ URANDOM_FILE + " for writing", e);
}
}
return sUrandomOut;
}
}
}
/**
* Generates a device- and invocation-specific seed to be mixed into the
* Linux PRNG.
*/
private static byte[] generateSeed() {
try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut =
new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid());
seedBufferOut.writeInt(Process.myUid());
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
seedBufferOut.close();
return seedBuffer.toByteArray();
} catch (IOException e) {
throw new SecurityException("Failed to generate seed", e);
}
}
/**
* Gets the hardware serial number of this device.
*
* @return serial number or {@code null} if not available.
*/
private static String getDeviceSerialNumber() {
// We're using the Reflection API because Build.SERIAL is only available
// since API Level 9 (Gingerbread, Android 2.3).
try {
return (String) Build.class.getField("SERIAL").get(null);
} catch (Exception ignored) {
return null;
}
}
private static byte[] getBuildFingerprintAndDeviceSerial() {
StringBuilder result = new StringBuilder();
String fingerprint = Build.FINGERPRINT;
if (fingerprint != null) {
result.append(fingerprint);
}
String serial = getDeviceSerialNumber();
if (serial != null) {
result.append(serial);
}
try {
return result.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported");
}
}
}

View File

@@ -65,27 +65,19 @@ object TimeoutHelper {
(context.applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager?)?.let { alarmManager -> (context.applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager?)?.let { alarmManager ->
val triggerTime = System.currentTimeMillis() + timeout val triggerTime = System.currentTimeMillis() + timeout
Log.d(TAG, "TimeoutHelper start") Log.d(TAG, "TimeoutHelper start")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
&& !alarmManager.canScheduleExactAlarms()) {
alarmManager.set(
AlarmManager.RTC,
triggerTime,
getLockPendingIntent(context)
)
} else {
alarmManager.setExact(
AlarmManager.RTC,
triggerTime,
getLockPendingIntent(context)
)
}
} else {
alarmManager.set( alarmManager.set(
AlarmManager.RTC, AlarmManager.RTC,
triggerTime, triggerTime,
getLockPendingIntent(context) getLockPendingIntent(context)
) )
} else {
alarmManager.setExact(
AlarmManager.RTC,
triggerTime,
getLockPendingIntent(context)
)
} }
} }
} }

View File

@@ -77,27 +77,19 @@ class LockReceiver(private var lockAction: () -> Unit) : BroadcastReceiver() {
// Launch the effective action after a small time // Launch the effective action after a small time
val first: Long = System.currentTimeMillis() + context.getString(R.string.timeout_screen_off).toLong() val first: Long = System.currentTimeMillis() + context.getString(R.string.timeout_screen_off).toLong()
(context.getSystemService(ALARM_SERVICE) as AlarmManager?)?.let { alarmManager -> (context.getSystemService(ALARM_SERVICE) as AlarmManager?)?.let { alarmManager ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
&& !alarmManager.canScheduleExactAlarms()) {
alarmManager.set(
AlarmManager.RTC_WAKEUP,
first,
lockPendingIntent
)
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
first,
lockPendingIntent
)
}
} else {
alarmManager.set( alarmManager.set(
AlarmManager.RTC_WAKEUP, AlarmManager.RTC_WAKEUP,
first, first,
lockPendingIntent lockPendingIntent
) )
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
first,
lockPendingIntent
)
} }
} }
} else { } else {

View File

@@ -67,55 +67,53 @@ object UriUtil {
readOnly: Boolean) { readOnly: Boolean) {
try { try {
// try to persist read and write permissions // try to persist read and write permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { contentResolver?.apply {
contentResolver?.apply { var readPermissionAllowed = false
var readPermissionAllowed = false var writePermissionAllowed = false
var writePermissionAllowed = false // Check current permissions allowed
// Check current permissions allowed persistedUriPermissions.find { uriPermission ->
persistedUriPermissions.find { uriPermission -> uriPermission.uri == uri
uriPermission.uri == uri }?.let { uriPermission ->
}?.let { uriPermission -> Log.d(TAG, "Check URI permission : $uriPermission")
Log.d(TAG, "Check URI permission : $uriPermission") if (uriPermission.isReadPermission) {
if (uriPermission.isReadPermission) { readPermissionAllowed = true
readPermissionAllowed = true
}
if (uriPermission.isWritePermission) {
writePermissionAllowed = true
}
} }
if (uriPermission.isWritePermission) {
// Release permission writePermissionAllowed = true
if (release) {
if (writePermissionAllowed) {
Log.d(TAG, "Release write permission : $uri")
val removeFlags: Int = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
releasePersistableUriPermission(uri, removeFlags)
}
if (readPermissionAllowed) {
Log.d(TAG, "Release read permission $uri")
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION
releasePersistableUriPermission(uri, takeFlags)
}
} }
}
// Take missing permission // Release permission
if (!readPermissionAllowed) { if (release) {
Log.d(TAG, "Take read permission $uri") if (writePermissionAllowed) {
Log.d(TAG, "Release write permission : $uri")
val removeFlags: Int = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
releasePersistableUriPermission(uri, removeFlags)
}
if (readPermissionAllowed) {
Log.d(TAG, "Release read permission $uri")
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION
takePersistableUriPermission(uri, takeFlags) releasePersistableUriPermission(uri, takeFlags)
} }
if (readOnly) { }
if (writePermissionAllowed) {
Log.d(TAG, "Release write permission $uri") // Take missing permission
val removeFlags: Int = Intent.FLAG_GRANT_WRITE_URI_PERMISSION if (!readPermissionAllowed) {
releasePersistableUriPermission(uri, removeFlags) Log.d(TAG, "Take read permission $uri")
} val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION
} else { takePersistableUriPermission(uri, takeFlags)
if (!writePermissionAllowed) { }
Log.d(TAG, "Take write permission $uri") if (readOnly) {
val takeFlags: Int = Intent.FLAG_GRANT_WRITE_URI_PERMISSION if (writePermissionAllowed) {
takePersistableUriPermission(uri, takeFlags) Log.d(TAG, "Release write permission $uri")
} val removeFlags: Int = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
releasePersistableUriPermission(uri, removeFlags)
}
} else {
if (!writePermissionAllowed) {
Log.d(TAG, "Take write permission $uri")
val takeFlags: Int = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
takePersistableUriPermission(uri, takeFlags)
} }
} }
} }
@@ -140,42 +138,38 @@ object UriUtil {
} }
fun Context.releaseAllUnnecessaryPermissionUris() { fun Context.releaseAllUnnecessaryPermissionUris() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { applicationContext?.let { appContext ->
applicationContext?.let { appContext -> val fileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(appContext)
val fileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(appContext) fileDatabaseHistoryAction.getDatabaseFileList { databaseFileList ->
fileDatabaseHistoryAction.getDatabaseFileList { databaseFileList -> val listToNotRemove = mutableListOf<Uri>()
val listToNotRemove = mutableListOf<Uri>() databaseFileList.forEach {
databaseFileList.forEach { it.databaseUri?.let { databaseUri ->
it.databaseUri?.let { databaseUri -> listToNotRemove.add(databaseUri)
listToNotRemove.add(databaseUri)
}
it.keyFileUri?.let { keyFileUri ->
listToNotRemove.add(keyFileUri)
}
} }
// Remove URI permission for not database files it.keyFileUri?.let { keyFileUri ->
val resolver = appContext.contentResolver listToNotRemove.add(keyFileUri)
resolver.persistedUriPermissions.forEach { uriPermission ->
val uri = uriPermission.uri
if (!listToNotRemove.contains(uri))
resolver.releaseUriPermission(uri)
} }
} }
// Remove URI permission for not database files
val resolver = appContext.contentResolver
resolver.persistedUriPermissions.forEach { uriPermission ->
val uri = uriPermission.uri
if (!listToNotRemove.contains(uri))
resolver.releaseUriPermission(uri)
}
} }
} }
} }
fun Intent.getUri(key: String): Uri? { fun Intent.getUri(key: String): Uri? {
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { val clipData = this.clipData
val clipData = this.clipData if (clipData != null) {
if (clipData != null) { if (clipData.description.label == key) {
if (clipData.description.label == key) { if (clipData.itemCount == 1) {
if (clipData.itemCount == 1) { val clipItem = clipData.getItemAt(0)
val clipItem = clipData.getItemAt(0) if (clipItem != null) {
if (clipItem != null) { return clipItem.uri
return clipItem.uri
}
} }
} }
} }

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.view package com.kunzisoft.keepass.view
import android.content.Context import android.content.Context
import android.os.Build
import android.text.Spannable import android.text.Spannable
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
@@ -104,18 +103,14 @@ class PasswordTextEditFieldView @JvmOverloads constructor(context: Context,
id = passwordProgressViewId id = passwordProgressViewId
layoutParams = (layoutParams as LayoutParams?)?.also { layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(LEFT_OF, actionImageButtonId) it.addRule(LEFT_OF, actionImageButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.addRule(START_OF, actionImageButtonId)
it.addRule(START_OF, actionImageButtonId)
}
} }
} }
mPasswordEntropyView.apply { mPasswordEntropyView.apply {
id = passwordEntropyViewId id = passwordEntropyViewId
layoutParams = (layoutParams as LayoutParams?)?.also { layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(ALIGN_RIGHT, passwordProgressViewId) it.addRule(ALIGN_RIGHT, passwordProgressViewId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.addRule(ALIGN_END, passwordProgressViewId)
it.addRule(ALIGN_END, passwordProgressViewId)
}
} }
} }
} }

View File

@@ -123,13 +123,11 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
setMaxChars(templateAttribute.options.getNumberChars()) setMaxChars(templateAttribute.options.getNumberChars())
setMaxLines(templateAttribute.options.getNumberLines()) setMaxLines(templateAttribute.options.getNumberLines())
setActionClick(templateAttribute, field, this) setActionClick(templateAttribute, field, this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (field.protectedValue.isProtected) {
if (field.protectedValue.isProtected) { textDirection = TEXT_DIRECTION_LTR
textDirection = TEXT_DIRECTION_LTR }
} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO
importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO
}
} }
} }
} }

View File

@@ -63,9 +63,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
// Here the value is often empty // Here the value is often empty
if (field.protectedValue.isProtected) { if (field.protectedValue.isProtected) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { textDirection = TEXT_DIRECTION_LTR
textDirection = TEXT_DIRECTION_LTR
}
if (mFirstTimeAskAllowCopyProtectedFields) { if (mFirstTimeAskAllowCopyProtectedFields) {
setCopyButtonState(TextFieldView.ButtonState.DEACTIVATE) setCopyButtonState(TextFieldView.ButtonState.DEACTIVATE)
setCopyButtonClickListener { _, _ -> setCopyButtonClickListener { _, _ ->
@@ -179,9 +177,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
otpElement.type.name, otpElement.type.name,
ProtectedString(false, otpElement.token))) ProtectedString(false, otpElement.token)))
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { textDirection = TEXT_DIRECTION_LTR
textDirection = TEXT_DIRECTION_LTR
}
mLastOtpTokenView = this mLastOtpTokenView = this
mOtpRunnable = Runnable { mOtpRunnable = Runnable {
if (otpElement.shouldRefreshToken()) { if (otpElement.shouldRefreshToken()) {

View File

@@ -51,9 +51,7 @@ open class TextEditFieldView @JvmOverloads constructor(context: Context,
imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
}
maxLines = 1 maxLines = 1
} }
private var actionImageButton = AppCompatImageButton( private var actionImageButton = AppCompatImageButton(
@@ -70,11 +68,9 @@ open class TextEditFieldView @JvmOverloads constructor(context: Context,
resources.displayMetrics resources.displayMetrics
).toInt() ).toInt()
it.addRule(ALIGN_PARENT_RIGHT) it.addRule(ALIGN_PARENT_RIGHT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.addRule(ALIGN_PARENT_END)
it.addRule(ALIGN_PARENT_END)
}
} }
visibility = View.GONE visibility = GONE
contentDescription = context.getString(R.string.menu_edit) contentDescription = context.getString(R.string.menu_edit)
} }
@@ -91,9 +87,7 @@ open class TextEditFieldView @JvmOverloads constructor(context: Context,
id = labelViewId id = labelViewId
layoutParams = (layoutParams as LayoutParams?)?.also { layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(LEFT_OF, actionImageButtonId) it.addRule(LEFT_OF, actionImageButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.addRule(START_OF, actionImageButtonId)
it.addRule(START_OF, actionImageButtonId)
}
} }
} }
valueView.apply { valueView.apply {
@@ -192,7 +186,7 @@ open class TextEditFieldView @JvmOverloads constructor(context: Context,
actionImageButton.setImageDrawable(ContextCompat.getDrawable(context, it)) actionImageButton.setImageDrawable(ContextCompat.getDrawable(context, it))
} }
actionImageButton.setOnClickListener(onActionClickListener) actionImageButton.setOnClickListener(onActionClickListener)
actionImageButton.visibility = if (onActionClickListener == null) View.GONE else View.VISIBLE actionImageButton.visibility = if (onActionClickListener == null) GONE else VISIBLE
} }
override var isFieldVisible: Boolean override var isFieldVisible: Boolean

View File

@@ -63,13 +63,12 @@ open class TextFieldView @JvmOverloads constructor(context: Context,
4f, 4f,
resources.displayMetrics resources.displayMetrics
).toInt() ).toInt()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.marginStart = TypedValue.applyDimension(
it.marginStart = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP,
TypedValue.COMPLEX_UNIT_DIP, 4f,
4f, resources.displayMetrics
resources.displayMetrics ).toInt()
).toInt()
}
} }
} }
protected val valueView = AppCompatTextView(context).apply { protected val valueView = AppCompatTextView(context).apply {
@@ -88,13 +87,11 @@ open class TextFieldView @JvmOverloads constructor(context: Context,
8f, 8f,
resources.displayMetrics resources.displayMetrics
).toInt() ).toInt()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.marginStart = TypedValue.applyDimension(
it.marginStart = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP,
TypedValue.COMPLEX_UNIT_DIP, 8f,
8f, resources.displayMetrics
resources.displayMetrics ).toInt()
).toInt()
}
} }
setTextIsSelectable(true) setTextIsSelectable(true)
} }
@@ -128,9 +125,7 @@ open class TextFieldView @JvmOverloads constructor(context: Context,
id = copyButtonId id = copyButtonId
layoutParams = (layoutParams as LayoutParams?)?.also { layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(ALIGN_PARENT_RIGHT) it.addRule(ALIGN_PARENT_RIGHT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.addRule(ALIGN_PARENT_END)
it.addRule(ALIGN_PARENT_END)
}
} }
} }
showButton.apply { showButton.apply {
@@ -138,14 +133,14 @@ open class TextFieldView @JvmOverloads constructor(context: Context,
layoutParams = (layoutParams as LayoutParams?)?.also { layoutParams = (layoutParams as LayoutParams?)?.also {
if (copyButton.isVisible) { if (copyButton.isVisible) {
it.addRule(LEFT_OF, copyButtonId) it.addRule(LEFT_OF, copyButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it.addRule(START_OF, copyButtonId) it.addRule(START_OF, copyButtonId)
}
} else { } else {
it.addRule(ALIGN_PARENT_RIGHT) it.addRule(ALIGN_PARENT_RIGHT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it.addRule(ALIGN_PARENT_END) it.addRule(ALIGN_PARENT_END)
}
} }
} }
} }
@@ -153,18 +148,14 @@ open class TextFieldView @JvmOverloads constructor(context: Context,
id = labelViewId id = labelViewId
layoutParams = (layoutParams as LayoutParams?)?.also { layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(LEFT_OF, showButtonId) it.addRule(LEFT_OF, showButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.addRule(START_OF, showButtonId)
it.addRule(START_OF, showButtonId)
}
} }
} }
valueView.apply { valueView.apply {
id = valueViewId id = valueViewId
layoutParams = (layoutParams as LayoutParams?)?.also { layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(LEFT_OF, showButtonId) it.addRule(LEFT_OF, showButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.addRule(START_OF, showButtonId)
it.addRule(START_OF, showButtonId)
}
it.addRule(BELOW, labelViewId) it.addRule(BELOW, labelViewId)
} }
} }

View File

@@ -10,7 +10,12 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.* import android.widget.AdapterView
import android.widget.BaseAdapter
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.Spinner
import android.widget.TextView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -51,9 +56,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
}
val drawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_down_white_24dp) val drawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_down_white_24dp)
?.apply { ?.apply {
mutate().colorFilter = BlendModeColorFilterCompat mutate().colorFilter = BlendModeColorFilterCompat
@@ -65,14 +68,12 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
drawable, drawable,
null null
) )
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { setCompoundDrawablesRelativeWithIntrinsicBounds(
setCompoundDrawablesRelativeWithIntrinsicBounds( null,
null, null,
null, drawable,
drawable, null
null )
)
}
isFocusable = false isFocusable = false
inputType = InputType.TYPE_NULL inputType = InputType.TYPE_NULL
maxLines = 1 maxLines = 1
@@ -94,9 +95,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
resources.displayMetrics resources.displayMetrics
).toInt() ).toInt()
it.addRule(ALIGN_PARENT_RIGHT) it.addRule(ALIGN_PARENT_RIGHT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it.addRule(ALIGN_PARENT_END)
it.addRule(ALIGN_PARENT_END)
}
} }
visibility = View.GONE visibility = View.GONE
contentDescription = context.getString(R.string.menu_edit) contentDescription = context.getString(R.string.menu_edit)
@@ -132,18 +131,14 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
id = labelViewId id = labelViewId
layoutParams = (layoutParams as LayoutParams?).also { layoutParams = (layoutParams as LayoutParams?).also {
it?.addRule(LEFT_OF, actionImageButtonId) it?.addRule(LEFT_OF, actionImageButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it?.addRule(START_OF, actionImageButtonId)
it?.addRule(START_OF, actionImageButtonId)
}
} }
} }
valueSpinnerView.apply { valueSpinnerView.apply {
id = valueViewId id = valueViewId
layoutParams = (layoutParams as LayoutParams?).also { layoutParams = (layoutParams as LayoutParams?).also {
it?.addRule(LEFT_OF, actionImageButtonId) it?.addRule(LEFT_OF, actionImageButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { it?.addRule(START_OF, actionImageButtonId)
it?.addRule(START_OF, actionImageButtonId)
}
it?.addRule(BELOW, labelViewId) it?.addRule(BELOW, labelViewId)
} }
} }

View File

@@ -234,11 +234,7 @@ fun View.updateLockPaddingStart() {
R.dimen.hidden_lock_button_size R.dimen.hidden_lock_button_size
} }
).let { lockPadding -> ).let { lockPadding ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { updatePaddingRelative(lockPadding)
updatePaddingRelative(lockPadding)
} else {
updatePadding(lockPadding)
}
} }
} }

View File

@@ -122,8 +122,7 @@
android:inputType="textPassword" android:inputType="textPassword"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:importantForAutofill="no" android:importantForAutofill="no"
android:hint="@string/otp_secret" android:hint="@string/otp_secret" />
tools:targetApi="jelly_bean" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>
@@ -178,8 +177,7 @@
tools:text="30" tools:text="30"
android:maxLength="3" android:maxLength="3"
android:digits="0123456789" android:digits="0123456789"
android:imeOptions="actionNext" android:imeOptions="actionNext" />
tools:targetApi="jelly_bean" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/setup_otp_counter_label" android:id="@+id/setup_otp_counter_label"
@@ -198,8 +196,7 @@
android:importantForAutofill="no" android:importantForAutofill="no"
android:hint="@string/otp_counter" android:hint="@string/otp_counter"
tools:text="1" tools:text="1"
android:imeOptions="actionNext" android:imeOptions="actionNext" />
tools:targetApi="jelly_bean" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<androidx.constraintlayout.widget.Guideline <androidx.constraintlayout.widget.Guideline
@@ -228,8 +225,7 @@
tools:text="6" tools:text="6"
android:maxLength="2" android:maxLength="2"
android:digits="0123456789" android:digits="0123456789"
android:imeOptions="actionNext" android:imeOptions="actionNext" />
tools:targetApi="jelly_bean" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -237,4 +233,4 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -65,7 +65,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right_white_24dp" android:src="@drawable/ic_arrow_right_white_24dp"
app:tint="?attr/colorOnSurface" app:tint="?attr/colorOnSurface"
android:importantForAccessibility="no" android:importantForAccessibility="no" />
tools:targetApi="jelly_bean" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@@ -9,7 +9,7 @@ android {
ndkVersion "21.4.7075529" ndkVersion "21.4.7075529"
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion 19
targetSdkVersion 34 targetSdkVersion 34
multiDexEnabled true multiDexEnabled true

View File

@@ -6,7 +6,7 @@ android {
compileSdkVersion 34 compileSdkVersion 34
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion 19
targetSdk 34 targetSdk 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -148,23 +148,6 @@ fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int = 0): Pa
@Suppress("DEPRECATION") getPackageInfo(packageName, flags) @Suppress("DEPRECATION") getPackageInfo(packageName, flags)
} }
@SuppressLint("InlinedApi")
fun PackageManager.allowCreateDocumentByStorageAccessFramework(): Boolean {
return when {
// To check if a custom file manager can manage the ACTION_CREATE_DOCUMENT
// queries filter is in Manifest
Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT -> {
queryIntentActivitiesCompat(
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
}, PackageManager.MATCH_DEFAULT_ONLY
).isNotEmpty()
}
else -> true
}
}
@SuppressLint("QueryPermissionsNeeded") @SuppressLint("QueryPermissionsNeeded")
private fun PackageManager.queryIntentActivitiesCompat(intent: Intent, flags: Int): List<ResolveInfo> { private fun PackageManager.queryIntentActivitiesCompat(intent: Intent, flags: Int): List<ResolveInfo> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {