IDEA-80278 support of building Android apk artifacts

This commit is contained in:
Eugene Kudelevsky
2012-06-04 19:20:50 +04:00
parent b636556d00
commit 84a3aff0ff
30 changed files with 2258 additions and 304 deletions
@@ -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) {
}
}
}
}
}
+5
View File
@@ -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();
@@ -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()));
}
}
@@ -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);
}
}
}
@@ -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 &amp;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 &amp;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="&amp;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="&amp;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 &amp;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&amp;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&amp;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="&amp;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="&amp;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>
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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="&amp;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 &amp;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="&amp;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="&amp;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&amp;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);
@@ -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(
@@ -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;
}
}