com.android.tools.idea.wizard.AddAndroidActivityPath.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.wizard.AddAndroidActivityPath.java

Source

/*
 * 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.wizard;

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.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 com.intellij.util.ui.UIUtil;
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.ScopedStateStore.Key;
import static com.android.tools.idea.wizard.ScopedStateStore.Scope.PATH;
import static com.android.tools.idea.wizard.ScopedStateStore.Scope.WIZARD;
import static com.android.tools.idea.wizard.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 gradleProject, @NotNull Module module, @NotNull String packageName) {
        Map<String, Object> paths = Maps.newHashMap();
        // Look up the resource directories inside this source set
        File moduleDirPath = gradleProject.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(gradleProject.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 gradleProject = AndroidFacet.getInstance(getModule()).getIdeaAndroidProject();
        assert gradleProject != null;
        return gradleProject.computePackageName();
    }

    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 gradleProject = facet.getIdeaAndroidProject();

        SourceProvider sourceProvider1 = myState.get(KEY_SOURCE_PROVIDER);
        if (sourceProvider1 != null && gradleProject != null) {
            String packageName = myState.get(KEY_PACKAGE_NAME);
            assert packageName != null;
            templateParameters.putAll(selectSourceProvider(sourceProvider1, gradleProject, 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());
    }
}