mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-80278 support of building Android apk artifacts
This commit is contained in:
+1
-1
@@ -77,7 +77,7 @@ public abstract class FacetBasedPackagingSourceItemsProvider<F extends Facet, E
|
||||
|
||||
protected abstract PackagingElement<?> createElement(ArtifactEditorContext context, F facet);
|
||||
|
||||
private static class FacetBasedSourceItem<F extends Facet> extends PackagingSourceItem {
|
||||
protected static class FacetBasedSourceItem<F extends Facet> extends PackagingSourceItem {
|
||||
private final FacetBasedPackagingSourceItemsProvider<F, ?> myProvider;
|
||||
private final F myFacet;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.jetbrains.android.util;
|
||||
|
||||
import com.android.jarutils.SignedJarBuilder;
|
||||
import com.android.resources.ResourceFolderType;
|
||||
import com.android.resources.ResourceType;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
@@ -38,6 +39,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
@@ -90,6 +94,8 @@ public class AndroidCommonUtils {
|
||||
|
||||
@NonNls public static final String PACKAGE_MANIFEST_ATTRIBUTE = "package";
|
||||
|
||||
@NonNls public static final String ANDROID_FINAL_PACKAGE_FOR_ARTIFACT_SUFFIX = ".afp";
|
||||
|
||||
private AndroidCommonUtils() {
|
||||
}
|
||||
|
||||
@@ -529,4 +535,32 @@ public class AndroidCommonUtils {
|
||||
final String b = path.substring(dot);
|
||||
return a + suffix + b;
|
||||
}
|
||||
|
||||
public static void signApk(@NotNull File srcApk,
|
||||
@NotNull File destFile,
|
||||
@NotNull PrivateKey privateKey,
|
||||
@NotNull X509Certificate certificate)
|
||||
throws IOException, GeneralSecurityException {
|
||||
FileOutputStream fos = new FileOutputStream(destFile);
|
||||
SignedJarBuilder builder = new SafeSignedJarBuilder(fos, privateKey, certificate, destFile.getPath());
|
||||
FileInputStream fis = new FileInputStream(srcApk);
|
||||
try {
|
||||
builder.writeZip(fis, null);
|
||||
builder.close();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
fos.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +219,11 @@
|
||||
<usageTargetProvider implementation="org.jetbrains.android.AndroidUsagesTargetProvider"/>
|
||||
<renameHandler implementation="org.jetbrains.android.AndroidRenameHandler" order="first"/>
|
||||
|
||||
<packaging.elementType implementation="org.jetbrains.android.compiler.artifact.AndroidFinalPackageElementType"/>
|
||||
<packaging.sourceItemProvider implementation="org.jetbrains.android.compiler.artifact.AndroidSourceItemsProvider"/>
|
||||
<packaging.artifactPropertiesProvider implementation="org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesProvider"/>
|
||||
<packaging.artifactType implementation="org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactType"/>
|
||||
|
||||
<compileServer.plugin jar-path="jps/android-jps-plugin.jar"/>
|
||||
<compileServer.plugin jar-path="android-common.jar"/>
|
||||
<compileServer.plugin jar-path="android-rt.jar"/>
|
||||
|
||||
@@ -44,6 +44,9 @@ import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.*;
|
||||
import com.intellij.packaging.artifacts.Artifact;
|
||||
import com.intellij.packaging.artifacts.ArtifactProperties;
|
||||
import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiFile;
|
||||
@@ -52,6 +55,9 @@ import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties;
|
||||
import org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesProvider;
|
||||
import org.jetbrains.android.compiler.artifact.AndroidArtifactSigningMode;
|
||||
import org.jetbrains.android.dom.manifest.Manifest;
|
||||
import org.jetbrains.android.dom.resources.Attr;
|
||||
import org.jetbrains.android.dom.resources.DeclareStyleable;
|
||||
@@ -86,6 +92,7 @@ public class AndroidCompileUtil {
|
||||
|
||||
@NonNls public static final String PROGUARD_CFG_FILE_NAME = "proguard-project.txt";
|
||||
@NonNls public static final String OLD_PROGUARD_CFG_FILE_NAME = "proguard.cfg";
|
||||
public static final String UNSIGNED_SUFFIX = ".unsigned";
|
||||
|
||||
private AndroidCompileUtil() {
|
||||
}
|
||||
@@ -131,7 +138,7 @@ public class AndroidCompileUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
static void addMessages(final CompileContext context, final Map<CompilerMessageCategory, List<String>> messages, Module module) {
|
||||
public static void addMessages(final CompileContext context, final Map<CompilerMessageCategory, List<String>> messages, @Nullable Module module) {
|
||||
addMessages(context, messages, null, module);
|
||||
}
|
||||
|
||||
@@ -150,7 +157,7 @@ public class AndroidCompileUtil {
|
||||
static void addMessages(final CompileContext context,
|
||||
final Map<CompilerMessageCategory, List<String>> messages,
|
||||
@Nullable final Map<VirtualFile, VirtualFile> presentableFilesMap,
|
||||
final Module module) {
|
||||
@Nullable final Module module) {
|
||||
ApplicationManager.getApplication().runReadAction(new Runnable() {
|
||||
public void run() {
|
||||
if (context.getProject().isDisposed()) return;
|
||||
@@ -167,7 +174,7 @@ public class AndroidCompileUtil {
|
||||
line = Integer.parseInt(matcher.group(2));
|
||||
}
|
||||
}
|
||||
context.addMessage(category, '[' + module.getName() + "] " + message, url, line, -1);
|
||||
context.addMessage(category, (module != null ? '[' + module.getName() + "] " : "") + message, url, line, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -619,7 +626,22 @@ public class AndroidCompileUtil {
|
||||
|
||||
public static boolean isReleaseBuild(@NotNull CompileContext context) {
|
||||
final Boolean value = context.getCompileScope().getUserData(RELEASE_BUILD_KEY);
|
||||
return value != null && value.booleanValue();
|
||||
if (value != null && value.booleanValue()) {
|
||||
return true;
|
||||
}
|
||||
final Project project = context.getProject();
|
||||
final Set<Artifact> artifacts = ArtifactCompileScope.getArtifactsToBuild(project, context.getCompileScope(), false);
|
||||
|
||||
if (artifacts != null) {
|
||||
for (Artifact artifact : artifacts) {
|
||||
final ArtifactProperties<?> properties = artifact.getProperties(AndroidArtifactPropertiesProvider.getInstance());
|
||||
if (properties instanceof AndroidApplicationArtifactProperties &&
|
||||
((AndroidApplicationArtifactProperties)properties).getSigningMode() != AndroidArtifactSigningMode.DEBUG) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void setReleaseBuild(@NotNull CompileScope compileScope) {
|
||||
@@ -668,7 +690,10 @@ public class AndroidCompileUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeGenSourceRoot(@NotNull Module module, @Nullable String sourceRootPath, boolean createIfNotExist, boolean exclude) {
|
||||
private static void initializeGenSourceRoot(@NotNull Module module,
|
||||
@Nullable String sourceRootPath,
|
||||
boolean createIfNotExist,
|
||||
boolean exclude) {
|
||||
if (sourceRootPath == null) {
|
||||
return;
|
||||
}
|
||||
@@ -716,31 +741,31 @@ public class AndroidCompileUtil {
|
||||
final Resources resources = pair.getFirst();
|
||||
waitForSmartMode(project);
|
||||
|
||||
ApplicationManager.getApplication().runReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!resources.isValid() || facet.getModule().isDisposed() || project.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
ApplicationManager.getApplication().runReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!resources.isValid() || facet.getModule().isDisposed() || project.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Attr attr : resources.getAttrs()) {
|
||||
final String name = attr.getName().getValue();
|
||||
for (final Attr attr : resources.getAttrs()) {
|
||||
final String name = attr.getName().getValue();
|
||||
|
||||
if (name != null) {
|
||||
resourceSet.add(new ResourceEntry(ResourceType.ATTR.getName(), name));
|
||||
}
|
||||
}
|
||||
|
||||
for (final DeclareStyleable styleable : resources.getDeclareStyleables()) {
|
||||
final String name = styleable.getName().getValue();
|
||||
|
||||
if (name != null) {
|
||||
resourceSet.add(new ResourceEntry(ResourceType.DECLARE_STYLEABLE.getName(), name));
|
||||
}
|
||||
if (name != null) {
|
||||
resourceSet.add(new ResourceEntry(ResourceType.ATTR.getName(), name));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (final DeclareStyleable styleable : resources.getDeclareStyleables()) {
|
||||
final String name = styleable.getName().getValue();
|
||||
|
||||
if (name != null) {
|
||||
resourceSet.add(new ResourceEntry(ResourceType.DECLARE_STYLEABLE.getName(), name));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
waitForSmartMode(project);
|
||||
|
||||
@@ -929,4 +954,22 @@ public class AndroidCompileUtil {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getUnsignedApkPath(@NotNull AndroidFacet facet) {
|
||||
final String apkPath = AndroidRootUtil.getApkPath(facet);
|
||||
return apkPath != null ? AndroidCommonUtils.addSuffixToFileName(apkPath, UNSIGNED_SUFFIX) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T handleExceptionError(@NotNull CompileContext context,
|
||||
@NotNull String messagePrefix,
|
||||
@NotNull Exception e) {
|
||||
reportException(context, messagePrefix, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void reportException(@NotNull CompileContext context, @NotNull String messagePrefix, @NotNull Exception e) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, messagePrefix + e.getClass().getSimpleName() + ": " + e.getMessage(), null, -1, -1);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.jetbrains.android.compiler;
|
||||
|
||||
import com.intellij.compiler.CompilerIOUtil;
|
||||
import com.intellij.compiler.impl.CompilerUtil;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.compiler.*;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -27,6 +28,7 @@ import com.intellij.openapi.roots.ModuleOrderEntry;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.OrderEntry;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.JarFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
@@ -54,8 +56,6 @@ import java.util.*;
|
||||
public class AndroidPackagingCompiler implements PackagingCompiler {
|
||||
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.AndroidPackagingCompiler");
|
||||
|
||||
public static final String UNSIGNED_SUFFIX = ".unsigned";
|
||||
|
||||
public void processOutdatedItem(CompileContext context, String url, @Nullable ValidityState state) {
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ public class AndroidPackagingCompiler implements PackagingCompiler {
|
||||
: item.getResPackagePath();
|
||||
|
||||
final String finalPath = unsigned
|
||||
? AndroidCommonUtils.addSuffixToFileName(item.getFinalPath(), UNSIGNED_SUFFIX)
|
||||
? AndroidCommonUtils.addSuffixToFileName(item.getFinalPath(), AndroidCompileUtil.UNSIGNED_SUFFIX)
|
||||
: item.getFinalPath();
|
||||
|
||||
final String[] sourceRoots = AndroidCompileUtil.toOsPaths(item.getSourceRoots());
|
||||
@@ -247,6 +247,18 @@ public class AndroidPackagingCompiler implements PackagingCompiler {
|
||||
item.getAdditionalNativeLibs(), finalPath, unsigned, item.mySdkPath, item.getCustomKeystorePath(),
|
||||
new ExcludedSourcesFilter(project)));
|
||||
|
||||
if (messages.get(CompilerMessageCategory.ERROR).size() == 0) {
|
||||
if (item.myReleaseBuild == unsigned) {
|
||||
final File dst = new File(
|
||||
AndroidCommonUtils.addSuffixToFileName(item.getFinalPath(), AndroidCommonUtils.ANDROID_FINAL_PACKAGE_FOR_ARTIFACT_SUFFIX));
|
||||
FileUtil.copy(new File(finalPath), dst);
|
||||
CompilerUtil.refreshIOFile(dst);
|
||||
final VirtualFile jar = JarFileSystem.getInstance().refreshAndFindFileByPath(dst.getPath() + "!/");
|
||||
if (jar != null) {
|
||||
jar.refresh(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
AndroidCompileUtil.addMessages(context, messages, item.myModule);
|
||||
}
|
||||
catch (final IOException e) {
|
||||
|
||||
@@ -17,6 +17,8 @@ package org.jetbrains.android.compiler;
|
||||
|
||||
import com.intellij.compiler.CompilerConfiguration;
|
||||
import com.intellij.compiler.CompilerConfigurationImpl;
|
||||
import com.intellij.compiler.options.CompileStepBeforeRun;
|
||||
import com.intellij.facet.ProjectFacetManager;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.compiler.*;
|
||||
@@ -35,7 +37,13 @@ import com.intellij.openapi.roots.OrderEntry;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.packaging.artifacts.Artifact;
|
||||
import com.intellij.packaging.artifacts.ArtifactProperties;
|
||||
import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
|
||||
import com.intellij.util.containers.hash.HashSet;
|
||||
import org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties;
|
||||
import org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesProvider;
|
||||
import org.jetbrains.android.compiler.artifact.AndroidArtifactSigningMode;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.facet.AndroidRootUtil;
|
||||
import org.jetbrains.android.maven.AndroidMavenUtil;
|
||||
@@ -55,10 +63,13 @@ public class AndroidPrecompileTask implements CompileTask {
|
||||
|
||||
@Override
|
||||
public boolean execute(CompileContext context) {
|
||||
if (!checkArtifacts(context)) {
|
||||
return false;
|
||||
}
|
||||
checkAndroidDependencies(context);
|
||||
|
||||
final Project project = context.getProject();
|
||||
|
||||
|
||||
ExcludedEntriesConfiguration configuration =
|
||||
((CompilerConfigurationImpl)CompilerConfiguration.getInstance(project)).getExcludedEntriesConfiguration();
|
||||
|
||||
@@ -107,6 +118,62 @@ public class AndroidPrecompileTask implements CompileTask {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkArtifacts(@NotNull CompileContext context) {
|
||||
final Project project = context.getProject();
|
||||
if (!ProjectFacetManager.getInstance(project).hasFacets(AndroidFacet.ID)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Set<Artifact> artifacts = ArtifactCompileScope.getArtifactsToBuild(project, context.getCompileScope(), false);
|
||||
if (artifacts == null) {
|
||||
return true;
|
||||
}
|
||||
final Set<Artifact> debugArtifacts = new HashSet<Artifact>();
|
||||
final Set<Artifact> releaseArtifacts = new HashSet<Artifact>();
|
||||
|
||||
for (final Artifact artifact : artifacts) {
|
||||
final ArtifactProperties<?> properties = artifact.getProperties(AndroidArtifactPropertiesProvider.getInstance());
|
||||
if (properties instanceof AndroidApplicationArtifactProperties) {
|
||||
final AndroidArtifactSigningMode mode = ((AndroidApplicationArtifactProperties)properties).getSigningMode();
|
||||
if (mode == AndroidArtifactSigningMode.DEBUG) {
|
||||
debugArtifacts.add(artifact);
|
||||
}
|
||||
else {
|
||||
releaseArtifacts.add(artifact);
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean success = true;
|
||||
|
||||
if (debugArtifacts.size() > 0 && releaseArtifacts.size() > 0) {
|
||||
final String message = "Cannot build debug and release Android artifacts in the same session\n" +
|
||||
"Debug artifacts: " + toString(debugArtifacts) + "\n" +
|
||||
"Release artifacts: " + toString(releaseArtifacts);
|
||||
context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (releaseArtifacts.size() > 0 &&
|
||||
CompileStepBeforeRun.getRunConfiguration(context) != null) {
|
||||
final String message = "Cannot build release Android artifacts in the 'make before run' session\n" +
|
||||
"Release artifacts: " + toString(releaseArtifacts);
|
||||
context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private static String toString(Collection<Artifact> artifacts) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
for (Artifact artifact : artifacts) {
|
||||
if (result.length() > 0) {
|
||||
result.append(", ");
|
||||
}
|
||||
result.append(artifact.getName());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static void checkAndroidDependencies(@NotNull CompileContext context) {
|
||||
for (Module module : context.getCompileScope().getAffectedModules()) {
|
||||
final AndroidFacet facet = AndroidFacet.getInstance(module);
|
||||
@@ -142,7 +209,7 @@ public class AndroidPrecompileTask implements CompileTask {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void clearResCache(@NotNull AndroidFacet facet, @NotNull CompileContext context) {
|
||||
final Module module = facet.getModule();
|
||||
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.openapi.compiler.CompileContext;
|
||||
import com.intellij.openapi.compiler.CompilerMessageCategory;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VfsUtilCore;
|
||||
import com.intellij.packaging.artifacts.Artifact;
|
||||
import com.intellij.packaging.artifacts.ArtifactProperties;
|
||||
import com.intellij.packaging.ui.ArtifactEditorContext;
|
||||
import com.intellij.packaging.ui.ArtifactPropertiesEditor;
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil;
|
||||
import com.intellij.util.xmlb.annotations.Transient;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.jetbrains.android.compiler.AndroidCompileUtil;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.AndroidCommonUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public class AndroidApplicationArtifactProperties extends ArtifactProperties<AndroidApplicationArtifactProperties> {
|
||||
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties");
|
||||
|
||||
private AndroidArtifactSigningMode mySigningMode = AndroidArtifactSigningMode.RELEASE_UNSIGNED;
|
||||
private String myKeyStoreUrl = "";
|
||||
private String myKeyStorePassword = "";
|
||||
private String myKeyAlias = "";
|
||||
private String myKeyPassword = "";
|
||||
|
||||
@Override
|
||||
public void onBuildFinished(@NotNull Artifact artifact, @NotNull CompileContext context) {
|
||||
if (!(artifact.getArtifactType() instanceof AndroidApplicationArtifactType) ||
|
||||
mySigningMode != AndroidArtifactSigningMode.RELEASE_SIGNED) {
|
||||
return;
|
||||
}
|
||||
final String errorPrefix = "[Artifact '" + artifact.getName() + "'] ";
|
||||
final Pair<PrivateKey, X509Certificate> pair = getPrivateKeyAndCertificate(context, errorPrefix);
|
||||
if (pair == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String artifactFilePath = artifact.getOutputFilePath();
|
||||
final String prefix = "Cannot sign artifact " + artifact.getName() + ": ";
|
||||
|
||||
if (artifactFilePath == null) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, prefix + "output path is not specified", null, -1, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
final File artifactFile = new File(artifactFilePath);
|
||||
if (!artifactFile.exists()) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, prefix + "file " + artifactFilePath + " hasn't been generated", null, -1, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
File tmpDir = null;
|
||||
try {
|
||||
tmpDir = FileUtil.createTempDirectory("android_artifact", "tmp");
|
||||
final File tmpArtifact = new File(tmpDir, "tmpArtifact.apk");
|
||||
FileUtil.copy(artifactFile, tmpArtifact);
|
||||
|
||||
if (!FileUtil.delete(artifactFile)) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, "Cannot delete file " + artifactFile.getPath(), null, -1, -1);
|
||||
return;
|
||||
}
|
||||
AndroidCommonUtils.signApk(tmpArtifact, artifactFile, pair.getFirst(), pair.getSecond());
|
||||
}
|
||||
catch (IOException e) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, prefix + "I/O error: " + e.getMessage(), null, -1, -1);
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
AndroidCompileUtil.reportException(context, prefix, e);
|
||||
}
|
||||
finally {
|
||||
if (tmpDir != null) {
|
||||
FileUtil.delete(tmpDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArtifactPropertiesEditor createEditor(@NotNull ArtifactEditorContext context) {
|
||||
return new AndroidArtifactPropertiesEditor(this, context.getProject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidApplicationArtifactProperties getState() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(AndroidApplicationArtifactProperties state) {
|
||||
XmlSerializerUtil.copyBean(state, this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Pair<PrivateKey, X509Certificate> getPrivateKeyAndCertificate(@NotNull CompileContext context, @NotNull String errorPrefix) {
|
||||
final String keyStoreFilePath = myKeyStoreUrl != null ? VfsUtilCore.urlToPath(myKeyStoreUrl) : "";
|
||||
|
||||
if (keyStoreFilePath.length() == 0) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, errorPrefix + "Key store file is not specified", null, -1, -1);
|
||||
return null;
|
||||
}
|
||||
if (myKeyStorePassword == null || myKeyStorePassword.length() == 0) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, errorPrefix + "Key store password is not specified", null, -1, -1);
|
||||
return null;
|
||||
}
|
||||
if (myKeyAlias == null || myKeyAlias.length() == 0) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, errorPrefix + "Key alias is not specified", null, -1, -1);
|
||||
return null;
|
||||
}
|
||||
if (myKeyPassword == null || myKeyPassword.length() == 0) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR, errorPrefix + "Key password is not specified", null, -1, -1);
|
||||
return null;
|
||||
}
|
||||
final File keyStoreFile = new File(keyStoreFilePath);
|
||||
final String keystorePasswordStr = getPlainKeystorePassword();
|
||||
final char[] keystorePassword = keystorePasswordStr.toCharArray();
|
||||
|
||||
final String keyPasswordStr = getPlainKeyPassword();
|
||||
final char[] keyPassword = keyPasswordStr.toCharArray();
|
||||
|
||||
final KeyStore keyStore;
|
||||
InputStream is = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
is = new FileInputStream(keyStoreFile);
|
||||
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(is, keystorePassword);
|
||||
|
||||
final KeyStore.PrivateKeyEntry entry =
|
||||
(KeyStore.PrivateKeyEntry)keyStore.getEntry(myKeyAlias, new KeyStore.PasswordProtection(keyPassword));
|
||||
if (entry == null) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR,
|
||||
errorPrefix + AndroidBundle.message("android.extract.package.cannot.find.key.error", myKeyAlias), null, -1, -1);
|
||||
return null;
|
||||
}
|
||||
|
||||
final PrivateKey privateKey = entry.getPrivateKey();
|
||||
final Certificate certificate = entry.getCertificate();
|
||||
if (privateKey == null || certificate == null) {
|
||||
context.addMessage(CompilerMessageCategory.ERROR,
|
||||
errorPrefix + AndroidBundle.message("android.extract.package.cannot.find.key.error", myKeyAlias), null, -1, -1);
|
||||
return null;
|
||||
}
|
||||
return Pair.create(privateKey, (X509Certificate)certificate);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
|
||||
}
|
||||
catch (KeyStoreException e) {
|
||||
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
|
||||
}
|
||||
catch (CertificateException e) {
|
||||
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
|
||||
}
|
||||
catch (UnrecoverableEntryException e) {
|
||||
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
|
||||
}
|
||||
finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AndroidArtifactSigningMode getSigningMode() {
|
||||
return mySigningMode;
|
||||
}
|
||||
|
||||
public void setSigningMode(AndroidArtifactSigningMode signingMode) {
|
||||
mySigningMode = signingMode;
|
||||
}
|
||||
|
||||
public String getKeyStoreUrl() {
|
||||
return myKeyStoreUrl;
|
||||
}
|
||||
|
||||
public String getKeyStorePassword() {
|
||||
return myKeyStorePassword;
|
||||
}
|
||||
|
||||
public String getKeyAlias() {
|
||||
return myKeyAlias;
|
||||
}
|
||||
|
||||
public String getKeyPassword() {
|
||||
return myKeyPassword;
|
||||
}
|
||||
|
||||
public void setKeyStoreUrl(String keyStoreUrl) {
|
||||
myKeyStoreUrl = keyStoreUrl;
|
||||
}
|
||||
|
||||
public void setKeyStorePassword(String keyStorePassword) {
|
||||
myKeyStorePassword = keyStorePassword;
|
||||
}
|
||||
|
||||
public void setKeyAlias(String keyAlias) {
|
||||
myKeyAlias = keyAlias;
|
||||
}
|
||||
|
||||
public void setKeyPassword(String keyPassword) {
|
||||
myKeyPassword = keyPassword;
|
||||
}
|
||||
|
||||
@Transient
|
||||
@NotNull
|
||||
public String getPlainKeystorePassword() {
|
||||
return new String(new Base64().decode(myKeyStorePassword.getBytes()));
|
||||
}
|
||||
|
||||
@Transient
|
||||
public void setPlainKeystorePassword(@NotNull String password) {
|
||||
myKeyStorePassword = new String(new Base64().encode(password.getBytes()));
|
||||
}
|
||||
|
||||
@Transient
|
||||
@NotNull
|
||||
public String getPlainKeyPassword() {
|
||||
return new String(new Base64().decode(myKeyPassword.getBytes()));
|
||||
}
|
||||
|
||||
@Transient
|
||||
public void setPlainKeyPassword(@NotNull String password) {
|
||||
myKeyPassword = new String(new Base64().encode(password.getBytes()));
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.LibrarySourceItem;
|
||||
import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.ModuleOutputSourceItem;
|
||||
import com.intellij.packaging.artifacts.ArtifactTemplate;
|
||||
import com.intellij.packaging.artifacts.ArtifactType;
|
||||
import com.intellij.packaging.elements.CompositePackagingElement;
|
||||
import com.intellij.packaging.elements.PackagingElementFactory;
|
||||
import com.intellij.packaging.elements.PackagingElementOutputKind;
|
||||
import com.intellij.packaging.elements.PackagingElementResolvingContext;
|
||||
import com.intellij.packaging.impl.artifacts.ArtifactUtil;
|
||||
import com.intellij.packaging.impl.artifacts.PlainArtifactType;
|
||||
import com.intellij.packaging.ui.PackagingSourceItem;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidApplicationArtifactType extends ArtifactType {
|
||||
public AndroidApplicationArtifactType() {
|
||||
super("apk", "Android Application");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return PlainArtifactType.ARTIFACT_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultPathFor(@NotNull PackagingElementOutputKind kind) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuitableItem(@NotNull PackagingSourceItem item) {
|
||||
return !(item instanceof ModuleOutputSourceItem || item instanceof LibrarySourceItem);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CompositePackagingElement<?> createRootElement(@NotNull String artifactName) {
|
||||
return PackagingElementFactory.getInstance().createArchive(ArtifactUtil.suggestArtifactFileName(artifactName) + ".apk");
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<? extends ArtifactTemplate> getNewArtifactTemplates(@NotNull PackagingElementResolvingContext context) {
|
||||
return Collections.singletonList(new MyTemplate(context));
|
||||
}
|
||||
|
||||
private class MyTemplate extends ArtifactTemplate {
|
||||
protected PackagingElementResolvingContext myContext;
|
||||
|
||||
public MyTemplate(@NotNull PackagingElementResolvingContext context) {
|
||||
myContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPresentableName() {
|
||||
return "From module...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewArtifactConfiguration createArtifact() {
|
||||
final List<Module> modules = new ArrayList<Module>();
|
||||
|
||||
for (Module module : myContext.getModulesProvider().getModules()) {
|
||||
final AndroidFacet facet = AndroidFacet.getInstance(module);
|
||||
|
||||
if (facet != null && !facet.getConfiguration().LIBRARY_PROJECT) {
|
||||
modules.add(module);
|
||||
}
|
||||
}
|
||||
|
||||
final AndroidFacet facet = AndroidArtifactUtil.chooseAndroidApplicationModule(myContext.getProject(), modules);
|
||||
if (facet == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final CompositePackagingElement<?> rootElement =
|
||||
AndroidApplicationArtifactType.this.createRootElement(facet.getModule().getName());
|
||||
rootElement.addFirstChild(new AndroidFinalPackageElement(myContext.getProject(), facet));
|
||||
return new NewArtifactConfiguration(rootElement, facet.getModule().getName(), AndroidApplicationArtifactType.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesEditor">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<vspacer id="39e8a">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="8bfdb" class="javax.swing.JRadioButton" binding="myReleaseSignedRadio">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<enabled value="true"/>
|
||||
<text value="Release &signed"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="791e9" class="javax.swing.JRadioButton" binding="myReleaseUnsignedRadio">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Release &unsigned"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="ce323" class="javax.swing.JRadioButton" binding="myDebugRadio">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Debug"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="b6cd" binding="myReleaseKeyPanel" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="2" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="587da" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="779"/>
|
||||
<text value="&Key store path: "/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="e6a18" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="8c694"/>
|
||||
<text value="Key store &password:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="8c694" class="javax.swing.JPasswordField" binding="myKeyStorePasswordField">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="5d3f2" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="9b706"/>
|
||||
<text value="K&ey alias: "/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="76382" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="3398"/>
|
||||
<text value="Key pass&word:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="3398" class="javax.swing.JPasswordField" binding="myKeyPasswordField">
|
||||
<constraints>
|
||||
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="9b706" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myKeyAliasField">
|
||||
<constraints>
|
||||
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="779" class="javax.swing.JTextField" binding="myKeyStorePathField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<grid id="bdf4e" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="48c0d" class="javax.swing.JButton" binding="myCreateKeyStoreButton">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Create new..."/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="a6531" class="javax.swing.JButton" binding="myLoadKeyStoreButton">
|
||||
<constraints>
|
||||
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Choose existing..."/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="ed5f1">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<buttonGroups>
|
||||
<group name="myGroup1">
|
||||
<member id="ce323"/>
|
||||
<member id="8bfdb"/>
|
||||
<member id="791e9"/>
|
||||
</group>
|
||||
</buttonGroups>
|
||||
</form>
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.android.annotations.NonNull;
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.fileChooser.FileChooser;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtilCore;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.packaging.ui.ArtifactPropertiesEditor;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.*;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidArtifactPropertiesEditor extends ArtifactPropertiesEditor {
|
||||
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties");
|
||||
|
||||
private final AndroidApplicationArtifactProperties myProperties;
|
||||
|
||||
private JPanel myPanel;
|
||||
private JRadioButton myDebugRadio;
|
||||
private JRadioButton myReleaseSignedRadio;
|
||||
private JRadioButton myReleaseUnsignedRadio;
|
||||
private JPanel myReleaseKeyPanel;
|
||||
private JPasswordField myKeyStorePasswordField;
|
||||
private JTextField myKeyStorePathField;
|
||||
private JPasswordField myKeyPasswordField;
|
||||
private TextFieldWithBrowseButton myKeyAliasField;
|
||||
private JButton myLoadKeyStoreButton;
|
||||
private JButton myCreateKeyStoreButton;
|
||||
|
||||
public AndroidArtifactPropertiesEditor(@NonNull AndroidApplicationArtifactProperties properties, @NotNull final Project project) {
|
||||
myProperties = properties;
|
||||
|
||||
final ActionListener listener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
UIUtil.setEnabled(myReleaseKeyPanel, myReleaseSignedRadio.isSelected(), true);
|
||||
}
|
||||
};
|
||||
myDebugRadio.addActionListener(listener);
|
||||
myReleaseUnsignedRadio.addActionListener(listener);
|
||||
myReleaseSignedRadio.addActionListener(listener);
|
||||
|
||||
myLoadKeyStoreButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
final String defaultPath = getKeyStorePath();
|
||||
final VirtualFile defaultFile = LocalFileSystem.getInstance().findFileByPath(defaultPath);
|
||||
final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor();
|
||||
final VirtualFile file = FileChooser.chooseFile(descriptor, myPanel, project, defaultFile);
|
||||
if (file != null) {
|
||||
myKeyStorePathField.setText(FileUtil.toSystemDependentName(file.getPath()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myCreateKeyStoreButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
final NewKeyStoreDialog dialog = new NewKeyStoreDialog(project, myKeyStorePathField.getText());
|
||||
dialog.show();
|
||||
|
||||
if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
myKeyStorePathField.setText(dialog.getKeyStorePath());
|
||||
myKeyStorePasswordField.setText(String.valueOf(dialog.getKeyStorePassword()));
|
||||
myKeyAliasField.setText(dialog.getKeyAlias());
|
||||
myKeyPasswordField.setText(String.valueOf(dialog.getKeyPassword()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myKeyAliasField.getButton().addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
final List<String> keys = loadExistingKeys();
|
||||
if (keys == null) {
|
||||
return;
|
||||
}
|
||||
final ChooseKeyDialog dialog =
|
||||
new ChooseKeyDialog(project, getKeyStorePath(), myKeyStorePasswordField.getPassword(), keys, getKeyAlias());
|
||||
dialog.show();
|
||||
|
||||
if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
final String chosenKey = dialog.getChosenKey();
|
||||
if (chosenKey != null) {
|
||||
myKeyAliasField.setText(chosenKey);
|
||||
}
|
||||
|
||||
final char[] password = dialog.getChosenKeyPassword();
|
||||
if (password != null) {
|
||||
myKeyPasswordField.setText(String.valueOf(password));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getKeyStorePath() {
|
||||
return myKeyStorePathField.getText().trim();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<String> loadExistingKeys() {
|
||||
final String errorPrefix = "Cannot load key store: ";
|
||||
InputStream is = null;
|
||||
try {
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
is = new FileInputStream(new File(getKeyStorePath()));
|
||||
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(is, myKeyStorePasswordField.getPassword());
|
||||
return AndroidUtils.toList(keyStore.aliases());
|
||||
}
|
||||
catch (KeyStoreException e) {
|
||||
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
|
||||
return null;
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
|
||||
return null;
|
||||
}
|
||||
catch (CertificateException e) {
|
||||
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
|
||||
return null;
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName() {
|
||||
return "Android";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent createComponent() {
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return getSigningMode() != myProperties.getSigningMode() ||
|
||||
!getKeyStoreFileUrl().equals(myProperties.getKeyStoreUrl()) ||
|
||||
!getKeyStorePassword().equals(myProperties.getPlainKeystorePassword()) ||
|
||||
!getKeyAlias().equals(myProperties.getKeyAlias()) ||
|
||||
!getKeyPassword().equals(myProperties.getPlainKeyPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
myProperties.setSigningMode(getSigningMode());
|
||||
myProperties.setKeyStoreUrl(getKeyStoreFileUrl());
|
||||
myProperties.setPlainKeystorePassword(getKeyStorePassword());
|
||||
myProperties.setKeyAlias(getKeyAlias());
|
||||
myProperties.setPlainKeyPassword(getKeyPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
switch (myProperties.getSigningMode()) {
|
||||
case RELEASE_UNSIGNED:
|
||||
myReleaseUnsignedRadio.setSelected(true);
|
||||
myDebugRadio.setSelected(false);
|
||||
myReleaseSignedRadio.setSelected(false);
|
||||
break;
|
||||
case DEBUG:
|
||||
myReleaseUnsignedRadio.setSelected(false);
|
||||
myDebugRadio.setSelected(true);
|
||||
myReleaseSignedRadio.setSelected(false);
|
||||
break;
|
||||
case RELEASE_SIGNED:
|
||||
myReleaseUnsignedRadio.setSelected(false);
|
||||
myDebugRadio.setSelected(false);
|
||||
myReleaseSignedRadio.setSelected(true);
|
||||
break;
|
||||
}
|
||||
final String keyStoreUrl = myProperties.getKeyStoreUrl();
|
||||
myKeyStorePathField.setText(keyStoreUrl != null ? VfsUtilCore.urlToPath(keyStoreUrl) : "");
|
||||
myKeyStorePasswordField.setText(myProperties.getPlainKeystorePassword());
|
||||
|
||||
final String keyAlias = myProperties.getKeyAlias();
|
||||
myKeyAliasField.setText(keyAlias != null ? keyAlias : "");
|
||||
myKeyPasswordField.setText(myProperties.getPlainKeyPassword());
|
||||
|
||||
UIUtil.setEnabled(myReleaseKeyPanel, myProperties.getSigningMode() == AndroidArtifactSigningMode.RELEASE_SIGNED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disposeUIResources() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private AndroidArtifactSigningMode getSigningMode() {
|
||||
if (myDebugRadio.isSelected()) {
|
||||
return AndroidArtifactSigningMode.DEBUG;
|
||||
}
|
||||
else if (myReleaseSignedRadio.isSelected()) {
|
||||
return AndroidArtifactSigningMode.RELEASE_SIGNED;
|
||||
}
|
||||
return AndroidArtifactSigningMode.RELEASE_UNSIGNED;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getKeyStoreFileUrl() {
|
||||
final String path = getKeyStorePath();
|
||||
return VfsUtilCore.pathToUrl(path);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getKeyStorePassword() {
|
||||
return String.valueOf(myKeyStorePasswordField.getPassword());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getKeyAlias() {
|
||||
return myKeyAliasField.getText().trim();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getKeyPassword() {
|
||||
return String.valueOf(myKeyPasswordField.getPassword());
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.packaging.artifacts.ArtifactProperties;
|
||||
import com.intellij.packaging.artifacts.ArtifactPropertiesProvider;
|
||||
import com.intellij.packaging.artifacts.ArtifactType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidArtifactPropertiesProvider extends ArtifactPropertiesProvider {
|
||||
protected AndroidArtifactPropertiesProvider() {
|
||||
super("android-properties");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailableFor(@NotNull ArtifactType type) {
|
||||
return type instanceof AndroidApplicationArtifactType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ArtifactProperties<?> createProperties(@NotNull ArtifactType artifactType) {
|
||||
return new AndroidApplicationArtifactProperties();
|
||||
}
|
||||
|
||||
public static AndroidArtifactPropertiesProvider getInstance() {
|
||||
return EP_NAME.findExtension(AndroidArtifactPropertiesProvider.class);
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.compiler.CompileContext;
|
||||
import com.intellij.openapi.compiler.CompileScope;
|
||||
import com.intellij.openapi.compiler.PackagingCompiler;
|
||||
import com.intellij.openapi.compiler.ValidityState;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidArtifactSigningCompiler implements PackagingCompiler {
|
||||
@Override
|
||||
public void processOutdatedItem(CompileContext context, String url, @Nullable ValidityState state) {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ProcessingItem[] getProcessingItems(CompileContext context) {
|
||||
return ApplicationManager.getApplication().runReadAction(new Computable<ProcessingItem[]>() {
|
||||
@Override
|
||||
public ProcessingItem[] compute() {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessingItem[] process(CompileContext context, ProcessingItem[] items) {
|
||||
return new ProcessingItem[0]; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Android Artifact Signing Compiler";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateConfiguration(CompileScope scope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidityState createValidityState(DataInput in) throws IOException {
|
||||
return new MyValidityState(in);
|
||||
}
|
||||
|
||||
private static class MyProcessingItem implements ProcessingItem {
|
||||
private final VirtualFile myApkFile;
|
||||
private final AndroidArtifactSigningMode mySigningMode;
|
||||
private final String myDebugKeyStorePath;
|
||||
private final MyValidityState myValidityState;
|
||||
|
||||
private MyProcessingItem(@NotNull VirtualFile apkFile,
|
||||
@NotNull AndroidArtifactSigningMode signingMode,
|
||||
@Nullable String debugKeyStorePath) {
|
||||
myApkFile = apkFile;
|
||||
mySigningMode = signingMode;
|
||||
myDebugKeyStorePath = debugKeyStorePath;
|
||||
|
||||
myValidityState = new MyValidityState(myApkFile.getModificationStamp(),
|
||||
mySigningMode.name(),
|
||||
myDebugKeyStorePath);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public VirtualFile getFile() {
|
||||
return myApkFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidityState getValidityState() {
|
||||
return myValidityState;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyValidityState implements ValidityState {
|
||||
private final long myApkFileTimestamp;
|
||||
private final String mySigningMode;
|
||||
private final String myDebugKeyStorePath;
|
||||
|
||||
private MyValidityState(long apkFileTimestamp, @NotNull String signingMode, @NotNull String debugKeyStorePath) {
|
||||
myApkFileTimestamp = apkFileTimestamp;
|
||||
mySigningMode = signingMode;
|
||||
myDebugKeyStorePath = debugKeyStorePath;
|
||||
}
|
||||
|
||||
private MyValidityState(@NotNull DataInput in) throws IOException {
|
||||
myApkFileTimestamp = in.readLong();
|
||||
mySigningMode = in.readUTF();
|
||||
myDebugKeyStorePath = in.readUTF();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsTo(ValidityState otherState) {
|
||||
if (!(otherState instanceof MyValidityState)) {
|
||||
return false;
|
||||
}
|
||||
final MyValidityState state = (MyValidityState)otherState;
|
||||
return state.myApkFileTimestamp == myApkFileTimestamp &&
|
||||
state.mySigningMode.equals(mySigningMode) &&
|
||||
state.myDebugKeyStorePath.equals(myDebugKeyStorePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(DataOutput out) throws IOException {
|
||||
out.writeLong(myApkFileTimestamp);
|
||||
out.writeUTF(mySigningMode);
|
||||
out.writeUTF(myDebugKeyStorePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public enum AndroidArtifactSigningMode {
|
||||
DEBUG, RELEASE_UNSIGNED, RELEASE_SIGNED
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ui.configuration.ChooseModulesDialog;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.packaging.artifacts.Artifact;
|
||||
import com.intellij.packaging.impl.artifacts.ArtifactUtil;
|
||||
import com.intellij.packaging.ui.ArtifactEditorContext;
|
||||
import com.intellij.util.Processor;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidArtifactUtil {
|
||||
private AndroidArtifactUtil() {
|
||||
}
|
||||
|
||||
public static boolean containsAndroidPackage(ArtifactEditorContext editorContext, Artifact artifact) {
|
||||
return !ArtifactUtil
|
||||
.processPackagingElements(artifact, AndroidFinalPackageElementType.getInstance(), new Processor<AndroidFinalPackageElement>() {
|
||||
public boolean process(AndroidFinalPackageElement e) {
|
||||
return false;
|
||||
}
|
||||
}, editorContext, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AndroidFacet chooseAndroidApplicationModule(@NotNull Project project, @NotNull List<Module> modules) {
|
||||
final ChooseModulesDialog dialog = new ChooseModulesDialog(project, modules, "Select Module",
|
||||
"Selected Android application module will be included in the created artifact with all dependencies");
|
||||
dialog.setSingleSelectionMode();
|
||||
dialog.show();
|
||||
final List<Module> selected = dialog.getChosenElements();
|
||||
if (selected.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
assert selected.size() == 1;
|
||||
final Module module = selected.get(0);
|
||||
final String moduleName = module.getName();
|
||||
|
||||
final AndroidFacet facet = AndroidFacet.getInstance(module);
|
||||
if (facet == null) {
|
||||
final String message = "Cannot find Android facet for module " + moduleName;
|
||||
Messages.showErrorDialog(project, message, CommonBundle.getErrorTitle());
|
||||
return null;
|
||||
}
|
||||
return facet;
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.compiler.ant.Generator;
|
||||
import com.intellij.facet.pointers.FacetPointer;
|
||||
import com.intellij.facet.pointers.FacetPointersManager;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.JarFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.packaging.artifacts.ArtifactType;
|
||||
import com.intellij.packaging.elements.*;
|
||||
import com.intellij.packaging.impl.elements.FacetBasedPackagingElement;
|
||||
import com.intellij.packaging.impl.elements.ModuleOutputPackagingElement;
|
||||
import com.intellij.packaging.impl.ui.DelegatedPackagingElementPresentation;
|
||||
import com.intellij.packaging.ui.ArtifactEditorContext;
|
||||
import com.intellij.packaging.ui.PackagingElementPresentation;
|
||||
import com.intellij.util.xmlb.annotations.Attribute;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.facet.AndroidRootUtil;
|
||||
import org.jetbrains.android.util.AndroidCommonUtils;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidFinalPackageElement extends PackagingElement<AndroidFinalPackageElement.AndroidFinalPackageElementState>
|
||||
implements FacetBasedPackagingElement, ModuleOutputPackagingElement {
|
||||
|
||||
@NonNls static final String FACET_ATTRIBUTE = "facet";
|
||||
|
||||
private FacetPointer<AndroidFacet> myFacetPointer;
|
||||
private final Project myProject;
|
||||
|
||||
public AndroidFinalPackageElement(@NotNull Project project, @Nullable AndroidFacet facet) {
|
||||
super(AndroidFinalPackageElementType.getInstance());
|
||||
myProject = project;
|
||||
myFacetPointer = facet != null ? FacetPointersManager.getInstance(myProject).create(facet) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
|
||||
return new DelegatedPackagingElementPresentation(new AndroidFinalPackagePresentation(myFacetPointer));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getApkPath() {
|
||||
if (myFacetPointer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final AndroidFacet facet = myFacetPointer.getFacet();
|
||||
if (facet == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String apkPath = AndroidRootUtil.getApkPath(facet);
|
||||
final String path = apkPath != null
|
||||
? AndroidCommonUtils.addSuffixToFileName(apkPath, AndroidCommonUtils.ANDROID_FINAL_PACKAGE_FOR_ARTIFACT_SUFFIX)
|
||||
: null;
|
||||
return path != null
|
||||
? FileUtil.toSystemIndependentName(path) + "!/"
|
||||
: null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext,
|
||||
@NotNull AntCopyInstructionCreator creator,
|
||||
@NotNull ArtifactAntGenerationContext generationContext,
|
||||
@NotNull ArtifactType artifactType) {
|
||||
final String apkPath = getApkPath();
|
||||
if (apkPath != null) {
|
||||
return Collections.singletonList(creator.createExtractedDirectoryInstruction(apkPath));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
|
||||
@NotNull PackagingElementResolvingContext resolvingContext,
|
||||
@NotNull ArtifactIncrementalCompilerContext compilerContext,
|
||||
@NotNull ArtifactType artifactType) {
|
||||
final String apkPath = getApkPath();
|
||||
if (apkPath != null) {
|
||||
final VirtualFile apk = JarFileSystem.getInstance().findFileByPath(apkPath);
|
||||
if (apk != null && apk.isValid() && apk.isDirectory()) {
|
||||
creator.addDirectoryCopyInstructions(apk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AndroidFacet getFacet() {
|
||||
return myFacetPointer != null ? myFacetPointer.getFacet() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqualTo(@NotNull PackagingElement<?> element) {
|
||||
if (!(element instanceof AndroidFinalPackageElement)) {
|
||||
return false;
|
||||
}
|
||||
final AndroidFinalPackageElement packageElement = (AndroidFinalPackageElement)element;
|
||||
|
||||
return myFacetPointer == null
|
||||
? packageElement.myFacetPointer == null
|
||||
: myFacetPointer.equals(packageElement.myFacetPointer);
|
||||
}
|
||||
|
||||
public AndroidFinalPackageElementState getState() {
|
||||
final AndroidFinalPackageElementState state = new AndroidFinalPackageElementState();
|
||||
state.myFacetPointer = myFacetPointer != null ? myFacetPointer.getId() : null;
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidFacet findFacet(@NotNull PackagingElementResolvingContext context) {
|
||||
return myFacetPointer != null ? myFacetPointer.findFacet(context.getModulesProvider(), context.getFacetsProvider()) : null;
|
||||
}
|
||||
|
||||
public void loadState(AndroidFinalPackageElementState state) {
|
||||
myFacetPointer = state.myFacetPointer != null
|
||||
? FacetPointersManager.getInstance(myProject).<AndroidFacet>create(state.myFacetPointer)
|
||||
: null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return myFacetPointer != null ? myFacetPointer.getModuleName() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Module findModule(PackagingElementResolvingContext context) {
|
||||
final AndroidFacet facet = findFacet(context);
|
||||
return facet != null ? facet.getModule() : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<VirtualFile> getSourceRoots(PackagingElementResolvingContext context) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static class AndroidFinalPackageElementState {
|
||||
|
||||
@Attribute(FACET_ATTRIBUTE)
|
||||
public String myFacetPointer;
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.packaging.artifacts.Artifact;
|
||||
import com.intellij.packaging.elements.CompositePackagingElement;
|
||||
import com.intellij.packaging.elements.PackagingElement;
|
||||
import com.intellij.packaging.elements.PackagingElementType;
|
||||
import com.intellij.packaging.ui.ArtifactEditorContext;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidFinalPackageElementType extends PackagingElementType<AndroidFinalPackageElement> {
|
||||
@NonNls public static final String TYPE_ID = "android-final-package";
|
||||
|
||||
protected AndroidFinalPackageElementType() {
|
||||
super(TYPE_ID, "Android Final Package");
|
||||
}
|
||||
|
||||
public static AndroidFinalPackageElementType getInstance() {
|
||||
return getInstance(AndroidFinalPackageElementType.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getCreateElementIcon() {
|
||||
return AndroidUtils.ANDROID_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
|
||||
return getAndroidApplicationFacets(context, context.getModulesProvider().getModules()).size() > 0 &&
|
||||
!AndroidArtifactUtil.containsAndroidPackage(context, artifact);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<AndroidFacet> getAndroidApplicationFacets(@NotNull ArtifactEditorContext context, @NotNull Module[] modules) {
|
||||
final List<AndroidFacet> result = new ArrayList<AndroidFacet>();
|
||||
for (Module module : modules) {
|
||||
for (AndroidFacet facet : context.getFacetsProvider().getFacetsByType(module, AndroidFacet.ID)) {
|
||||
if (!facet.getConfiguration().LIBRARY_PROJECT) {
|
||||
result.add(facet);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<Module> facetsToModules(@NotNull Collection<AndroidFacet> facets) {
|
||||
final List<Module> result = new ArrayList<Module>(facets.size());
|
||||
for (AndroidFacet facet : facets) {
|
||||
result.add(facet.getModule());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<? extends PackagingElement<?>> chooseAndCreate(@NotNull ArtifactEditorContext context,
|
||||
@NotNull Artifact artifact,
|
||||
@NotNull CompositePackagingElement<?> parent) {
|
||||
final List<AndroidFacet> facets = getAndroidApplicationFacets(context, context.getModulesProvider().getModules());
|
||||
final List<Module> modules = facetsToModules(facets);
|
||||
|
||||
final AndroidFacet facet = AndroidArtifactUtil.chooseAndroidApplicationModule(context.getProject(), modules);
|
||||
if (facet == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(new AndroidFinalPackageElement(context.getProject(), facet));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public AndroidFinalPackageElement createEmpty(@NotNull Project project) {
|
||||
return new AndroidFinalPackageElement(project, null);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.facet.pointers.FacetPointer;
|
||||
import com.intellij.ide.projectView.PresentationData;
|
||||
import com.intellij.packaging.ui.SourceItemPresentation;
|
||||
import com.intellij.packaging.ui.SourceItemWeights;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
class AndroidFinalPackagePresentation extends SourceItemPresentation {
|
||||
private final FacetPointer<AndroidFacet> myFacetPointer;
|
||||
|
||||
public AndroidFinalPackagePresentation(@Nullable FacetPointer<AndroidFacet> facetPointer) {
|
||||
myFacetPointer = facetPointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPresentableName() {
|
||||
final String moduleName = myFacetPointer != null ? myFacetPointer.getModuleName() : "<unknown>";
|
||||
return "'" + moduleName + "' Android final package";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(@NotNull PresentationData presentationData,
|
||||
SimpleTextAttributes mainAttributes,
|
||||
SimpleTextAttributes commentAttributes) {
|
||||
presentationData.setIcons(AndroidFacet.getFacetType().getIcon());
|
||||
presentationData.addText(getPresentableName(), mainAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWeight() {
|
||||
return SourceItemWeights.LIBRARY_WEIGHT - 5;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.facet.pointers.FacetPointersManager;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.FacetBasedPackagingSourceItemsProvider;
|
||||
import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.ModuleSourceItemGroup;
|
||||
import com.intellij.packaging.artifacts.Artifact;
|
||||
import com.intellij.packaging.ui.ArtifactEditorContext;
|
||||
import com.intellij.packaging.ui.PackagingSourceItem;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class AndroidSourceItemsProvider extends FacetBasedPackagingSourceItemsProvider<AndroidFacet, AndroidFinalPackageElement> {
|
||||
public AndroidSourceItemsProvider() {
|
||||
super(AndroidFacet.ID, AndroidFinalPackageElementType.getInstance());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AndroidFinalPackagePresentation createPresentation(AndroidFacet facet) {
|
||||
return new AndroidFinalPackagePresentation(FacetPointersManager.getInstance(facet.getModule().getProject()).create(facet));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AndroidFinalPackageElement createElement(ArtifactEditorContext context, AndroidFacet facet) {
|
||||
return new AndroidFinalPackageElement(context.getProject(), facet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<? extends PackagingSourceItem> getSourceItems(@NotNull ArtifactEditorContext editorContext,
|
||||
@NotNull Artifact artifact,
|
||||
@Nullable PackagingSourceItem parent) {
|
||||
|
||||
if (parent instanceof ModuleSourceItemGroup && !AndroidArtifactUtil.containsAndroidPackage(editorContext, artifact)) {
|
||||
final Module module = ((ModuleSourceItemGroup)parent).getModule();
|
||||
final Set<AndroidFacet> facets =
|
||||
new HashSet<AndroidFacet>(editorContext.getFacetsProvider().getFacetsByType(module, AndroidFacet.ID));
|
||||
return Collections.singletonList(new FacetBasedSourceItem<AndroidFacet>(this, facets.iterator().next()));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AndroidFacet getFacet(AndroidFinalPackageElement element) {
|
||||
return element.getFacet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.compiler.artifact.ChooseKeyDialog">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="6262e" class="com.intellij.ui.components.JBRadioButton" binding="myUseExistingKeyRadioButton">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Use an existing key:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="ba9f7">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="91b01" class="com.intellij.ui.components.JBRadioButton" binding="myCreateNewKeyRadioButton">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Create a &new key"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="97118" binding="myNewKeyPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="2" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
<component id="b9c2f" class="javax.swing.JComboBox" binding="myKeyCombo">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<buttonGroups>
|
||||
<group name="myGroup1">
|
||||
<member id="6262e"/>
|
||||
<member id="91b01"/>
|
||||
</group>
|
||||
</buttonGroups>
|
||||
<inspectionSuppressions>
|
||||
<suppress inspection="NoLabelFor" id="b9c2f"/>
|
||||
</inspectionSuppressions>
|
||||
</form>
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.ide.wizard.CommitStepException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.ui.CollectionComboBoxModel;
|
||||
import com.intellij.ui.components.JBRadioButton;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.android.exportSignedPackage.NewKeyForm;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class ChooseKeyDialog extends DialogWrapper {
|
||||
private JPanel myNewKeyPanel;
|
||||
private JBRadioButton myCreateNewKeyRadioButton;
|
||||
private JBRadioButton myUseExistingKeyRadioButton;
|
||||
private JComboBox myKeyCombo;
|
||||
private JPanel myPanel;
|
||||
|
||||
private final NewKeyForm myNewKeyForm = new MyNewKeyForm();
|
||||
private final Project myProject;
|
||||
private final String myKeyStorePath;
|
||||
private final char[] myKeyStorePassword;
|
||||
private final List<String> myExistingKeys;
|
||||
|
||||
public ChooseKeyDialog(@NotNull Project project,
|
||||
@NotNull String keyStorePath,
|
||||
@NotNull char[] password,
|
||||
@NotNull List<String> existingKeys,
|
||||
@Nullable String keyToSelect) {
|
||||
super(project);
|
||||
myProject = project;
|
||||
myKeyStorePath = keyStorePath;
|
||||
myKeyStorePassword = password;
|
||||
myExistingKeys = existingKeys;
|
||||
myKeyCombo.setModel(new CollectionComboBoxModel(existingKeys, existingKeys.get(0)));
|
||||
|
||||
if (keyToSelect != null && existingKeys.contains(keyToSelect)) {
|
||||
myKeyCombo.setSelectedItem(keyToSelect);
|
||||
}
|
||||
myNewKeyPanel.add(myNewKeyForm.getContentPanel(), BorderLayout.CENTER);
|
||||
|
||||
final ActionListener listener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
UIUtil.setEnabled(myNewKeyPanel, myCreateNewKeyRadioButton.isSelected(), true);
|
||||
}
|
||||
};
|
||||
myCreateNewKeyRadioButton.addActionListener(listener);
|
||||
myUseExistingKeyRadioButton.addActionListener(listener);
|
||||
|
||||
final boolean useExisting = existingKeys.size() > 0;
|
||||
myUseExistingKeyRadioButton.setSelected(useExisting);
|
||||
myCreateNewKeyRadioButton.setSelected(!useExisting);
|
||||
UIUtil.setEnabled(myNewKeyPanel, !useExisting, true);
|
||||
|
||||
setTitle("Create New Key");
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
if (myCreateNewKeyRadioButton.isSelected()) {
|
||||
try {
|
||||
myNewKeyForm.createKey();
|
||||
}
|
||||
catch (CommitStepException e) {
|
||||
Messages.showErrorDialog(myPanel, e.getMessage(), CommonBundle.getErrorTitle());
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.doOKAction();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getChosenKey() {
|
||||
return myUseExistingKeyRadioButton.isSelected()
|
||||
? (String)myKeyCombo.getSelectedItem()
|
||||
: myNewKeyForm.getKeyAlias();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public char[] getChosenKeyPassword() {
|
||||
return myCreateNewKeyRadioButton.isSelected()
|
||||
? myNewKeyForm.getKeyPassword()
|
||||
: null;
|
||||
}
|
||||
|
||||
private class MyNewKeyForm extends NewKeyForm {
|
||||
@Override
|
||||
protected List<String> getExistingKeyAliasList() {
|
||||
return myExistingKeys;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Project getProject() {
|
||||
return myProject;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected char[] getKeyStorePassword() {
|
||||
return myKeyStorePassword;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getKeyStoreLocation() {
|
||||
return myKeyStorePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.compiler.artifact.NewKeyStoreDialog">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="507" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="61ec4" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="decbb"/>
|
||||
<text value="&Key store path:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="a56a5">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="decbb" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myKeyStorePathField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<grid id="1d308" binding="myNewKeyPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<clientProperties>
|
||||
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
|
||||
</clientProperties>
|
||||
<border type="etched" title="Key"/>
|
||||
<children/>
|
||||
</grid>
|
||||
<component id="f7c78" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="75dcc"/>
|
||||
<text value="&Password:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="75dcc" class="javax.swing.JPasswordField" binding="myPasswordField">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="aa6ed" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="bffd5"/>
|
||||
<text value="Co&nfirm password:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="bffd5" class="javax.swing.JPasswordField" binding="myConfirmedPassword">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.jetbrains.android.compiler.artifact;
|
||||
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.ide.wizard.CommitStepException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import org.jetbrains.android.exportSignedPackage.NewKeyForm;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.android.util.SaveFileListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class NewKeyStoreDialog extends DialogWrapper {
|
||||
private JPanel myNewKeyPanel;
|
||||
private JPanel myPanel;
|
||||
private TextFieldWithBrowseButton myKeyStorePathField;
|
||||
private JPasswordField myPasswordField;
|
||||
private JPasswordField myConfirmedPassword;
|
||||
|
||||
private final NewKeyForm myNewKeyForm;
|
||||
private final Project myProject;
|
||||
|
||||
public NewKeyStoreDialog(@NotNull Project project, @NotNull String defaultKeyStorePath) {
|
||||
super(project);
|
||||
myProject = project;
|
||||
myKeyStorePathField.setText(defaultKeyStorePath);
|
||||
setTitle("Create New Key Store");
|
||||
myNewKeyForm = new MyNewKeyForm();
|
||||
myNewKeyPanel.add(myNewKeyForm.getContentPanel(), BorderLayout.CENTER);
|
||||
|
||||
myKeyStorePathField.addActionListener(new SaveFileListener(myPanel, myKeyStorePathField, AndroidBundle.message(
|
||||
"android.extract.package.choose.keystore.title")) {
|
||||
@Override
|
||||
protected String getDefaultLocation() {
|
||||
return getKeyStorePath();
|
||||
}
|
||||
});
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myKeyStorePathField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
if (getKeyStorePath().length() == 0) {
|
||||
Messages.showErrorDialog(myPanel, "Specify key store path", CommonBundle.getErrorTitle());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
AndroidUtils.checkNewPassword(myPasswordField, myConfirmedPassword);
|
||||
myNewKeyForm.createKey();
|
||||
}
|
||||
catch (CommitStepException e) {
|
||||
Messages.showErrorDialog(myPanel, e.getMessage(), CommonBundle.getErrorTitle());
|
||||
return;
|
||||
}
|
||||
super.doOKAction();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getKeyStorePath() {
|
||||
return myKeyStorePathField.getText().trim();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public char[] getKeyStorePassword() {
|
||||
return myPasswordField.getPassword();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getKeyAlias() {
|
||||
return myNewKeyForm.getKeyAlias();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public char[] getKeyPassword() {
|
||||
return myNewKeyForm.getKeyPassword();
|
||||
}
|
||||
|
||||
private class MyNewKeyForm extends NewKeyForm {
|
||||
|
||||
@Override
|
||||
protected List<String> getExistingKeyAliasList() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Project getProject() {
|
||||
return myProject;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected char[] getKeyStorePassword() {
|
||||
return NewKeyStoreDialog.this.getKeyStorePassword();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getKeyStoreLocation() {
|
||||
return getKeyStorePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.jetbrains.android.exportSignedPackage;
|
||||
|
||||
import com.android.jarutils.SignedJarBuilder;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
@@ -50,7 +49,6 @@ import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import org.jetbrains.android.compiler.AndroidCompileUtil;
|
||||
import org.jetbrains.android.compiler.AndroidPackagingCompiler;
|
||||
import org.jetbrains.android.compiler.AndroidProguardCompiler;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.facet.AndroidFacetConfiguration;
|
||||
@@ -58,7 +56,6 @@ import org.jetbrains.android.facet.AndroidRootUtil;
|
||||
import org.jetbrains.android.sdk.AndroidPlatform;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.AndroidCommonUtils;
|
||||
import org.jetbrains.android.util.SafeSignedJarBuilder;
|
||||
import org.jetbrains.android.util.SaveFileListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -68,12 +65,8 @@ import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
@@ -110,7 +103,7 @@ class ApkStep extends ExportSignedPackageWizardStep {
|
||||
myWizard = wizard;
|
||||
myApkPathLabel.setLabelFor(myApkPathField);
|
||||
myProguardConfigFilePathLabel.setLabelFor(myProguardConfigFilePathField);
|
||||
|
||||
|
||||
myApkPathField.getButton().addActionListener(
|
||||
new SaveFileListener(myContentPanel, myApkPathField, AndroidBundle.message("android.extract.package.choose.dest.apk")) {
|
||||
@Override
|
||||
@@ -140,7 +133,7 @@ class ApkStep extends ExportSignedPackageWizardStep {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
myProguardCheckBox.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -193,7 +186,7 @@ class ApkStep extends ExportSignedPackageWizardStep {
|
||||
myIncludeSystemProguardFileCheckBox.setVisible(AndroidCommonUtils.isIncludingInProguardSupported(sdkToolsRevision));
|
||||
|
||||
final String proguardCfgPath = properties.getValue(PROGUARD_CFG_PATH_PROPERTY);
|
||||
if (proguardCfgPath != null &&
|
||||
if (proguardCfgPath != null &&
|
||||
LocalFileSystem.getInstance().refreshAndFindFileByPath(proguardCfgPath) != null) {
|
||||
myProguardConfigFilePathField.setText(FileUtil.toSystemDependentName(proguardCfgPath));
|
||||
final String includeSystemProguardFile = properties.getValue(INCLUDE_SYSTEM_PROGUARD_FILE_PROPERTY);
|
||||
@@ -274,35 +267,11 @@ class ApkStep extends ExportSignedPackageWizardStep {
|
||||
|
||||
@SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
|
||||
private void createApk(File destFile) throws IOException, GeneralSecurityException {
|
||||
final String srcApkPath = AndroidRootUtil.getApkPath(myWizard.getFacet()) + AndroidPackagingCompiler.UNSIGNED_SUFFIX;
|
||||
final String srcApkPath = AndroidCompileUtil.getUnsignedApkPath(myWizard.getFacet());
|
||||
final File srcApk = new File(FileUtil.toSystemDependentName(srcApkPath));
|
||||
|
||||
if (myWizard.isSigned()) {
|
||||
FileOutputStream fos = new FileOutputStream(destFile);
|
||||
PrivateKey privateKey = myWizard.getPrivateKey();
|
||||
assert privateKey != null;
|
||||
X509Certificate certificate = myWizard.getCertificate();
|
||||
assert certificate != null;
|
||||
SignedJarBuilder builder = new SafeSignedJarBuilder(fos, privateKey, certificate, destFile.getPath());
|
||||
FileInputStream fis = new FileInputStream(srcApk);
|
||||
try {
|
||||
builder.writeZip(fis, null);
|
||||
builder.close();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
fos.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
AndroidCommonUtils.signApk(srcApk, destFile, myWizard.getPrivateKey(), myWizard.getCertificate());
|
||||
}
|
||||
else {
|
||||
FileUtil.copy(srcApk, destFile);
|
||||
|
||||
-36
@@ -18,11 +18,9 @@ package org.jetbrains.android.exportSignedPackage;
|
||||
|
||||
import com.intellij.ide.wizard.CommitStepException;
|
||||
import com.intellij.ide.wizard.StepAdapter;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
@@ -44,42 +42,8 @@ public abstract class ExportSignedPackageWizardStep extends StepAdapter {
|
||||
|
||||
public abstract String getHelpId();
|
||||
|
||||
protected static void checkNewPassword(JPasswordField passwordField, JPasswordField confirmedPasswordField) throws CommitStepException {
|
||||
char[] password = passwordField.getPassword();
|
||||
char[] confirmedPassword = confirmedPasswordField.getPassword();
|
||||
try {
|
||||
checkPassword(password);
|
||||
if (password.length < 6) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.incorrect.password.length"));
|
||||
}
|
||||
if (!Arrays.equals(password, confirmedPassword)) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.passwords.not.match.error"));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Arrays.fill(password, '\0');
|
||||
Arrays.fill(confirmedPassword, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void commitForNext() throws CommitStepException;
|
||||
|
||||
protected static void checkPassword(char[] password) throws CommitStepException {
|
||||
if (password.length == 0) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.password.error"));
|
||||
}
|
||||
}
|
||||
|
||||
protected static void checkPassword(JPasswordField passwordField) throws CommitStepException {
|
||||
char[] password = passwordField.getPassword();
|
||||
try {
|
||||
checkPassword(password);
|
||||
}
|
||||
finally {
|
||||
Arrays.fill(password, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return null;
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.ui.CollectionComboBoxModel;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
@@ -132,7 +133,7 @@ class InitialKeyStep extends ExportSignedPackageWizardStep {
|
||||
if (myAliasCombo.getSelectedItem() == null) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.extract.package.select.key.alias.error"));
|
||||
}
|
||||
checkPassword(password);
|
||||
AndroidUtils.checkPassword(password);
|
||||
String alias = (String)myAliasCombo.getSelectedItem();
|
||||
loadKey(alias, password);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.android.util.SaveFileListener;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -102,7 +103,7 @@ class KeystoreStep extends ExportSignedPackageWizardStep {
|
||||
private KeyStore checkExistingKeystoreOptionsAndCreateKeystore(File keystoreFile) throws CommitStepException {
|
||||
char[] password = myKeystorePasswordField.getPassword();
|
||||
FileInputStream fis = null;
|
||||
checkPassword(password);
|
||||
AndroidUtils.checkPassword(password);
|
||||
if (!keystoreFile.isFile()) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.cannot.find.file.error", keystoreFile.getPath()));
|
||||
}
|
||||
@@ -129,7 +130,7 @@ class KeystoreStep extends ExportSignedPackageWizardStep {
|
||||
}
|
||||
|
||||
private void checkNewKeystoreOptions(File keystoreFile) throws CommitStepException {
|
||||
checkNewPassword(myKeystorePasswordField, myConfirmKeystorePasswordField);
|
||||
AndroidUtils.checkNewPassword(myKeystorePasswordField, myConfirmKeystorePasswordField);
|
||||
|
||||
if (keystoreFile.exists()) {
|
||||
throw new CommitStepException(AndroidBundle.message(
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.exportSignedPackage.NewKeyStep">
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.exportSignedPackage.NewKeyForm">
|
||||
<grid id="27dc6" binding="myContentPanel" layout-manager="GridLayoutManager" row-count="5" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
@@ -71,7 +71,7 @@
|
||||
<grid row="3" column="0" row-span="1" col-span="4" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="etched"/>
|
||||
<border type="etched" title="Certificate"/>
|
||||
<children>
|
||||
<component id="7b983" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
@@ -0,0 +1,270 @@
|
||||
package org.jetbrains.android.exportSignedPackage;
|
||||
|
||||
import com.android.jarutils.DebugKeyProvider;
|
||||
import com.android.jarutils.KeystoreHelper;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.ide.wizard.CommitStepException;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public abstract class NewKeyForm {
|
||||
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.exportSignedPackage.NewKeyForm");
|
||||
|
||||
private JPanel myContentPanel;
|
||||
private JTextField myAliasField;
|
||||
private JPasswordField myKeyPasswordField;
|
||||
private JPasswordField myConfirmKeyPasswordField;
|
||||
private JSpinner myValiditySpinner;
|
||||
private JTextField myFirstAndLastNameField;
|
||||
private JTextField myOrganizationUnitField;
|
||||
private JTextField myCityField;
|
||||
private JTextField myStateOrProvinceField;
|
||||
private JTextField myCountryCodeField;
|
||||
private JPanel myCertificatePanel;
|
||||
private JTextField myOrganizationField;
|
||||
|
||||
private KeyStore myKeyStore;
|
||||
private PrivateKey myPrivateKey;
|
||||
private X509Certificate myCertificate;
|
||||
|
||||
public NewKeyForm() {
|
||||
myValiditySpinner.setModel(new SpinnerNumberModel(25, 1, 1000, 1));
|
||||
}
|
||||
|
||||
private int getValidity() {
|
||||
SpinnerNumberModel model = (SpinnerNumberModel)myValiditySpinner.getModel();
|
||||
return model.getNumber().intValue();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
myAliasField.setText(generateAlias());
|
||||
}
|
||||
|
||||
public JPanel getContentPanel() {
|
||||
return myContentPanel;
|
||||
}
|
||||
|
||||
private boolean findNonEmptyCertificateField() {
|
||||
for (Component component : myCertificatePanel.getComponents()) {
|
||||
if (component instanceof JTextField) {
|
||||
if (((JTextField)component).getText().trim().length() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void createKey() throws CommitStepException {
|
||||
if (getKeyAlias().length() == 0) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.key.alias.error"));
|
||||
}
|
||||
AndroidUtils.checkNewPassword(myKeyPasswordField, myConfirmKeyPasswordField);
|
||||
if (!findNonEmptyCertificateField()) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.certificate.field.error"));
|
||||
}
|
||||
doCreateKey();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String generateAlias() {
|
||||
List<String> aliasList = getExistingKeyAliasList();
|
||||
String prefix = "key";
|
||||
if (aliasList == null) {
|
||||
return prefix + '0';
|
||||
}
|
||||
Set<String> aliasSet = new HashSet<String>();
|
||||
for (String alias : aliasList) {
|
||||
aliasSet.add(alias.toLowerCase());
|
||||
}
|
||||
for (int i = 0; ; i++) {
|
||||
String alias = prefix + i;
|
||||
if (!aliasSet.contains(alias)) {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract List<String> getExistingKeyAliasList();
|
||||
|
||||
private static void buildDName(StringBuilder builder, String prefix, JTextField textField) {
|
||||
if (textField != null) {
|
||||
String value = textField.getText().trim();
|
||||
if (value.length() > 0) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append(prefix);
|
||||
builder.append('=');
|
||||
builder.append(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getDName() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
buildDName(builder, "CN", myFirstAndLastNameField);
|
||||
buildDName(builder, "OU", myOrganizationUnitField);
|
||||
buildDName(builder, "O", myOrganizationField);
|
||||
buildDName(builder, "L", myCityField);
|
||||
buildDName(builder, "ST", myStateOrProvinceField);
|
||||
buildDName(builder, "C", myCountryCodeField);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void doCreateKey() throws CommitStepException {
|
||||
String keystoreLocation = getKeyStoreLocation();
|
||||
char[] keystorePassword = getKeyStorePassword();
|
||||
char[] keyPassword = getKeyPassword();
|
||||
String keyAlias = getKeyAlias();
|
||||
String dname = getDName();
|
||||
assert dname != null;
|
||||
boolean createdStore = false;
|
||||
final StringBuilder errorBuilder = new StringBuilder();
|
||||
final StringBuilder outBuilder = new StringBuilder();
|
||||
try {
|
||||
createdStore = KeystoreHelper
|
||||
.createNewStore(keystoreLocation, null, new String(keystorePassword), keyAlias, new String(keyPassword), dname, getValidity(),
|
||||
new DebugKeyProvider.IKeyGenOutput() {
|
||||
public void err(String message) {
|
||||
errorBuilder.append(message).append('\n');
|
||||
LOG.info("Error: " + message);
|
||||
}
|
||||
|
||||
public void out(String message) {
|
||||
outBuilder.append(message).append('\n');
|
||||
LOG.info(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.info(e);
|
||||
errorBuilder.append(e.getMessage()).append('\n');
|
||||
}
|
||||
normalizeBuilder(errorBuilder);
|
||||
normalizeBuilder(outBuilder);
|
||||
try {
|
||||
if (createdStore) {
|
||||
if (errorBuilder.length() > 0) {
|
||||
String prefix = AndroidBundle.message("android.create.new.key.error.prefix");
|
||||
Messages.showErrorDialog(myContentPanel, prefix + '\n' + errorBuilder.toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (errorBuilder.length() > 0) {
|
||||
throw new CommitStepException(errorBuilder.toString());
|
||||
}
|
||||
if (outBuilder.length() > 0) {
|
||||
throw new CommitStepException(outBuilder.toString());
|
||||
}
|
||||
throw new CommitStepException(AndroidBundle.message("android.cannot.create.new.key.error"));
|
||||
}
|
||||
PropertiesComponent.getInstance(getProject()).setValue(KeystoreStep.DEFAULT_KEYSTORE_LOCATION, keystoreLocation);
|
||||
loadKeystoreAndKey(keystoreLocation, keystorePassword, keyAlias, keyPassword);
|
||||
}
|
||||
finally {
|
||||
Arrays.fill(keystorePassword, '\0');
|
||||
Arrays.fill(keyPassword, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public char[] getKeyPassword() {
|
||||
return myKeyPasswordField.getPassword();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getKeyAlias() {
|
||||
return myAliasField.getText().trim();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract Project getProject();
|
||||
|
||||
@NotNull
|
||||
protected abstract char[] getKeyStorePassword();
|
||||
|
||||
@NotNull
|
||||
protected abstract String getKeyStoreLocation();
|
||||
|
||||
private void loadKeystoreAndKey(String keystoreLocation, char[] keystorePassword, String keyAlias, char[] keyPassword)
|
||||
throws CommitStepException {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
fis = new FileInputStream(new File(keystoreLocation));
|
||||
keyStore.load(fis, keystorePassword);
|
||||
myKeyStore = keyStore;
|
||||
KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(keyAlias, new KeyStore.PasswordProtection(keyPassword));
|
||||
if (entry == null) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.extract.package.cannot.find.key.error", keyAlias));
|
||||
}
|
||||
PrivateKey privateKey = entry.getPrivateKey();
|
||||
Certificate certificate = entry.getCertificate();
|
||||
if (privateKey == null || certificate == null) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.extract.package.cannot.find.key.error", keyAlias));
|
||||
}
|
||||
PropertiesComponent.getInstance(getProject()).setValue(InitialKeyStep.DEFAULT_KEY_ALIAS, keyAlias);
|
||||
myPrivateKey = privateKey;
|
||||
myCertificate = (X509Certificate)certificate;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new CommitStepException("Error: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void normalizeBuilder(StringBuilder builder) {
|
||||
if (builder.length() > 0) {
|
||||
builder.deleteCharAt(builder.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyStore getKeyStore() {
|
||||
return myKeyStore;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PrivateKey getPrivateKey() {
|
||||
return myPrivateKey;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public X509Certificate getCertificate() {
|
||||
return myCertificate;
|
||||
}
|
||||
}
|
||||
@@ -16,79 +16,37 @@
|
||||
|
||||
package org.jetbrains.android.exportSignedPackage;
|
||||
|
||||
import com.android.jarutils.DebugKeyProvider;
|
||||
import com.android.jarutils.KeystoreHelper;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.ide.wizard.CommitStepException;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import org.jetbrains.android.util.AndroidBundle;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Eugene.Kudelevsky
|
||||
*/
|
||||
public class NewKeyStep extends ExportSignedPackageWizardStep {
|
||||
private static final Logger LOG = Logger.getInstance("#com.jetbrains.android.exportSignedPackage.NewKeyStep");
|
||||
|
||||
private JPanel myContentPanel;
|
||||
private JTextField myAliasField;
|
||||
private JPasswordField myKeyPasswordField;
|
||||
private JPasswordField myConfirmKeyPasswordField;
|
||||
private JSpinner myValiditySpinner;
|
||||
private JTextField myFirstAndLastNameField;
|
||||
private JTextField myOrganizationUnitField;
|
||||
private JTextField myCityField;
|
||||
private JTextField myStateOrProvinceField;
|
||||
private JTextField myCountryCodeField;
|
||||
private JPanel myCertificatePanel;
|
||||
private JTextField myOrganizationField;
|
||||
|
||||
private final ExportSignedPackageWizard myWizard;
|
||||
private final NewKeyForm myNewKeyForm;
|
||||
|
||||
public NewKeyStep(ExportSignedPackageWizard wizard) {
|
||||
myWizard = wizard;
|
||||
myValiditySpinner.setModel(new SpinnerNumberModel(25, 1, 1000, 1));
|
||||
}
|
||||
|
||||
private int getValidity() {
|
||||
SpinnerNumberModel model = (SpinnerNumberModel)myValiditySpinner.getModel();
|
||||
return model.getNumber().intValue();
|
||||
myNewKeyForm = new MyNewKeyForm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void _init() {
|
||||
myAliasField.setText(generateAlias());
|
||||
myNewKeyForm.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myContentPanel;
|
||||
}
|
||||
|
||||
private boolean findNonEmptyCertificateField() {
|
||||
for (Component component : myCertificatePanel.getComponents()) {
|
||||
if (component instanceof JTextField) {
|
||||
if (((JTextField)component).getText().trim().length() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return myNewKeyForm.getContentPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,157 +56,42 @@ public class NewKeyStep extends ExportSignedPackageWizardStep {
|
||||
|
||||
@Override
|
||||
protected void commitForNext() throws CommitStepException {
|
||||
if (myAliasField.getText().trim().length() == 0) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.key.alias.error"));
|
||||
}
|
||||
checkNewPassword(myKeyPasswordField, myConfirmKeyPasswordField);
|
||||
if (!findNonEmptyCertificateField()) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.certificate.field.error"));
|
||||
}
|
||||
createKey();
|
||||
myNewKeyForm.createKey();
|
||||
final KeyStore keyStore = myNewKeyForm.getKeyStore();
|
||||
final PrivateKey privateKey = myNewKeyForm.getPrivateKey();
|
||||
final X509Certificate certificate = myNewKeyForm.getCertificate();
|
||||
assert keyStore != null && privateKey != null && certificate != null;
|
||||
myWizard.setKeystore(keyStore);
|
||||
myWizard.setPrivateKey(privateKey);
|
||||
myWizard.setCertificate(certificate);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String generateAlias() {
|
||||
List<String> aliasList = myWizard.getKeyAliasList();
|
||||
String prefix = "key";
|
||||
if (aliasList == null) {
|
||||
return prefix + '0';
|
||||
private class MyNewKeyForm extends NewKeyForm {
|
||||
@Override
|
||||
protected List<String> getExistingKeyAliasList() {
|
||||
return myWizard.getKeyAliasList();
|
||||
}
|
||||
Set<String> aliasSet = new HashSet<String>();
|
||||
for (String alias : aliasList) {
|
||||
aliasSet.add(alias.toLowerCase());
|
||||
}
|
||||
for (int i = 0; ; i++) {
|
||||
String alias = prefix + i;
|
||||
if (!aliasSet.contains(alias)) {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildDName(StringBuilder builder, String prefix, JTextField textField) {
|
||||
if (textField != null) {
|
||||
String value = textField.getText().trim();
|
||||
if (value.length() > 0) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append(prefix);
|
||||
builder.append('=');
|
||||
builder.append(value);
|
||||
}
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getKeyStoreLocation() {
|
||||
final String location = myWizard.getKeystoreLocation();
|
||||
assert location != null;
|
||||
return location;
|
||||
}
|
||||
}
|
||||
|
||||
private String getDName() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
buildDName(builder, "CN", myFirstAndLastNameField);
|
||||
buildDName(builder, "OU", myOrganizationUnitField);
|
||||
buildDName(builder, "O", myOrganizationField);
|
||||
buildDName(builder, "L", myCityField);
|
||||
buildDName(builder, "ST", myStateOrProvinceField);
|
||||
buildDName(builder, "C", myCountryCodeField);
|
||||
return builder.toString();
|
||||
}
|
||||
@NotNull
|
||||
@Override
|
||||
protected char[] getKeyStorePassword() {
|
||||
final char[] password = myWizard.getKeystorePassword();
|
||||
assert password != null;
|
||||
return password;
|
||||
}
|
||||
|
||||
private void createKey() throws CommitStepException {
|
||||
String keystoreLocation = myWizard.getKeystoreLocation();
|
||||
assert keystoreLocation != null;
|
||||
char[] keystorePassword = myWizard.getKeystorePassword();
|
||||
assert keystorePassword != null;
|
||||
char[] keyPassword = myKeyPasswordField.getPassword();
|
||||
String keyAlias = myAliasField.getText().trim();
|
||||
String dname = getDName();
|
||||
assert dname != null;
|
||||
boolean createdStore = false;
|
||||
final StringBuilder errorBuilder = new StringBuilder();
|
||||
final StringBuilder outBuilder = new StringBuilder();
|
||||
try {
|
||||
createdStore = KeystoreHelper
|
||||
.createNewStore(keystoreLocation, null, new String(keystorePassword), keyAlias, new String(keyPassword), dname, getValidity(),
|
||||
new DebugKeyProvider.IKeyGenOutput() {
|
||||
public void err(String message) {
|
||||
errorBuilder.append(message).append('\n');
|
||||
LOG.info("Error: " + message);
|
||||
}
|
||||
|
||||
public void out(String message) {
|
||||
outBuilder.append(message).append('\n');
|
||||
LOG.info(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.info(e);
|
||||
errorBuilder.append(e.getMessage()).append('\n');
|
||||
}
|
||||
normalizeBuilder(errorBuilder);
|
||||
normalizeBuilder(outBuilder);
|
||||
try {
|
||||
if (createdStore) {
|
||||
if (errorBuilder.length() > 0) {
|
||||
String prefix = AndroidBundle.message("android.create.new.key.error.prefix");
|
||||
Messages.showErrorDialog(myContentPanel, prefix + '\n' + errorBuilder.toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (errorBuilder.length() > 0) {
|
||||
throw new CommitStepException(errorBuilder.toString());
|
||||
}
|
||||
if (outBuilder.length() > 0) {
|
||||
throw new CommitStepException(outBuilder.toString());
|
||||
}
|
||||
throw new CommitStepException(AndroidBundle.message("android.cannot.create.new.key.error"));
|
||||
}
|
||||
PropertiesComponent.getInstance(myWizard.getProject()).setValue(KeystoreStep.DEFAULT_KEYSTORE_LOCATION, keystoreLocation);
|
||||
loadKeystoreAndKey(keystoreLocation, keystorePassword, keyAlias, keyPassword);
|
||||
}
|
||||
finally {
|
||||
Arrays.fill(keystorePassword, '\0');
|
||||
Arrays.fill(keyPassword, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
private void loadKeystoreAndKey(String keystoreLocation, char[] keystorePassword, String keyAlias, char[] keyPassword)
|
||||
throws CommitStepException {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
fis = new FileInputStream(new File(keystoreLocation));
|
||||
keyStore.load(fis, keystorePassword);
|
||||
myWizard.setKeystore(keyStore);
|
||||
KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(keyAlias, new KeyStore.PasswordProtection(keyPassword));
|
||||
if (entry == null) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.extract.package.cannot.find.key.error", keyAlias));
|
||||
}
|
||||
PrivateKey privateKey = entry.getPrivateKey();
|
||||
Certificate certificate = entry.getCertificate();
|
||||
if (privateKey == null || certificate == null) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.extract.package.cannot.find.key.error", keyAlias));
|
||||
}
|
||||
PropertiesComponent.getInstance(myWizard.getProject()).setValue(InitialKeyStep.DEFAULT_KEY_ALIAS, keyAlias);
|
||||
myWizard.setPrivateKey(privateKey);
|
||||
myWizard.setCertificate((X509Certificate)certificate);
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new CommitStepException("Error: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void normalizeBuilder(StringBuilder builder) {
|
||||
if (builder.length() > 0) {
|
||||
builder.deleteCharAt(builder.length() - 1);
|
||||
@NotNull
|
||||
@Override
|
||||
protected Project getProject() {
|
||||
return myWizard.getProject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.intellij.facet.FacetManager;
|
||||
import com.intellij.facet.ModifiableFacetModel;
|
||||
import com.intellij.facet.ProjectFacetManager;
|
||||
import com.intellij.ide.util.DefaultPsiElementCellRenderer;
|
||||
import com.intellij.ide.wizard.CommitStepException;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.module.Module;
|
||||
@@ -85,10 +86,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author yole, coyote
|
||||
@@ -714,4 +712,47 @@ public class AndroidUtils {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void checkNewPassword(JPasswordField passwordField, JPasswordField confirmedPasswordField) throws CommitStepException {
|
||||
char[] password = passwordField.getPassword();
|
||||
char[] confirmedPassword = confirmedPasswordField.getPassword();
|
||||
try {
|
||||
checkPassword(password);
|
||||
if (password.length < 6) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.incorrect.password.length"));
|
||||
}
|
||||
if (!Arrays.equals(password, confirmedPassword)) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.passwords.not.match.error"));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Arrays.fill(password, '\0');
|
||||
Arrays.fill(confirmedPassword, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkPassword(char[] password) throws CommitStepException {
|
||||
if (password.length == 0) {
|
||||
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.password.error"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkPassword(JPasswordField passwordField) throws CommitStepException {
|
||||
char[] password = passwordField.getPassword();
|
||||
try {
|
||||
checkPassword(password);
|
||||
}
|
||||
finally {
|
||||
Arrays.fill(password, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <T> List<T> toList(@NotNull Enumeration<T> enumeration) {
|
||||
final List<T> result = new ArrayList<T>();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
result.add(enumeration.nextElement());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user