Compare commits

..

No commits in common. "8845ceec9a58b604edeb4d8001e842d182342608" and "5ed4c84bf24bd29c859d8344313f385a8ec04c7d" have entirely different histories.

22 changed files with 120 additions and 162 deletions

View file

@ -30,6 +30,8 @@ android {
} }
dependencies { dependencies {
compileOnly project(':libs:hidden-apis-stub')
implementation project(':libs:hidden-apis-shim')
implementation project(":libs:lpac-jni") implementation project(":libs:lpac-jni")
implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'

View file

@ -1,9 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools" <manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="ProtectedPermissions">
<uses-feature
android:name="android.hardware.telephony"
android:required="true" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION" />
<application <application
android:networkSecurityConfig="@xml/network_security_config"> android:networkSecurityConfig="@xml/network_security_config">

View file

@ -3,16 +3,14 @@ package im.angry.openeuicc
import android.app.Application import android.app.Application
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.BaseEuiccChannelManager
open class OpenEuiccApplication : Application() { abstract class BaseOpenEuiccApplication : Application() {
val telephonyManager by lazy { val telephonyManager by lazy {
getSystemService(TelephonyManager::class.java)!! getSystemService(TelephonyManager::class.java)!!
} }
open val euiccChannelManager: EuiccChannelManager by lazy { abstract val euiccChannelManager: BaseEuiccChannelManager
EuiccChannelManager(this)
}
val subscriptionManager by lazy { val subscriptionManager by lazy {
getSystemService(SubscriptionManager::class.java)!! getSystemService(SubscriptionManager::class.java)!!

View file

@ -6,7 +6,7 @@ import android.os.HandlerThread
import android.se.omapi.SEService import android.se.omapi.SEService
import android.telephony.UiccCardInfo import android.telephony.UiccCardInfo
import android.util.Log import android.util.Log
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.BaseOpenEuiccApplication
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -16,9 +16,9 @@ import java.lang.IllegalArgumentException
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
open class EuiccChannelManager(protected val context: Context) { abstract class BaseEuiccChannelManager(private val context: Context) {
companion object { companion object {
const val TAG = "EuiccChannelManager" const val TAG = "BaseEuiccChannelManager"
} }
private val channels = mutableListOf<EuiccChannel>() private val channels = mutableListOf<EuiccChannel>()
@ -28,7 +28,7 @@ open class EuiccChannelManager(protected val context: Context) {
private val lock = Mutex() private val lock = Mutex()
protected val tm by lazy { protected val tm by lazy {
(context.applicationContext as OpenEuiccApplication).telephonyManager (context.applicationContext as BaseOpenEuiccApplication).telephonyManager
} }
private val handler = Handler(HandlerThread("BaseEuiccChannelManager").also { it.start() }.looper) private val handler = Handler(HandlerThread("BaseEuiccChannelManager").also { it.start() }.looper)
@ -48,10 +48,7 @@ open class EuiccChannelManager(protected val context: Context) {
} }
} }
open fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfo, channelInfo: EuiccChannelInfo): EuiccChannel? { abstract fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfo, channelInfo: EuiccChannelInfo): EuiccChannel?
// No-op when unprivileged
return null
}
private suspend fun tryOpenEuiccChannel(uiccInfo: UiccCardInfo): EuiccChannel? { private suspend fun tryOpenEuiccChannel(uiccInfo: UiccCardInfo): EuiccChannel? {
lock.withLock { lock.withLock {
@ -128,8 +125,4 @@ open class EuiccChannelManager(protected val context: Context) {
seService?.shutdown() seService?.shutdown()
seService = null seService = null
} }
open fun notifyEuiccProfilesChanged(slotId: Int) {
// No-op for unprivileged
}
} }

View file

@ -2,7 +2,7 @@ package im.angry.openeuicc.ui
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.BaseEuiccChannelManager
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.openEuiccApplication import im.angry.openeuicc.util.openEuiccApplication
@ -19,7 +19,7 @@ fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int): T where T: Fragment, T:
val <T> T.slotId: Int where T: Fragment, T: EuiccFragmentMarker val <T> T.slotId: Int where T: Fragment, T: EuiccFragmentMarker
get() = requireArguments().getInt("slotId") get() = requireArguments().getInt("slotId")
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: EuiccFragmentMarker val <T> T.euiccChannelManager: BaseEuiccChannelManager where T: Fragment, T: EuiccFragmentMarker
get() = openEuiccApplication.euiccChannelManager get() = openEuiccApplication.euiccChannelManager
val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker

View file

@ -82,7 +82,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
lifecycleScope.launch { lifecycleScope.launch {
val profiles = withContext(Dispatchers.IO) { val profiles = withContext(Dispatchers.IO) {
euiccChannelManager.notifyEuiccProfilesChanged(slotId) openEuiccApplication.subscriptionManager.tryRefreshCachedEuiccInfo(channel.cardId)
channel.lpa.profiles channel.lpa.profiles
} }

View file

@ -4,25 +4,27 @@ import android.os.Bundle
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Spinner import android.widget.Spinner
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.BaseEuiccChannelManager
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
open class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
companion object { companion object {
const val TAG = "MainActivity" const val TAG = "MainActivity"
} }
protected lateinit var manager: EuiccChannelManager private lateinit var manager: BaseEuiccChannelManager
private lateinit var spinnerAdapter: ArrayAdapter<String> private lateinit var spinnerAdapter: ArrayAdapter<String>
private lateinit var spinner: Spinner private lateinit var spinner: Spinner
@ -31,7 +33,7 @@ open class MainActivity : AppCompatActivity() {
private lateinit var noEuiccPlaceholder: View private lateinit var noEuiccPlaceholder: View
protected lateinit var tm: TelephonyManager private lateinit var tm: TelephonyManager
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -69,19 +71,32 @@ open class MainActivity : AppCompatActivity() {
} }
if (tm.supportsDSDS) {
val dsds = menu.findItem(R.id.dsds)
dsds.isVisible = true
dsds.isChecked = tm.dsdsEnabled
}
return true return true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.dsds -> {
tm.dsdsEnabled = !item.isChecked
Toast.makeText(this, R.string.toast_dsds_switched, Toast.LENGTH_LONG).show()
finish()
true
}
else -> false
}
private suspend fun init() { private suspend fun init() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
manager.enumerateEuiccChannels() manager.enumerateEuiccChannels()
manager.knownChannels.forEach { manager.knownChannels.forEach {
Log.d(TAG, it.name) Log.d(TAG, it.name)
Log.d(TAG, it.lpa.eID) Log.d(TAG, it.lpa.eID)
// Request the system to refresh the list of profiles every time we start openEuiccApplication.subscriptionManager.tryRefreshCachedEuiccInfo(it.cardId)
// Note that this is currently supposed to be no-op when unprivileged,
// but it could change in the future
manager.notifyEuiccProfilesChanged(it.slotId)
} }
} }

View file

@ -1,6 +1,28 @@
package im.angry.openeuicc.util package im.angry.openeuicc.util
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import java.lang.Exception
val TelephonyManager.supportsDSDS: Boolean
get() = supportedModemCount == 2
var TelephonyManager.dsdsEnabled: Boolean
get() = activeModemCount >= 2
set(value) {
switchMultiSimConfig(if (value) { 2 } else {1})
}
fun SubscriptionManager.tryRefreshCachedEuiccInfo(cardId: Int) {
if (cardId != 0) {
try {
requestEmbeddedSubscriptionInfoListRefresh(cardId)
} catch (e: Exception) {
// Ignore
}
}
}
val LocalProfileInfo.displayName: String val LocalProfileInfo.displayName: String
get() = nickName.ifEmpty { name } get() = nickName.ifEmpty { name }

View file

@ -6,12 +6,12 @@ import android.graphics.Rect
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.BaseOpenEuiccApplication
val Activity.openEuiccApplication: OpenEuiccApplication val Activity.openEuiccApplication: BaseOpenEuiccApplication
get() = application as OpenEuiccApplication get() = application as BaseOpenEuiccApplication
val Fragment.openEuiccApplication: OpenEuiccApplication val Fragment.openEuiccApplication: BaseOpenEuiccApplication
get() = requireActivity().openEuiccApplication get() = requireActivity().openEuiccApplication
// Source: <https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height> // Source: <https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height>

View file

@ -7,4 +7,11 @@
app:actionViewClass="android.widget.Spinner" app:actionViewClass="android.widget.Spinner"
android:background="?android:attr/colorPrimary" android:background="?android:attr/colorPrimary"
app:showAsAction="always" /> app:showAsAction="always" />
<item
android:id="@+id/dsds"
android:title="@string/dsds"
android:checkable="true"
android:visible="false"
app:showAsAction="never" />
</menu> </menu>

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="no_euicc">No eUICC card on this device is accessible by this app.\nYou may want to try out the privileged OpenEUICC app instead.</string> <string name="no_euicc">No eUICC found on this device.\nOn some devices, you may need to enable dual SIM first in the menu of this app.</string>
<string name="dsds">Dual SIM</string>
<string name="enabled">Enabled</string> <string name="enabled">Enabled</string>
<string name="disabled">Disabled</string> <string name="disabled">Disabled</string>
@ -15,6 +17,7 @@
<string name="toast_profile_enabled">eSIM profile switched. Please wait for a while when the card is restarting.</string> <string name="toast_profile_enabled">eSIM profile switched. Please wait for a while when the card is restarting.</string>
<string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string> <string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
<string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</string> <string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</string>
<string name="toast_dsds_switched">DSDS state switched. Please wait until the modem restarts.</string>
<string name="profile_download">New eSIM</string> <string name="profile_download">New eSIM</string>
<string name="profile_download_server">Server (RSP / SM-DP+)</string> <string name="profile_download_server">Server (RSP / SM-DP+)</string>

View file

@ -3,7 +3,33 @@ plugins {
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
} }
apply from: '../helpers.gradle' def getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', '--first-parent', '--count', 'master'
standardOutput = stdout
}
return Integer.parseInt(stdout.toString().trim())
}
catch (ignored) {
return -1;
}
}
def getVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--always', '--tags', '--dirty'
standardOutput = stdout
}
return stdout.toString().trim()
}
catch (ignored) {
return null;
}
}
// Signing config, mainly intended for debug builds // Signing config, mainly intended for debug builds
def keystorePropertiesFile = rootProject.file("keystore.properties"); def keystorePropertiesFile = rootProject.file("keystore.properties");
@ -17,8 +43,8 @@ android {
applicationId "im.angry.openeuicc" applicationId "im.angry.openeuicc"
minSdk 30 minSdk 30
targetSdk 31 targetSdk 31
versionCode getGitVersionCode() versionCode getVersionCode()
versionName getGitVersionName() versionName getVersionName()
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -41,7 +67,7 @@ android {
signingConfig signingConfigs.config signingConfig signingConfigs.config
} }
} }
applicationVariants.configureEach { variant -> applicationVariants.all { variant ->
if (variant.name == "debug") { if (variant.name == "debug") {
variant.outputs.each { o -> o.versionCodeOverride = System.currentTimeSeconds() } variant.outputs.each { o -> o.versionCodeOverride = System.currentTimeSeconds() }
} }

View file

@ -3,14 +3,6 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:ignore="ProtectedPermissions" tools:ignore="ProtectedPermissions"
package="im.angry.openeuicc"> package="im.angry.openeuicc">
<uses-feature
android:name="android.hardware.telephony"
android:required="true" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION" />
<application <application
android:name=".PrivilegedOpenEuiccApplication" android:name=".PrivilegedOpenEuiccApplication"
@ -21,7 +13,7 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.OpenEUICC"> android:theme="@style/Theme.OpenEUICC">
<activity <activity
android:name=".ui.PrivilegedMainActivity" android:name=".ui.MainActivity"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View file

@ -1,10 +1,10 @@
package im.angry.openeuicc package im.angry.openeuicc
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.BaseEuiccChannelManager
import im.angry.openeuicc.core.PrivilegedEuiccChannelManager import im.angry.openeuicc.core.PrivilegedEuiccChannelManager
class PrivilegedOpenEuiccApplication: OpenEuiccApplication() { class PrivilegedOpenEuiccApplication: BaseOpenEuiccApplication() {
override val euiccChannelManager: EuiccChannelManager by lazy { override val euiccChannelManager: BaseEuiccChannelManager by lazy {
PrivilegedEuiccChannelManager(this) PrivilegedEuiccChannelManager(this)
} }

View file

@ -3,12 +3,11 @@ package im.angry.openeuicc.core
import android.content.Context import android.content.Context
import android.telephony.UiccCardInfo import android.telephony.UiccCardInfo
import android.util.Log import android.util.Log
import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import java.lang.Exception import java.lang.Exception
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(context) { class PrivilegedEuiccChannelManager(context: Context): BaseEuiccChannelManager(context) {
override fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfo, channelInfo: EuiccChannelInfo): EuiccChannel? { override fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfo, channelInfo: EuiccChannelInfo): EuiccChannel? {
if (uiccInfo.isEuicc && !uiccInfo.isRemovable) { if (uiccInfo.isEuicc && !uiccInfo.isRemovable) {
Log.d(TAG, "Using TelephonyManager for slot ${uiccInfo.slotIndex}") Log.d(TAG, "Using TelephonyManager for slot ${uiccInfo.slotIndex}")
@ -36,12 +35,4 @@ class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(conte
} }
} }
} }
override fun notifyEuiccProfilesChanged(slotId: Int) {
(context.applicationContext as OpenEuiccApplication).subscriptionManager.apply {
findEuiccChannelBySlotBlocking(slotId)?.let {
tryRefreshCachedEuiccInfo(it.cardId)
}
}
}
} }

View file

@ -4,13 +4,13 @@ import android.service.euicc.*
import android.telephony.euicc.DownloadableSubscription import android.telephony.euicc.DownloadableSubscription
import android.telephony.euicc.EuiccInfo import android.telephony.euicc.EuiccInfo
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.BaseOpenEuiccApplication
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
class OpenEuiccService : EuiccService() { class OpenEuiccService : EuiccService() {
private val openEuiccApplication private val openEuiccApplication
get() = application as OpenEuiccApplication get() = application as BaseOpenEuiccApplication
private fun findChannel(slotId: Int): EuiccChannel? = private fun findChannel(slotId: Int): EuiccChannel? =
openEuiccApplication.euiccChannelManager openEuiccApplication.euiccChannelManager

View file

@ -6,7 +6,7 @@ import androidx.appcompat.app.AppCompatActivity
class LuiActivity : AppCompatActivity() { class LuiActivity : AppCompatActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
startActivity(Intent(this, PrivilegedMainActivity::class.java)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} }
} }

View file

@ -1,32 +0,0 @@
package im.angry.openeuicc.ui
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import im.angry.openeuicc.R
import im.angry.openeuicc.util.*
class PrivilegedMainActivity : MainActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.activity_main_privileged, menu)
if (tm.supportsDSDS) {
val dsds = menu.findItem(R.id.dsds)
dsds.isVisible = true
dsds.isChecked = tm.dsdsEnabled
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.dsds -> {
tm.dsdsEnabled = !item.isChecked
Toast.makeText(this, R.string.toast_dsds_switched, Toast.LENGTH_LONG).show()
finish()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View file

@ -1,24 +0,0 @@
package im.angry.openeuicc.util
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import java.lang.Exception
val TelephonyManager.supportsDSDS: Boolean
get() = supportedModemCount == 2
var TelephonyManager.dsdsEnabled: Boolean
get() = activeModemCount >= 2
set(value) {
switchMultiSimConfig(if (value) { 2 } else {1})
}
fun SubscriptionManager.tryRefreshCachedEuiccInfo(cardId: Int) {
if (cardId != 0) {
try {
requestEmbeddedSubscriptionInfoListRefresh(cardId)
} catch (e: Exception) {
// Ignore
}
}
}

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/dsds"
android:title="@string/dsds"
android:checkable="true"
android:visible="false"
app:showAsAction="never" />
</menu>

View file

@ -1,8 +1,3 @@
<resources> <resources>
<string name="app_name">OpenEUICC</string> <string name="app_name">OpenEUICC</string>
<string name="no_euicc">No eUICC found on this device.\nOn some devices, you may need to enable dual SIM first in the menu of this app.</string>
<string name="dsds">Dual SIM</string>
<string name="toast_dsds_switched">DSDS state switched. Please wait until the modem restarts.</string>
</resources> </resources>

View file

@ -1,27 +0,0 @@
ext.getGitVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', '--first-parent', '--count', 'master'
standardOutput = stdout
}
return Integer.parseInt(stdout.toString().trim())
}
catch (ignored) {
return -1;
}
}
ext.getGitVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--always', '--tags', '--dirty'
standardOutput = stdout
}
return stdout.toString().trim()
}
catch (ignored) {
return null;
}
}