blob: 9649995a95bc5e672f02a26986a9e848f7115cf1 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.npw;
import com.android.builder.model.SourceProvider;
import com.android.sdklib.AndroidVersion;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.model.AndroidModuleInfo;
import com.android.tools.idea.model.ManifestInfo;
import com.android.tools.idea.templates.*;
import com.android.tools.idea.wizard.dynamic.DynamicWizardPath;
import com.android.tools.idea.wizard.template.TemplateWizard;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.RecentsManager;
import com.intellij.util.ArrayUtil;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.facet.IdeaSourceProvider;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.android.tools.idea.templates.KeystoreUtils.getDebugKeystore;
import static com.android.tools.idea.templates.TemplateMetadata.*;
import static com.android.tools.idea.wizard.dynamic.ScopedStateStore.Key;
import static com.android.tools.idea.wizard.dynamic.ScopedStateStore.Scope.PATH;
import static com.android.tools.idea.wizard.dynamic.ScopedStateStore.Scope.WIZARD;
import static com.android.tools.idea.wizard.dynamic.ScopedStateStore.createKey;
/**
* Wizard path for adding a new activity.
*/
public final class AddAndroidActivityPath extends DynamicWizardPath {
public static final Key<Boolean> KEY_IS_LAUNCHER = createKey("is.launcher.activity", PATH, Boolean.class);
public static final Key<TemplateEntry> KEY_SELECTED_TEMPLATE = createKey("selected.template", PATH, TemplateEntry.class);
public static final Key<AndroidVersion> KEY_MIN_SDK = createKey(TemplateMetadata.ATTR_MIN_API, PATH, AndroidVersion.class);
public static final Key<AndroidVersion> KEY_TARGET_API = createKey(TemplateMetadata.ATTR_TARGET_API, PATH, AndroidVersion.class);
public static final Key<Integer> KEY_BUILD_SDK = createKey(TemplateMetadata.ATTR_BUILD_API, PATH, Integer.class);
public static final Key<String> KEY_PACKAGE_NAME = createKey(TemplateMetadata.ATTR_PACKAGE_NAME, PATH, String.class);
public static final Key<SourceProvider> KEY_SOURCE_PROVIDER = createKey("source.provider", PATH, SourceProvider.class);
public static final Key<String> KEY_SOURCE_PROVIDER_NAME = createKey(ATTR_SOURCE_PROVIDER_NAME, PATH, String.class);
public static final Set<String> PACKAGE_NAME_PARAMETERS = ImmutableSet.of(TemplateMetadata.ATTR_PACKAGE_NAME);
public static final Set<String> CLASS_NAME_PARAMETERS = ImmutableSet.of(TemplateMetadata.ATTR_PARENT_ACTIVITY_CLASS);
public static final Key<Boolean> KEY_OPEN_EDITORS = createKey("open.editors", WIZARD, Boolean.class);
public static final Set<Key<String>> IMPLICIT_PARAMETERS = ImmutableSet.of(KEY_PACKAGE_NAME, KEY_SOURCE_PROVIDER_NAME);
private static final Logger LOG = Logger.getInstance(AddAndroidActivityPath.class);
@Nullable private final ActivityGalleryStep myGalleryStep;
private TemplateParameterStep2 myParameterStep;
private final boolean myIsNewModule;
private IconStep myAssetStudioStep;
private VirtualFile myTargetFolder;
@Nullable private File myTemplate;
private final Map<String, Object> myPredefinedParameterValues;
private final Disposable myParentDisposable;
/**
* Creates a new instance of the wizard path.
*/
public AddAndroidActivityPath(@Nullable VirtualFile targetFolder, @Nullable File template,
Map<String, Object> predefinedParameterValues,
Disposable parentDisposable) {
myTemplate = template;
myPredefinedParameterValues = predefinedParameterValues;
myParentDisposable = parentDisposable;
myIsNewModule = false;
myTargetFolder = targetFolder != null && !targetFolder.isDirectory() ? targetFolder.getParent() : targetFolder;
FormFactorUtils.FormFactor formFactor = getFormFactor(targetFolder);
if (template == null) {
myGalleryStep = new ActivityGalleryStep(formFactor, false, KEY_SELECTED_TEMPLATE, parentDisposable);
}
else {
myGalleryStep = null;
}
}
private static FormFactorUtils.FormFactor getFormFactor(@Nullable VirtualFile targetFolder) {
// TODO There should be some way for this wizard to figure out form factor from a target or from a template
return FormFactorUtils.FormFactor.MOBILE;
}
/**
* Finds and returns the main src directory for the given project or null if one cannot be found.
*/
@Nullable
public static File findSrcDirectory(@NotNull SourceProvider sourceProvider) {
return Iterables.getFirst(sourceProvider.getJavaDirectories(), null);
}
@Nullable
private static File findTestDirectory(@NotNull Module module) {
List<VirtualFile> testsRoot = ModuleRootManager.getInstance(module).getSourceRoots(JavaModuleSourceRootTypes.TESTS);
return testsRoot.size() == 0 ? null : VfsUtilCore.virtualToIoFile(testsRoot.get(0));
}
/**
* Finds and returns the main res directory for the given project or null if one cannot be found.
*/
@Nullable
public static File findResDirectory(@NotNull SourceProvider sourceProvider) {
Collection<File> resDirectories = sourceProvider.getResDirectories();
File resDir = null;
if (!resDirectories.isEmpty()) {
resDir = resDirectories.iterator().next();
}
return resDir;
}
/**
* Finds and returns the main res directory for the given project or null if one cannot be found.
*/
@Nullable
public static File findAidlDir(@NotNull SourceProvider sourceProvider) {
Collection<File> aidlDirectories = sourceProvider.getAidlDirectories();
File resDir = null;
if (!aidlDirectories.isEmpty()) {
resDir = aidlDirectories.iterator().next();
}
return resDir;
}
/**
* Finds and returns the main manifest directory for the given project or null if one cannot be found.
*/
@Nullable
public static File findManifestDirectory(@NotNull SourceProvider sourceProvider) {
File manifestFile = sourceProvider.getManifestFile();
File manifestDir = manifestFile.getParentFile();
if (manifestDir != null) {
return manifestDir;
}
return null;
}
/**
* Calculate the package name from the given target directory. Returns the package name or null if no package name could
* be calculated.
*/
@Nullable
public static String getPackageFromDirectory(@NotNull VirtualFile directory,
@NotNull SourceProvider sourceProvider,
@NotNull Module module, @NotNull String srcDir) {
File javaSourceRoot;
File javaDir = findSrcDirectory(sourceProvider);
if (javaDir == null) {
javaSourceRoot = new File(AndroidRootUtil.getModuleDirPath(module), srcDir);
}
else {
javaSourceRoot = new File(javaDir.getPath());
}
File javaSourcePackageRoot = VfsUtilCore.virtualToIoFile(directory);
if (!FileUtil.isAncestor(javaSourceRoot, javaSourcePackageRoot, true)) {
return null;
}
String relativePath = FileUtil.getRelativePath(javaSourceRoot, javaSourcePackageRoot);
String packageName = relativePath != null ? FileUtil.toSystemIndependentName(relativePath).replace('/', '.') : null;
if (packageName == null || !AndroidUtils.isValidJavaPackageName(packageName)) {
return null;
}
return packageName;
}
@Nullable
private static File getModuleRoot(@Nullable Module module) {
if (module == null) {
return null;
}
VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots();
if (roots.length > 0) {
return VfsUtilCore.virtualToIoFile(roots[0]);
}
else {
return null;
}
}
private static Map<String, Object> selectSourceProvider(@NotNull SourceProvider sourceProvider,
@NotNull IdeaAndroidProject androidModel,
@NotNull Module module,
@NotNull String packageName) {
Map<String, Object> paths = Maps.newHashMap();
// Look up the resource directories inside this source set
File moduleDirPath = androidModel.getRootDirPath();
File javaDir = findSrcDirectory(sourceProvider);
File testDir = findTestDirectory(module);
String javaPath = getJavaPath(moduleDirPath, javaDir);
paths.put(ATTR_SRC_DIR, javaPath);
File resDir = findResDirectory(sourceProvider);
if (resDir != null) {
String resPath = FileUtil.getRelativePath(moduleDirPath, resDir);
if (resPath != null) {
resPath = FileUtil.toSystemIndependentName(resPath);
}
paths.put(ATTR_RES_DIR, resPath);
paths.put(ATTR_RES_OUT, FileUtil.toSystemIndependentName(resDir.getPath()));
}
File manifestDir = findManifestDirectory(sourceProvider);
if (manifestDir != null) {
String manifestPath = FileUtil.getRelativePath(moduleDirPath, manifestDir);
paths.put(ATTR_MANIFEST_DIR, manifestPath);
paths.put(ATTR_MANIFEST_OUT, FileUtil.toSystemIndependentName(manifestDir.getPath()));
}
File aidlDir = findAidlDir(sourceProvider);
if (aidlDir != null) {
String aidlPath = FileUtil.getRelativePath(moduleDirPath, aidlDir);
paths.put(ATTR_AIDL_DIR, aidlPath);
paths.put(ATTR_AIDL_OUT, FileUtil.toSystemIndependentName(aidlDir.getPath()));
}
if (testDir == null) {
String absolutePath = Joiner.on('/').join(androidModel.getRootDir().getPath(), TemplateWizard.TEST_SOURCE_PATH,
TemplateWizard.JAVA_SOURCE_PATH);
testDir = new File(FileUtil.toSystemDependentName(absolutePath));
}
assert javaPath != null;
// Calculate package name
paths.put(TemplateMetadata.ATTR_PACKAGE_NAME, packageName);
String relativePackageDir = packageName.replace('.', File.separatorChar);
File srcOut = new File(javaDir, relativePackageDir);
File testOut = new File(testDir, relativePackageDir);
paths.put(ATTR_TEST_DIR, FileUtil.toSystemIndependentName(testDir.getAbsolutePath()));
paths.put(ATTR_TEST_OUT, FileUtil.toSystemIndependentName(testOut.getAbsolutePath()));
paths.put(ATTR_APPLICATION_PACKAGE, ManifestInfo.get(module, false).getPackage());
paths.put(ATTR_SRC_OUT, FileUtil.toSystemIndependentName(srcOut.getAbsolutePath()));
return paths;
}
@Nullable
private static String getJavaPath(File ioModuleDir, @Nullable File javaDir) {
String javaPath = null;
if (javaDir != null) {
javaPath = FileUtil.getRelativePath(ioModuleDir, javaDir);
if (javaPath != null) {
javaPath = FileUtil.toSystemIndependentName(javaPath);
}
}
return javaPath;
}
public static List<String> getParameterValueHistory(@NotNull Parameter parameter, Project project) {
List<String> entries = RecentsManager.getInstance(project).getRecentEntries(getRecentHistoryKey(parameter.id));
return entries == null ? ImmutableList.<String>of() : entries;
}
public static String getRecentHistoryKey(@Nullable String parameter) {
return "android.template." + parameter;
}
public static void saveRecentValues(@NotNull Project project, @NotNull Map<String, Object> state) {
for (String id : Iterables.concat(PACKAGE_NAME_PARAMETERS, CLASS_NAME_PARAMETERS)) {
String value = (String)state.get(id);
if (!StringUtil.isEmpty(value)) {
RecentsManager.getInstance(project).registerRecentEntry(getRecentHistoryKey(id), value);
}
}
}
@Override
protected void init() {
Module module = getModule();
assert module != null;
AndroidFacet facet = AndroidFacet.getInstance(module);
assert facet != null;
AndroidPlatform platform = AndroidPlatform.getInstance(module);
if (platform != null) {
myState.put(KEY_BUILD_SDK, platform.getTarget().getVersion().getFeatureLevel());
}
AndroidModuleInfo moduleInfo = AndroidModuleInfo.get(facet);
AndroidVersion minSdkVersion = moduleInfo.getMinSdkVersion();
myState.put(KEY_MIN_SDK, minSdkVersion);
myState.put(KEY_TARGET_API, moduleInfo.getTargetSdkVersion());
myState.put(KEY_PACKAGE_NAME, getInitialPackageName(module, facet));
myState.put(KEY_OPEN_EDITORS, true);
if (myGalleryStep != null) {
addStep(myGalleryStep);
}
else {
assert myTemplate != null;
TemplateMetadata templateMetadata = TemplateManager.getInstance().getTemplate(myTemplate);
assert templateMetadata != null;
myState.put(KEY_SELECTED_TEMPLATE, new TemplateEntry(myTemplate, templateMetadata));
}
SourceProvider[] sourceProviders = getSourceProviders(module, myTargetFolder);
myParameterStep = new TemplateParameterStep2(getFormFactor(myTargetFolder), myPredefinedParameterValues,
myParentDisposable, KEY_PACKAGE_NAME, sourceProviders);
myAssetStudioStep = new IconStep(KEY_SELECTED_TEMPLATE, KEY_SOURCE_PROVIDER, null, myParentDisposable);
addStep(myParameterStep);
addStep(myAssetStudioStep);
}
@NotNull
public static SourceProvider[] getSourceProviders(@Nullable Module module, @Nullable VirtualFile targetDirectory) {
if (module != null) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
List<SourceProvider> providers;
if (targetDirectory != null) {
providers = IdeaSourceProvider.getSourceProvidersForFile(facet, targetDirectory, facet.getMainSourceProvider());
}
else {
providers = IdeaSourceProvider.getAllSourceProviders(facet);
}
return ArrayUtil.toObjectArray(providers, SourceProvider.class);
}
}
return new SourceProvider[0];
}
/**
* Initial package name is either a package user selected when invoking the wizard or default package for the module.
*/
private String getInitialPackageName(Module module, AndroidFacet facet) {
if (myTargetFolder != null) {
List<SourceProvider> sourceProviders =
IdeaSourceProvider.getSourceProvidersForFile(facet, myTargetFolder, facet.getMainSourceProvider());
File targetDirectoryFile = VfsUtilCore.virtualToIoFile(myTargetFolder);
if (sourceProviders.size() > 0 && IdeaSourceProvider.containsFile(sourceProviders.get(0), targetDirectoryFile)) {
File srcDirectory = findSrcDirectory(sourceProviders.get(0));
if (srcDirectory != null) {
String packageName = getPackageFromDirectory(myTargetFolder, sourceProviders.get(0), module, srcDirectory.toString());
if (packageName != null) {
return packageName;
}
}
}
}
return getApplicationPackageName();
}
@NotNull
@Override
public String getPathName() {
return "Add Android activity";
}
@Override
public boolean performFinishingActions() {
TemplateEntry templateEntry = myState.get(KEY_SELECTED_TEMPLATE);
final Project project = getProject();
Module module = getModule();
assert templateEntry != null;
assert project != null && module != null;
final Template template = templateEntry.getTemplate();
File moduleRoot = getModuleRoot(module);
if (moduleRoot == null) {
return false;
}
Map<String, Object> parameterMap = getTemplateParameterMap(templateEntry.getMetadata());
saveRecentValues(project, parameterMap);
template.render(VfsUtilCore.virtualToIoFile(project.getBaseDir()), moduleRoot, parameterMap, project);
myAssetStudioStep.createAssets();
if (Boolean.TRUE.equals(myState.get(KEY_OPEN_EDITORS))) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
TemplateUtils.openEditors(project, template.getFilesToOpen(), true);
}
});
}
return true;
}
@NotNull
private Map<String, Object> getTemplateParameterMap(@NotNull TemplateMetadata template) {
Map<String, Object> parameterValueMap = Maps.newHashMap();
parameterValueMap.put(TemplateMetadata.ATTR_IS_NEW_PROJECT, myIsNewModule);
parameterValueMap.putAll(getDirectories());
for (Key<?> parameter : IMPLICIT_PARAMETERS) {
parameterValueMap.put(parameter.name, myState.get(parameter));
}
for (Parameter parameter : template.getParameters()) {
parameterValueMap.put(parameter.id, myState.get(myParameterStep.getParameterKey(parameter)));
}
try {
parameterValueMap.put(ATTR_DEBUG_KEYSTORE_SHA1, KeystoreUtils.sha1(KeystoreUtils.getOrCreateDefaultDebugKeystore()));
}
catch (Exception e) {
LOG.info("Could not compute SHA1 hash of debug keystore.", e);
}
File moduleRoot = getModuleRoot(getModule());
if (moduleRoot != null) {
parameterValueMap.put(TemplateMetadata.ATTR_PROJECT_OUT, FileUtil.toSystemIndependentName(moduleRoot.getAbsolutePath()));
}
if (Objects.equal(getApplicationPackageName(), parameterValueMap.get(TemplateMetadata.ATTR_PACKAGE_NAME))) {
parameterValueMap.remove(ATTR_APPLICATION_PACKAGE);
}
return parameterValueMap;
}
private String getApplicationPackageName() {
//noinspection ConstantConditions
IdeaAndroidProject androidModel = AndroidFacet.getInstance(getModule()).getAndroidModel();
assert androidModel != null;
return androidModel.getApplicationId();
}
private Map<String, Object> getDirectories() {
Map<String, Object> templateParameters = Maps.newHashMap();
Module module = getModule();
assert module != null;
AndroidFacet facet = AndroidFacet.getInstance(module);
assert facet != null;
AndroidPlatform platform = AndroidPlatform.getInstance(module);
if (platform != null) {
templateParameters.put(ATTR_BUILD_API, platform.getTarget().getVersion().getFeatureLevel());
templateParameters.put(ATTR_BUILD_API_STRING, getBuildApiString(platform.getTarget().getVersion()));
}
// Read minSdkVersion and package from manifest and/or build.gradle files
AndroidModuleInfo moduleInfo = AndroidModuleInfo.get(facet);
IdeaAndroidProject androidModel = facet.getAndroidModel();
SourceProvider sourceProvider1 = myState.get(KEY_SOURCE_PROVIDER);
if (sourceProvider1 != null && androidModel != null) {
String packageName = myState.get(KEY_PACKAGE_NAME);
assert packageName != null;
templateParameters.putAll(selectSourceProvider(sourceProvider1, androidModel, module, packageName));
}
AndroidVersion minSdkVersion = moduleInfo.getMinSdkVersion();
String minSdkName = minSdkVersion.getApiString();
templateParameters.put(ATTR_MIN_API, minSdkName);
templateParameters.put(ATTR_TARGET_API, moduleInfo.getTargetSdkVersion().getApiLevel());
templateParameters.put(ATTR_MIN_API_LEVEL, minSdkVersion.getFeatureLevel());
templateParameters.put(ATTR_IS_LIBRARY_MODULE, facet.isLibraryProject());
try {
templateParameters.put(ATTR_DEBUG_KEYSTORE_SHA1, KeystoreUtils.sha1(getDebugKeystore(facet)));
}
catch (Exception e) {
LOG.info("Could not compute SHA1 hash of debug keystore.", e);
templateParameters.put(ATTR_DEBUG_KEYSTORE_SHA1, "");
}
Project project = getProject();
assert project != null;
templateParameters.put(NewModuleWizardState.ATTR_PROJECT_LOCATION, project.getBasePath());
// We're really interested in the directory name on disk, not the module name. These will be different if you give a module the same
// name as its containing project.
String moduleName = new File(module.getModuleFilePath()).getParentFile().getName();
templateParameters.put(FormFactorUtils.ATTR_MODULE_NAME, moduleName);
return templateParameters;
}
public String getActionDescription() {
TemplateEntry template = myState.get(KEY_SELECTED_TEMPLATE);
return String.format("Add %1$s", template == null ? "Template" : template.getTitle());
}
}