mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'master' of github.com:Dev-ClayP/KeePassDX into Dev-ClayP-master
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -359,77 +359,40 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
|
|||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ->
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user