session-android/libsession/src/main/java/org/session/libsession/utilities/Util.kt

384 lines
11 KiB
Kotlin

package org.session.libsession.utilities
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.provider.Telephony
import android.text.Spannable
import android.text.SpannableString
import android.text.TextUtils
import android.text.style.StyleSpan
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Base64
import java.io.*
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
import java.text.DecimalFormat
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ExecutorService
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.math.min
object Util {
@Volatile
private var handler: Handler? = null
fun isMainThread(): Boolean {
return Looper.myLooper() == Looper.getMainLooper()
}
@JvmStatic
fun assertMainThread() {
if (!isMainThread()) {
throw java.lang.AssertionError("Main-thread assertion failed.")
}
}
@JvmStatic
@Throws(IOException::class)
fun copy(`in`: InputStream, out: OutputStream?): Long {
val buffer = ByteArray(8192)
var read: Int
var total: Long = 0
while (`in`.read(buffer).also { read = it } != -1) {
out?.write(buffer, 0, read)
total += read.toLong()
}
`in`.close()
out?.close()
return total
}
@JvmStatic
fun uri(uri: String?): Uri? {
return if (uri == null) null else Uri.parse(uri)
}
@JvmStatic
fun runOnMain(runnable: Runnable) {
if (isMainThread()) runnable.run()
else getHandler()?.post(runnable)
}
@JvmStatic
fun runOnMainDelayed(runnable: Runnable, delayMillis: Long) {
getHandler()?.postDelayed(runnable, delayMillis)
}
@JvmStatic
fun runOnMainSync(runnable: Runnable) {
if (isMainThread()) {
runnable.run()
} else {
val sync = CountDownLatch(1)
runOnMain(Runnable {
try {
runnable.run()
} finally {
sync.countDown()
}
})
try {
sync.await()
} catch (ie: InterruptedException) {
throw java.lang.AssertionError(ie)
}
}
}
@JvmStatic
fun cancelRunnableOnMain(runnable: Runnable) {
getHandler()?.removeCallbacks(runnable)
}
private fun getHandler(): Handler? {
if (handler == null) {
synchronized(Util::class.java) {
if (handler == null) {
handler = Handler(Looper.getMainLooper())
}
}
}
return handler
}
@JvmStatic
fun wait(lock: Object, timeout: Long) {
try {
lock.wait(timeout)
} catch (ie: InterruptedException) {
throw AssertionError(ie)
}
}
@JvmStatic
fun newSingleThreadedLifoExecutor(): ExecutorService {
val executor = ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, LinkedBlockingLifoQueue<Runnable>())
executor.execute {
Thread.currentThread().priority = Thread.MIN_PRIORITY
}
return executor
}
@JvmStatic
fun join(list: Array<String?>, delimiter: String?): String {
return join(Arrays.asList(*list), delimiter)
}
@JvmStatic
fun join(list: Collection<String?>, delimiter: String?): String {
val result = StringBuilder()
var i = 0
for (item in list) {
result.append(item)
if (++i < list.size) result.append(delimiter)
}
return result.toString()
}
@JvmStatic
fun join(list: LongArray, delimeter: String?): String {
val sb = java.lang.StringBuilder()
for (j in list.indices) {
if (j != 0) sb.append(delimeter)
sb.append(list[j])
}
return sb.toString()
}
@JvmStatic
fun <E> chunk(list: List<E>, chunkSize: Int): List<List<E>> {
val chunks: MutableList<List<E>> = ArrayList(list.size / chunkSize)
var i = 0
while (i < list.size) {
val chunk = list.subList(i, Math.min(list.size, i + chunkSize))
chunks.add(chunk)
i += chunkSize
}
return chunks
}
@JvmStatic
fun equals(a: Any?, b: Any?): Boolean {
return a === b || a != null && a == b
}
@JvmStatic
fun hashCode(vararg objects: Any?): Int {
return Arrays.hashCode(objects)
}
@JvmStatic
fun <K, V> getOrDefault(map: Map<K, V>, key: K, defaultValue: V): V? {
return if (map.containsKey(key)) map[key] else defaultValue
}
@JvmStatic
@Throws(IOException::class)
fun getStreamLength(`in`: InputStream): Long {
val buffer = ByteArray(4096)
var totalSize = 0
var read: Int
while (`in`.read(buffer).also { read = it } != -1) {
totalSize += read
}
return totalSize.toLong()
}
@JvmStatic
fun toIntExact(value: Long): Int {
if (value.toInt().compareTo(value) != 0){
throw ArithmeticException("integer overflow")
}
return value.toInt()
}
@JvmStatic
fun close(closeable: Closeable) {
try {
closeable.close()
} catch (e: IOException) {
Log.w("Loki", e)
}
}
@JvmStatic
fun isOwnNumber(context: Context, number: String): Boolean {
return TextSecurePreferences.getLocalNumber(context).equals(number)
}
@JvmStatic
fun <T> partition(list: List<T>, partitionSize: Int): List<List<T>> {
val results: MutableList<List<T>> = LinkedList()
var index = 0
while (index < list.size) {
val subListSize = min(partitionSize, list.size - index)
results.add(list.subList(index, index + subListSize))
index += partitionSize
}
return results
}
@JvmStatic
fun toIsoString(bytes: ByteArray?): String {
return String(bytes!!, StandardCharsets.ISO_8859_1)
}
@JvmStatic
fun toIsoBytes(isoString: String): ByteArray {
return isoString.toByteArray(StandardCharsets.ISO_8859_1)
}
@JvmStatic
fun toUtf8Bytes(utf8String: String): ByteArray {
return utf8String.toByteArray(StandardCharsets.UTF_8)
}
@JvmStatic
@SuppressLint("NewApi")
fun isDefaultSmsProvider(context: Context): Boolean {
return context.packageName == Telephony.Sms.getDefaultSmsPackage(context)
}
@JvmStatic
@Throws(IOException::class)
fun readFully(`in`: InputStream?, buffer: ByteArray) {
if (`in` == null) return
readFully(`in`, buffer, buffer.size)
}
@JvmStatic
@Throws(IOException::class)
fun readFully(`in`: InputStream, buffer: ByteArray?, len: Int) {
var offset = 0
while (true) {
val read = `in`.read(buffer, offset, len - offset)
if (read == -1) throw EOFException("Stream ended early")
offset += if (read + offset < len) read else return
}
}
@JvmStatic
@Throws(IOException::class)
fun readFully(`in`: InputStream): ByteArray? {
val bout = ByteArrayOutputStream()
val buffer = ByteArray(4096)
var read: Int
while (`in`.read(buffer).also { read = it } != -1) {
bout.write(buffer, 0, read)
}
`in`.close()
return bout.toByteArray()
}
@JvmStatic
@Throws(IOException::class)
fun readFullyAsString(`in`: InputStream): String? {
return String(readFully(`in`)!!)
}
@JvmStatic
fun getSecret(size: Int): String? {
val secret = getSecretBytes(size)
return Base64.encodeBytes(secret)
}
@JvmStatic
fun getSecretBytes(size: Int): ByteArray {
val secret = ByteArray(size)
getSecureRandom().nextBytes(secret)
return secret
}
@JvmStatic
fun getSecureRandom(): SecureRandom {
return SecureRandom()
}
@JvmStatic
fun getFirstNonEmpty(vararg values: String?): String? {
for (value in values) {
if (!TextUtils.isEmpty(value)) {
return value
}
}
return ""
}
@JvmStatic
fun isEmpty(collection: Collection<*>?): Boolean {
return collection == null || collection.isEmpty()
}
@JvmStatic
fun <T> getRandomElement(elements: Array<T>): T {
return elements[SecureRandom().nextInt(elements.size)]
}
@JvmStatic
fun getBoldedString(value: String?): CharSequence {
if (value.isNullOrEmpty()) { return "" }
val spanned = SpannableString(value)
spanned.setSpan(StyleSpan(Typeface.BOLD), 0,
spanned.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spanned
}
@JvmStatic
fun clamp(value: Int, min: Int, max: Int): Int {
return Math.min(Math.max(value, min), max)
}
@JvmStatic
fun combine(vararg elements: ByteArray?): ByteArray? {
return try {
val baos = ByteArrayOutputStream()
for (element in elements) {
baos.write(element)
}
baos.toByteArray()
} catch (e: IOException) {
throw java.lang.AssertionError(e)
}
}
@JvmStatic
fun split(input: ByteArray?, firstLength: Int, secondLength: Int): Array<ByteArray?> {
val parts = arrayOfNulls<ByteArray>(2)
parts[0] = ByteArray(firstLength)
System.arraycopy(input, 0, parts[0], 0, firstLength)
parts[1] = ByteArray(secondLength)
System.arraycopy(input, firstLength, parts[1], 0, secondLength)
return parts
}
@JvmStatic
fun getPrettyFileSize(sizeBytes: Long): String {
if (sizeBytes <= 0) return "0"
val units = arrayOf("B", "kB", "MB", "GB", "TB")
val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt()
return DecimalFormat("#,##0.#").format(sizeBytes / Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups]
}
}
fun <T, K: Any> Iterable<T>.associateByNotNull(
keySelector: (T) -> K?
) = associateByNotNull(keySelector) { it }
fun <T, K: Any, V: Any> Iterable<T>.associateByNotNull(
keySelector: (T) -> K?,
valueTransform: (T) -> V?,
): Map<K, V> = buildMap {
for (item in this@associateByNotNull) {
val key = keySelector(item) ?: continue
val value = valueTransform(item) ?: continue
this[key] = value
}
}