diff --git a/.gitignore b/.gitignore
index 30189ea..6703893 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@ target
.idea
readyci.iml
.scannerwork
-.DS_Store
+.DS_Store
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index f7711e6..aa568a5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.squarepolka
readyci
- 0.6.1
+ 0.6.3
org.springframework.boot
@@ -21,6 +21,11 @@
+
+ net.dongliu
+ apk-parser
+ 2.6.5
+
org.jetbrains.kotlin
@@ -136,7 +141,7 @@
org.jacoco
jacoco-maven-plugin
- 0.8.1
+ 0.8.3
${basedir}/target/coverage-reports/jacoco-unit.exec
${basedir}/target/coverage-reports/jacoco-unit.exec
diff --git a/src/main/java/com/squarepolka/readyci/configuration/AndroidPropConstants.java b/src/main/java/com/squarepolka/readyci/configuration/AndroidPropConstants.java
index 8462428..8093abe 100644
--- a/src/main/java/com/squarepolka/readyci/configuration/AndroidPropConstants.java
+++ b/src/main/java/com/squarepolka/readyci/configuration/AndroidPropConstants.java
@@ -8,7 +8,6 @@ private AndroidPropConstants() {
public static final String BUILD_PROP_SCHEME = "scheme";
public static final String BUILD_PROP_DEPLOY_TRACK = "deployTrack";
- public static final String BUILD_PROP_PACKAGE_NAME = "packageName";
public static final String BUILD_PROP_SERVICE_ACCOUNT_FILE = "playStoreAuthCert";
public static final String BUILD_PROP_SERVICE_ACCOUNT_EMAIL = "playStoreEmail";
public static final String BUILD_PROP_JAVA_KEYSTORE_PATH = "javaKeystorePath";
@@ -19,5 +18,6 @@ private AndroidPropConstants() {
public static final String BUILD_PROP_HOCKEYAPP_TOKEN = "hockappToken";
public static final String BUILD_PROP_HOCKEYAPP_RELEASE_TAGS = "hockeyappReleaseTags";
public static final String BUILD_PROP_HOCKEYAPP_RELEASE_NOTES = "hockeyappReleaseNotes";
+ public static final String BUILD_PROP_FILENAME_FILTERS = "fileNameFilters";
}
diff --git a/src/main/java/com/squarepolka/readyci/taskrunner/BuildEnvironment.java b/src/main/java/com/squarepolka/readyci/taskrunner/BuildEnvironment.java
index b126db4..148f731 100644
--- a/src/main/java/com/squarepolka/readyci/taskrunner/BuildEnvironment.java
+++ b/src/main/java/com/squarepolka/readyci/taskrunner/BuildEnvironment.java
@@ -75,6 +75,20 @@ public List getProperties(String propertyName) {
return values;
}
+ /**
+ * Fetch a list of environment properties
+ * @param propertyName
+ * @return list of String property values
+ * @throws PropertyMissingException if the property does not exist
+ */
+ public List getProperties(String propertyName, List defaultValues) {
+ List values = (List) buildParameters.get(propertyName);
+ if (null == values || values.isEmpty()) {
+ return defaultValues;
+ }
+ return values;
+ }
+
/**
* Fetch a single environment property
* @param propertyName
diff --git a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidSignApp.java b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidSignApp.java
index 7acd0a6..6b92939 100644
--- a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidSignApp.java
+++ b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidSignApp.java
@@ -29,6 +29,8 @@ public void performTask(BuildEnvironment buildEnvironment) throws TaskFailedExce
String scheme = buildEnvironment.getProperty(BUILD_PROP_SCHEME);
String keystorePath = buildEnvironment.getProperty(BUILD_PROP_JAVA_KEYSTORE_PATH);
String storePass = buildEnvironment.getProperty(BUILD_PROP_STOREPASS);
+
+ // TODO: these are likely to be incorrect
String unsignedApkPath = String.format("%s/app/build/outputs/apk/%s/app-%s-unsigned.apk", buildEnvironment.getProjectPath(), scheme.toLowerCase(), scheme.toLowerCase());
String signedApkPath = String.format("%s/app/build/outputs/apk/%s/app-%s.apk", buildEnvironment.getProjectPath(), scheme.toLowerCase(), scheme.toLowerCase());
diff --git a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadHockeyapp.java b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadHockeyapp.java
deleted file mode 100644
index 75d3e49..0000000
--- a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadHockeyapp.java
+++ /dev/null
@@ -1,72 +0,0 @@
-
-package com.squarepolka.readyci.tasks.app.android;
-
-
-import com.squarepolka.readyci.configuration.ReadyCIConfiguration;
-import com.squarepolka.readyci.taskrunner.BuildEnvironment;
-import com.squarepolka.readyci.tasks.Task;
-import com.squarepolka.readyci.util.Util;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
-
-import java.io.*;
-import java.util.Collection;
-
-@Component
-public class AndroidUploadHockeyapp extends Task {
-
-
- public static final String TASK_UPLOAD_HOCKEYAPP = "android_upload_hockeyapp";
- public static final String BUILD_PROP_HOCKEYAPP_TOKEN = "hockappToken";
- public static final String BUILD_PROP_HOCKEYAPP_RELEASE_TAGS = "hockeyappReleaseTags";
- public static final String BUILD_PROP_HOCKEYAPP_RELEASE_NOTES = "hockeyappReleaseNotes";
-
- private static final String COMMAND_GIT = "/usr/bin/git";
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ReadyCIConfiguration.class);
-
- @Override
- public String taskIdentifier() {
- return TASK_UPLOAD_HOCKEYAPP;
- }
-
- @Override
- public void performTask(BuildEnvironment buildEnvironment) {
-
- String hockappToken = buildEnvironment.getProperty(BUILD_PROP_HOCKEYAPP_TOKEN);
- String releaseTags = buildEnvironment.getProperty(BUILD_PROP_HOCKEYAPP_RELEASE_TAGS, "");
- String releaseNotes = buildEnvironment.getProperty(BUILD_PROP_HOCKEYAPP_RELEASE_NOTES, "");
-
- if(releaseNotes.isEmpty()) {
- InputStream inputStream = executeCommand(new String[]{COMMAND_GIT, "log", "-1", "--pretty=%B"}, buildEnvironment.getProjectPath());
- try {
- releaseNotes = Util.readInputStream(inputStream);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- // upload all the apk builds that it finds
- Collection files = Util.findAllByExtension(new File(buildEnvironment.getProjectPath()), ".apk");
- for (File apk : files) {
- if(apk.getAbsolutePath().contains("build")) {
- LOGGER.warn("uploading "+ apk.getAbsolutePath());
- // Upload to HockeyApp
- executeCommand(new String[]{"/usr/bin/curl",
- "https://rink.hockeyapp.net/api/2/apps/upload",
- "-H", "X-HockeyAppToken: " + hockappToken,
- "-F", "ipa=@" + apk.getAbsolutePath(),
- "-F", "notes=" + releaseNotes,
- "-F", "tags=" + releaseTags,
- "-F", "notes_type=0", // Textual release notes
- "-F", "status=2", // Make this version available for download
- "-F", "notify=1", // Notify users who can install the app
- "-F", "strategy=add", // Add the build if one with the same build number exists
- "-F", "mandatory=1" // Download is mandatory
- }, buildEnvironment.getProjectPath());
- }
- }
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadHockeyapp.kt b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadHockeyapp.kt
new file mode 100644
index 0000000..e8a9af3
--- /dev/null
+++ b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadHockeyapp.kt
@@ -0,0 +1,64 @@
+package com.squarepolka.readyci.tasks.app.android
+
+import com.squarepolka.readyci.configuration.AndroidPropConstants
+import com.squarepolka.readyci.configuration.ReadyCIConfiguration
+import com.squarepolka.readyci.taskrunner.BuildEnvironment
+import com.squarepolka.readyci.tasks.Task
+import com.squarepolka.readyci.tasks.app.android.extensions.isDebuggable
+import com.squarepolka.readyci.tasks.app.android.extensions.isSigned
+import com.squarepolka.readyci.util.Util
+import net.dongliu.apk.parser.ApkFile
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Component
+
+import java.io.File
+
+@Component
+class AndroidUploadHockeyapp : Task() {
+
+ companion object {
+ private const val TASK_UPLOAD_HOCKEYAPP = "android_upload_hockeyapp"
+ }
+
+ override fun taskIdentifier(): String = TASK_UPLOAD_HOCKEYAPP
+
+ override fun performTask(buildEnvironment: BuildEnvironment) {
+ val hockappToken = buildEnvironment.getProperty(AndroidPropConstants.BUILD_PROP_HOCKEYAPP_TOKEN)
+ val releaseTags = buildEnvironment.getProperty(AndroidPropConstants.BUILD_PROP_HOCKEYAPP_RELEASE_TAGS, "")
+ val releaseNotes = buildEnvironment.getProperty(AndroidPropConstants.BUILD_PROP_HOCKEYAPP_RELEASE_NOTES, "")
+ val filenameFilters = buildEnvironment.getProperties(AndroidPropConstants.BUILD_PROP_FILENAME_FILTERS,
+ listOf("zipaligned", "unsigned"))
+
+ if(hockappToken == null) {
+ throw RuntimeException("AndroidUploadStore: Missing vital details for play store deployment:\n- hockappToken is required")
+ }
+
+ val filteredApks = AndroidUtil.findAllApkOutputs(buildEnvironment.projectPath)
+ .filter { file ->
+ filenameFilters.none { file.nameWithoutExtension.contains(it) }
+ }
+ .map { Pair(it, ApkFile(it)) }
+ .filter { it.second.isSigned }
+
+ when(filteredApks.size) {
+ 0 -> throw RuntimeException("Could not find the signed APK")
+ 1 -> {} // do nothing
+ else -> throw RuntimeException("There are too many valid APKs that we can upload, please provide a more specific scheme for this pipeline ")
+ }
+
+ val rawFile = filteredApks.first().first
+
+ executeCommand(arrayOf("/usr/bin/curl", "https://rink.hockeyapp.net/api/2/apps/upload",
+ "-H", "X-HockeyAppToken: $hockappToken",
+ "-F", "ipa=@${rawFile.absolutePath}",
+ "-F", "notes=$releaseNotes",
+ "-F", "tags=$releaseTags",
+ "-F", "notes_type=0", // Textual release notes
+ "-F", "status=2", // Make this version available for download
+ "-F", "notify=1", // Notify users who can install the app
+ "-F", "strategy=add", // Add the build if one with the same build number exists
+ "-F", "mandatory=1" // Download is mandatory
+ ), buildEnvironment.projectPath)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadStore.java b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadStore.java
deleted file mode 100644
index c524dbd..0000000
--- a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadStore.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package com.squarepolka.readyci.tasks.app.android;
-
-import com.google.api.client.http.AbstractInputStreamContent;
-import com.google.api.client.http.FileContent;
-import com.google.api.services.androidpublisher.AndroidPublisher;
-import com.google.api.services.androidpublisher.model.*;
-import com.squarepolka.readyci.configuration.ReadyCIConfiguration;
-import com.squarepolka.readyci.taskrunner.BuildEnvironment;
-import com.squarepolka.readyci.taskrunner.TaskFailedException;
-import com.squarepolka.readyci.tasks.Task;
-import com.squarepolka.readyci.util.android.AndroidPublisherHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static com.squarepolka.readyci.configuration.AndroidPropConstants.*;
-
-@Component
-public class AndroidUploadStore extends Task {
-
- public static final String TASK_UPLOAD_STORE = "android_upload_play_store";
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ReadyCIConfiguration.class);
-
- @Override
- public String taskIdentifier() {
- return TASK_UPLOAD_STORE;
- }
-
- @Override
- public void performTask(BuildEnvironment buildEnvironment) throws TaskFailedException {
-
- try {
-
- String deployTrack = buildEnvironment.getProperty(BUILD_PROP_DEPLOY_TRACK, "");
- String packageName = buildEnvironment.getProperty(BUILD_PROP_PACKAGE_NAME, "");
- String playStoreEmail = buildEnvironment.getProperty(BUILD_PROP_SERVICE_ACCOUNT_EMAIL, "");
- String playStoreCert = buildEnvironment.getProperty(BUILD_PROP_SERVICE_ACCOUNT_FILE, "");
-
- if (deployTrack.isEmpty() ||
- packageName.isEmpty() ||
- playStoreEmail.isEmpty() ||
- playStoreCert.isEmpty()) {
-
- StringBuilder sb = new StringBuilder();
-
- sb.append("AndroidUploadStore: Missing vital details for play store deployment:");
- if(deployTrack.isEmpty())
- sb.append("\n- deployTrack is required");
- if(packageName.isEmpty())
- sb.append("\n- packageName is required");
- if(playStoreEmail.isEmpty())
- sb.append("\n- playStoreEmail is required");
- if(playStoreCert.isEmpty())
- sb.append("\n- playStoreCert is required");
-
- throw new Exception(sb.toString());
- }
-
- String playStoreCertLocation = String.format("%s/%s", buildEnvironment.getCredentialsPath(), playStoreCert);
-
- String scheme = buildEnvironment.getProperty(BUILD_PROP_SCHEME);
- String appBinaryPath = String.format("%s/app/build/outputs/apk/%s/app-%s.apk",
- buildEnvironment.getProjectPath(), scheme.toLowerCase(), scheme.toLowerCase());
-
- LOGGER.warn("AndroidUploadStore: uploading "+appBinaryPath);
-
-
- // Create the API service.
- AndroidPublisher service = AndroidPublisherHelper.init(packageName, playStoreEmail, playStoreCertLocation);
- final AndroidPublisher.Edits edits = service.edits();
-
- // Create a new edit to make changes to your listing.
- AndroidPublisher.Edits.Insert editRequest = edits.insert(packageName, null);
- AppEdit edit = editRequest.execute();
- final String editId = edit.getId();
- LOGGER.info("AndroidUploadStore: Created edit with id: {}", editId);
-
- final AbstractInputStreamContent apkFile = new FileContent(AndroidPublisherHelper.MIME_TYPE_APK, new File(appBinaryPath));
- AndroidPublisher.Edits.Apks.Upload uploadRequest = edits
- .apks()
- .upload(packageName, editId, apkFile);
-
- Apk apk = uploadRequest.execute();
- LOGGER.info("AndroidUploadStore: Version code {} has been uploaded", apk.getVersionCode());
-
-
- // Assign apk to alpha track.
- List apkVersionCodes = new ArrayList();
- apkVersionCodes.add(Long.valueOf(apk.getVersionCode()));
- AndroidPublisher.Edits.Tracks.Update updateTrackRequest = edits
- .tracks()
- .update(packageName,
- editId,
- deployTrack,
- new Track().setReleases(
- Collections.singletonList(
- new TrackRelease()
- .setName("Alpha Release")
- .setVersionCodes(apkVersionCodes)
- .setStatus("completed")
- .setReleaseNotes(Collections.singletonList(
- new LocalizedText()
- .setLanguage("en-AU")
- .setText("This is an alpha release"))))));
- Track updatedTrack = updateTrackRequest.execute();
- LOGGER.info("AndroidUploadStore: Track {} has been updated.", updatedTrack.getTrack());
-
-
- // Commit changes for edit.
- AndroidPublisher.Edits.Commit commitRequest = edits.commit(packageName, editId);
- AppEdit appEdit = commitRequest.execute();
- LOGGER.info("AndroidUploadStore: App edit with id {} has been committed", appEdit.getId());
-
- } catch (Exception ex) {
- LOGGER.error("AndroidUploadStore: Exception was thrown while uploading apk to alpha track", ex);
- }
- }
-
-}
diff --git a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadStore.kt b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadStore.kt
new file mode 100644
index 0000000..ca60238
--- /dev/null
+++ b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUploadStore.kt
@@ -0,0 +1,108 @@
+package com.squarepolka.readyci.tasks.app.android
+
+import com.google.api.client.http.FileContent
+import com.google.api.services.androidpublisher.model.*
+import com.squarepolka.readyci.configuration.AndroidPropConstants
+import com.squarepolka.readyci.configuration.ReadyCIConfiguration
+import com.squarepolka.readyci.taskrunner.BuildEnvironment
+import com.squarepolka.readyci.taskrunner.TaskFailedException
+import com.squarepolka.readyci.tasks.Task
+import com.squarepolka.readyci.util.android.AndroidPublisherHelper
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Component
+import net.dongliu.apk.parser.ApkFile
+import java.util.ArrayList
+import com.squarepolka.readyci.configuration.AndroidPropConstants.*
+import com.squarepolka.readyci.tasks.app.android.extensions.isDebuggable
+import com.squarepolka.readyci.tasks.app.android.extensions.isSigned
+
+@Component
+class AndroidUploadStore : Task() {
+ companion object {
+ private const val TASK_UPLOAD_STORE = "android_upload_play_store"
+ private val LOGGER = LoggerFactory.getLogger(ReadyCIConfiguration::class.java)
+ }
+
+ override fun taskIdentifier(): String = TASK_UPLOAD_STORE
+
+ @Throws(TaskFailedException::class)
+ override fun performTask(buildEnvironment: BuildEnvironment) {
+ try {
+ val deployTrack = buildEnvironment.getProperty(BUILD_PROP_DEPLOY_TRACK, "")
+ val playStoreEmail = buildEnvironment.getProperty(BUILD_PROP_SERVICE_ACCOUNT_EMAIL, "")
+ val playStoreCert = buildEnvironment.getProperty(BUILD_PROP_SERVICE_ACCOUNT_FILE, "")
+ val filenameFilters = buildEnvironment.getProperties(AndroidPropConstants.BUILD_PROP_FILENAME_FILTERS,
+ listOf("zipaligned", "unsigned"))
+
+ if ((deployTrack.isEmpty() || playStoreEmail.isEmpty() || playStoreCert.isEmpty())) {
+ val sb = StringBuilder()
+ sb.append("AndroidUploadStore: Missing vital details for play store deployment:")
+ if (deployTrack.isEmpty())
+ sb.append("\n- deployTrack is required")
+ if (playStoreEmail.isEmpty())
+ sb.append("\n- playStoreEmail is required")
+ if (playStoreCert.isEmpty())
+ sb.append("\n- playStoreCert is required")
+ throw Exception(sb.toString())
+ }
+
+ val playStoreCertLocation = String.format("%s/%s", buildEnvironment.credentialsPath, playStoreCert)
+
+ val filteredApks = AndroidUtil.findAllApkOutputs(buildEnvironment.projectPath)
+ .filter { file ->
+ filenameFilters.none { file.nameWithoutExtension.contains(it) }
+ }
+ .map { Pair(it, ApkFile(it)) }
+ .filter { it.second.isSigned }
+ .filter { !it.second.isDebuggable }
+
+ when(filteredApks.size) {
+ 0 -> throw RuntimeException("Could not find the signed APK")
+ 1 -> {} // do nothing
+ else -> throw RuntimeException("There are too many valid APKs that we can upload, please provide a more specific scheme for this pipeline ")
+ }
+
+ val rawFile = filteredApks.first().first
+ val apkMetadata = filteredApks.first().second.apkMeta
+
+ // Create the API service.
+ val service = AndroidPublisherHelper.init(apkMetadata.packageName, playStoreEmail, playStoreCertLocation)
+ val edits = service.edits()
+
+ // Create a new edit to make changes to your listing.
+ val editRequest = edits.insert(apkMetadata.packageName, null)
+ val edit = editRequest.execute()
+ LOGGER.info("AndroidUploadStore: Created edit with id: {}", edit.id)
+
+ val apkFile = FileContent(AndroidPublisherHelper.MIME_TYPE_APK, rawFile)
+ val uploadRequest = edits
+ .apks()
+ .upload(apkMetadata.packageName, edit.id, apkFile)
+ val apk = uploadRequest.execute()
+ LOGGER.info("AndroidUploadStore: Version code {} has been uploaded", apk.versionCode)
+
+ // Assign apk to alpha track.
+ val apkVersionCodes = ArrayList()
+ apkVersionCodes.add(apk.versionCode.toLong())
+ val updateTrackRequest = edits
+ .tracks()
+ .update(apkMetadata.packageName,
+ edit.id,
+ deployTrack,
+ Track().setReleases(
+ listOf(TrackRelease()
+ .setName(apkMetadata.versionName)
+ .setVersionCodes(apkVersionCodes)
+ .setStatus("completed"))))
+ val updatedTrack = updateTrackRequest.execute()
+ LOGGER.info("AndroidUploadStore: Track {} has been updated.", updatedTrack.track)
+
+ // Commit changes for edit.
+ val commitRequest = edits.commit(apkMetadata.packageName, edit.id)
+ val appEdit = commitRequest.execute()
+ LOGGER.info("AndroidUploadStore: App edit with id {} has been committed", appEdit.id)
+ } catch (ex: Exception) {
+ LOGGER.error("AndroidUploadStore: Exception was thrown while uploading apk to alpha track", ex)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUtil.kt b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUtil.kt
new file mode 100644
index 0000000..463573c
--- /dev/null
+++ b/src/main/java/com/squarepolka/readyci/tasks/app/android/AndroidUtil.kt
@@ -0,0 +1,11 @@
+package com.squarepolka.readyci.tasks.app.android
+
+import com.squarepolka.readyci.util.Util
+import java.io.File
+
+object AndroidUtil {
+ fun findAllApkOutputs(dir: String) = Util.findAllByExtension(File(dir), ".apk")
+ .filter {
+ it.absolutePath.contains("build/outputs")
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/squarepolka/readyci/tasks/app/android/extensions/ApkFileExtensions.kt b/src/main/java/com/squarepolka/readyci/tasks/app/android/extensions/ApkFileExtensions.kt
new file mode 100644
index 0000000..c479c9a
--- /dev/null
+++ b/src/main/java/com/squarepolka/readyci/tasks/app/android/extensions/ApkFileExtensions.kt
@@ -0,0 +1,20 @@
+package com.squarepolka.readyci.tasks.app.android.extensions
+
+import net.dongliu.apk.parser.ApkFile
+import net.dongliu.apk.parser.bean.ApkSignStatus
+import org.w3c.dom.Element
+import org.xml.sax.InputSource
+import java.io.StringReader
+import javax.xml.parsers.DocumentBuilderFactory
+
+val ApkFile.isSigned: Boolean
+ get() = verifyApk() == ApkSignStatus.signed
+
+val ApkFile.isDebuggable: Boolean
+ get() {
+ val inputSource = InputSource(StringReader(manifestXml))
+ val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource)
+ val debuggable = (doc.getElementsByTagName("application").item(0) as Element).getAttribute("android:debuggable")
+
+ return debuggable == null || !debuggable.toBoolean()
+ }
\ No newline at end of file
diff --git a/src/main/main.iml b/src/main/main.iml
new file mode 100644
index 0000000..284dd72
--- /dev/null
+++ b/src/main/main.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/test.iml b/src/test/test.iml
new file mode 100644
index 0000000..4b5a533
--- /dev/null
+++ b/src/test/test.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file