mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Remove JELLY_BEAN_MR1 conditions and unused PRNGFixes
This commit is contained in:
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user