blob: 4309566da735e63672fc0b0ecfe0a433c322e3dd [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.gradle.project;
import com.android.builder.model.AndroidProject;
import com.android.ide.common.repository.SdkMavenRepository;
import com.android.sdklib.AndroidTargetHash;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.descriptors.IPkgDesc;
import com.android.sdklib.repository.descriptors.PkgDesc;
import com.android.sdklib.repository.descriptors.PkgType;
import com.android.sdklib.repository.local.LocalSdk;
import com.android.tools.idea.gradle.GradleSyncState;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.gradle.customizer.android.DependenciesModuleCustomizer;
import com.android.tools.idea.gradle.dependency.LibraryDependency;
import com.android.tools.idea.gradle.eclipse.ImportModule;
import com.android.tools.idea.gradle.messages.Message;
import com.android.tools.idea.gradle.messages.ProjectSyncMessages;
import com.android.tools.idea.gradle.service.notification.hyperlink.InstallPlatformHyperlink;
import com.android.tools.idea.gradle.service.notification.hyperlink.NotificationHyperlink;
import com.android.tools.idea.gradle.service.notification.hyperlink.OpenAndroidSdkManagerHyperlink;
import com.android.tools.idea.gradle.service.notification.hyperlink.OpenUrlHyperlink;
import com.android.tools.idea.gradle.util.ProjectBuilder;
import com.android.tools.idea.gradle.variant.conflict.Conflict;
import com.android.tools.idea.gradle.variant.conflict.ConflictSet;
import com.android.tools.idea.gradle.variant.profiles.ProjectProfileSelectionDialog;
import com.android.tools.idea.rendering.ProjectResourceRepository;
import com.android.tools.idea.sdk.IdeSdks;
import com.android.tools.idea.sdk.VersionCheck;
import com.android.tools.idea.sdk.wizard.SdkQuickfixWizard;
import com.android.tools.idea.templates.TemplateManager;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.jarFinder.InternetAttachSourceProvider;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.roots.libraries.ui.OrderRoot;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.NonNavigatable;
import com.intellij.util.SystemProperties;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.ResourceFolderManager;
import org.jetbrains.android.sdk.AndroidSdkAdditionalData;
import org.jetbrains.android.sdk.AndroidSdkData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
import static com.android.SdkConstants.FN_ANNOTATIONS_JAR;
import static com.android.SdkConstants.FN_FRAMEWORK_LIBRARY;
import static com.android.tools.idea.gradle.customizer.AbstractDependenciesModuleCustomizer.pathToUrl;
import static com.android.tools.idea.gradle.messages.CommonMessageGroupNames.FAILED_TO_SET_UP_SDK;
import static com.android.tools.idea.gradle.project.LibraryAttachments.getStoredLibraryAttachments;
import static com.android.tools.idea.gradle.project.ProjectDiagnostics.findAndReportStructureIssues;
import static com.android.tools.idea.gradle.project.ProjectJdkChecks.hasCorrectJdkVersion;
import static com.android.tools.idea.gradle.service.notification.errors.AbstractSyncErrorHandler.FAILED_TO_SYNC_GRADLE_PROJECT_ERROR_GROUP_FORMAT;
import static com.android.tools.idea.gradle.util.GradleUtil.getAndroidProject;
import static com.android.tools.idea.gradle.util.Projects.*;
import static com.android.tools.idea.gradle.variant.conflict.ConflictResolution.solveSelectionConflicts;
import static com.android.tools.idea.gradle.variant.conflict.ConflictSet.findConflicts;
import static com.android.tools.idea.startup.ExternalAnnotationsSupport.attachJdkAnnotations;
import static com.intellij.notification.NotificationType.INFORMATION;
import static com.intellij.openapi.roots.OrderRootType.CLASSES;
import static com.intellij.openapi.roots.OrderRootType.SOURCES;
import static com.intellij.openapi.util.io.FileUtil.*;
import static com.intellij.openapi.vfs.StandardFileSystems.JAR_PROTOCOL_PREFIX;
import static com.intellij.util.ArrayUtil.toStringArray;
import static com.intellij.util.io.URLUtil.JAR_SEPARATOR;
import static org.jetbrains.android.sdk.AndroidSdkUtils.*;
public class PostProjectSetupTasksExecutor {
private static final String SOURCES_JAR_NAME_SUFFIX = "-sources.jar";
/** Whether a message indicating that "a new SDK Tools version is available" is already shown. */
private static boolean ourNewSdkVersionToolsInfoAlreadyShown;
/** Whether we've checked for build expiration */
private static boolean ourCheckedExpiration;
private static final boolean DEFAULT_GENERATE_SOURCES_AFTER_SYNC = true;
private static final boolean DEFAULT_USING_CACHED_PROJECT_DATA = false;
private static final long DEFAULT_LAST_SYNC_TIMESTAMP = -1;
@NotNull private final Project myProject;
private volatile boolean myGenerateSourcesAfterSync = DEFAULT_GENERATE_SOURCES_AFTER_SYNC;
private volatile boolean myUsingCachedProjectData = DEFAULT_USING_CACHED_PROJECT_DATA;
private volatile long myLastSyncTimestamp = DEFAULT_LAST_SYNC_TIMESTAMP;
@NotNull
public static PostProjectSetupTasksExecutor getInstance(@NotNull Project project) {
return ServiceManager.getService(project, PostProjectSetupTasksExecutor.class);
}
public PostProjectSetupTasksExecutor(@NotNull Project project) {
myProject = project;
}
/**
* Invoked after a project has been synced with Gradle.
*/
public void onProjectSyncCompletion() {
ProjectSyncMessages messages = ProjectSyncMessages.getInstance(myProject);
messages.reportDependencySetupErrors();
messages.reportComponentIncompatibilities();
findAndReportStructureIssues(myProject);
ModuleManager moduleManager = ModuleManager.getInstance(myProject);
for (Module module : moduleManager.getModules()) {
if (!hasCorrectJdkVersion(module)) {
// we already displayed the error, no need to check each module.
break;
}
}
if (hasErrors(myProject)) {
addSdkLinkIfNecessary();
checkSdkToolsVersion(myProject);
updateGradleSyncState();
return;
}
executeProjectChanges(myProject, new Runnable() {
@Override
public void run() {
attachSourcesToLibraries();
adjustModuleStructures();
ensureValidSdks();
}
});
enforceExternalBuild(myProject);
AndroidGradleProjectComponent.getInstance(myProject).checkForSupportedModules();
findAndShowVariantConflicts();
checkSdkToolsVersion(myProject);
addSdkLinkIfNecessary();
ProjectResourceRepository.moduleRootsChanged(myProject);
updateGradleSyncState();
if (myGenerateSourcesAfterSync) {
ProjectBuilder.getInstance(myProject).generateSourcesOnly();
}
// set default value back.
myGenerateSourcesAfterSync = DEFAULT_GENERATE_SOURCES_AFTER_SYNC;
TemplateManager.getInstance().refreshDynamicTemplateMenu(myProject);
}
private void updateGradleSyncState() {
if (!myUsingCachedProjectData) {
// Notify "sync end" event first, to register the timestamp. Otherwise the cache (GradleProjectSyncData) will store the date of the
// previous sync, and not the one from the sync that just ended.
GradleSyncState.getInstance(myProject).syncEnded();
GradleProjectSyncData.save(myProject);
} else {
long lastSyncTimestamp = myLastSyncTimestamp;
if (lastSyncTimestamp == DEFAULT_LAST_SYNC_TIMESTAMP) {
lastSyncTimestamp = System.currentTimeMillis();
}
GradleSyncState.getInstance(myProject).syncSkipped(lastSyncTimestamp);
}
// set default value back.
myUsingCachedProjectData = DEFAULT_USING_CACHED_PROJECT_DATA;
myLastSyncTimestamp = DEFAULT_LAST_SYNC_TIMESTAMP;
}
private void adjustModuleStructures() {
Set<Sdk> androidSdks = Sets.newHashSet();
ModuleManager moduleManager = ModuleManager.getInstance(myProject);
for (Module module : moduleManager.getModules()) {
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
adjustInterModuleDependencies(moduleRootManager);
Sdk sdk = moduleRootManager.getSdk();
if (sdk != null) {
if (isAndroidSdk(sdk)) {
androidSdks.add(sdk);
}
continue;
}
ModifiableRootModel model = moduleRootManager.getModifiableModel();
try {
Sdk jdk = IdeSdks.getJdk();
model.setSdk(jdk);
}
finally {
model.commit();
}
}
for (Sdk sdk: androidSdks) {
refreshLibrariesIn(sdk);
}
removeAllModuleCompiledArtifacts(myProject);
}
private static void adjustInterModuleDependencies(@NotNull ModuleRootManager moduleRootManager) {
// Verifies that inter-module dependencies between Android modules are correctly set. If module A depends on module B, and module B
// does not contain sources but exposes an AAR as an artifact, the IDE should set the dependency in the 'exploded AAR' instead of trying
// to find the library in module B. The 'exploded AAR' is in the 'build' folder of module A.
// See: https://code.google.com/p/android/issues/detail?id=162634
AndroidProject androidProject = getAndroidProject(moduleRootManager.getModule());
if (androidProject == null) {
return;
}
ModifiableRootModel modifiableModel = moduleRootManager.getModifiableModel();
try {
for (Module dependency : moduleRootManager.getModuleDependencies()) {
AndroidProject dependencyAndroidProject = getAndroidProject(dependency);
if (dependencyAndroidProject == null) {
LibraryDependency backup = getModuleCompiledArtifact(dependency);
if (backup != null) {
DependenciesModuleCustomizer.updateLibraryDependency(modifiableModel, backup, androidProject);
}
}
}
}
finally {
modifiableModel.commit();
}
}
// After a sync, the contents of an IDEA SDK does not get refreshed. This is an issue when an IDEA SDK is corrupt (e.g. missing libraries
// like android.jar) and then it is restored by installing the missing platform from within the IDE (using a "quick fix.") After the
// automatic project sync (triggered by the SDK restore) the contents of the SDK are not refreshed, and references to Android classes are
// not found in editors. Removing and adding the libraries effectively refreshes the contents of the IDEA SDK, and references in editors
// work again.
private static void refreshLibrariesIn(@NotNull Sdk sdk) {
VirtualFile[] libraries = sdk.getRootProvider().getFiles(CLASSES);
SdkModificator sdkModificator = sdk.getSdkModificator();
sdkModificator.removeRoots(CLASSES);
sdkModificator.commitChanges();
sdkModificator = sdk.getSdkModificator();
for (VirtualFile library : libraries) {
sdkModificator.addRoot(library, CLASSES);
}
sdkModificator.commitChanges();
}
private void attachSourcesToLibraries() {
LibraryTable libraryTable = ProjectLibraryTable.getInstance(myProject);
LibraryAttachments storedLibraryAttachments = getStoredLibraryAttachments(myProject);
for (Library library : libraryTable.getLibraries()) {
Set<String> sourcePaths = Sets.newHashSet();
for (VirtualFile file : library.getFiles(SOURCES)) {
sourcePaths.add(file.getUrl());
}
Library.ModifiableModel libraryModel = library.getModifiableModel();
// Find the source attachment based on the location of the library jar file.
for (VirtualFile classFile : library.getFiles(CLASSES)) {
VirtualFile sourceJar = findSourceJarForJar(classFile);
if (sourceJar != null) {
String url = pathToUrl(sourceJar.getPath());
if (!sourcePaths.contains(url)) {
libraryModel.addRoot(url, SOURCES);
sourcePaths.add(url);
}
}
}
if (storedLibraryAttachments != null) {
storedLibraryAttachments.addUrlsTo(libraryModel);
}
libraryModel.commit();
}
if (storedLibraryAttachments != null) {
storedLibraryAttachments.removeFromProject();
}
}
@Nullable
private static VirtualFile findSourceJarForJar(@NotNull VirtualFile jarFile) {
// We need to get the real jar file. The one that we received is just a wrapper around a URL. Getting the parent from this file returns
// null.
File jarFilePath = getJarFromJarUrl(jarFile.getUrl());
if (jarFilePath == null) {
return null;
}
File sourceJarPath = getSourceJarForAndroidSupportAar(jarFilePath);
if (sourceJarPath != null) {
return VfsUtil.findFileByIoFile(sourceJarPath, true);
}
VirtualFile realJarFile = VfsUtil.findFileByIoFile(jarFilePath, true);
if (realJarFile == null) {
// Unlikely to happen. At this point the jar file should exist.
return null;
}
VirtualFile parent = realJarFile.getParent();
String sourceFileName = jarFile.getNameWithoutExtension() + SOURCES_JAR_NAME_SUFFIX;
if (parent != null) {
// Try finding sources in the same folder as the jar file. This is the layout of Maven repositories.
VirtualFile sourceJar = parent.findChild(sourceFileName);
if (sourceJar != null) {
return sourceJar;
}
// Try the parent's parent. This is the layout of the repository cache in .gradle folder.
parent = parent.getParent();
if (parent != null) {
for (VirtualFile child : parent.getChildren()) {
if (!child.isDirectory()) {
continue;
}
sourceJar = child.findChild(sourceFileName);
if (sourceJar != null) {
return sourceJar;
}
}
}
}
// Try IDEA's own cache.
File librarySourceDirPath = InternetAttachSourceProvider.getLibrarySourceDir();
File sourceJar = new File(librarySourceDirPath, sourceFileName);
return VfsUtil.findFileByIoFile(sourceJar, true);
}
/**
* Provides the path of the source jar for the libraries in the group "com.android.support" in the Android Support Maven repository (in
* the Android SDK.)
* <p>
* Since AndroidProject (the Gradle model) does not provide the location of the source jar for aar libraries, we can deduce it from the
* path of the "exploded aar".
* </p>
*/
@Nullable
private static File getSourceJarForAndroidSupportAar(@NotNull File jarFilePath) {
String path = jarFilePath.getPath();
if (!path.contains(ImportModule.SUPPORT_GROUP_ID)) {
return null;
}
int startingIndex = -1;
List<String> pathSegments = splitPath(jarFilePath.getParentFile().getPath());
int segmentCount = pathSegments.size();
for (int i = 0; i < segmentCount; i++) {
if (ResourceFolderManager.EXPLODED_AAR.equals(pathSegments.get(i))) {
startingIndex = i + 1;
break;
}
}
if (startingIndex == -1 || startingIndex >= segmentCount) {
return null;
}
List<String> sourceJarRelativePath = Lists.newArrayList();
String groupId = pathSegments.get(startingIndex++);
if (ImportModule.SUPPORT_GROUP_ID.equals(groupId)) {
File androidHomePath = IdeSdks.getAndroidSdkPath();
File repositoryLocation = SdkMavenRepository.ANDROID.getRepositoryLocation(androidHomePath, true);
if (repositoryLocation != null) {
sourceJarRelativePath.addAll(Splitter.on('.').splitToList(groupId));
String artifactId = pathSegments.get(startingIndex++);
sourceJarRelativePath.add(artifactId);
String version = pathSegments.get(startingIndex);
sourceJarRelativePath.add(version);
String sourceJarName = artifactId + "-" + version + SOURCES_JAR_NAME_SUFFIX;
sourceJarRelativePath.add(sourceJarName);
File sourceJar = new File(repositoryLocation, join(toStringArray(sourceJarRelativePath)));
return sourceJar.isFile() ? sourceJar : null;
}
}
return null;
}
@Nullable
private static File getJarFromJarUrl(@NotNull String url) {
// URLs for jar file start with "jar://" and end with "!/".
if (!url.startsWith(JAR_PROTOCOL_PREFIX)) {
return null;
}
String path = url.substring(JAR_PROTOCOL_PREFIX.length());
int index = path.lastIndexOf(JAR_SEPARATOR);
if (index != -1) {
path = path.substring(0, index);
}
return new File(toSystemDependentName(path));
}
private void findAndShowVariantConflicts() {
ConflictSet conflicts = findConflicts(myProject);
List<Conflict> structureConflicts = conflicts.getStructureConflicts();
if (!structureConflicts.isEmpty() && SystemProperties.getBooleanProperty("enable.project.profiles", false)) {
ProjectProfileSelectionDialog dialog = new ProjectProfileSelectionDialog(myProject, structureConflicts);
dialog.show();
}
List<Conflict> selectionConflicts = conflicts.getSelectionConflicts();
if (!selectionConflicts.isEmpty()) {
boolean atLeastOneSolved = solveSelectionConflicts(selectionConflicts);
if (atLeastOneSolved) {
conflicts = findConflicts(myProject);
}
}
conflicts.showSelectionConflicts();
}
private void addSdkLinkIfNecessary() {
ProjectSyncMessages messages = ProjectSyncMessages.getInstance(myProject);
int sdkErrorCount = messages.getMessageCount(FAILED_TO_SET_UP_SDK);
if (sdkErrorCount > 0) {
// If we have errors due to platforms not being installed, we add an extra message that prompts user to open Android SDK manager and
// install any missing platforms.
String text = "Open Android SDK Manager and install all missing platforms.";
Message hint = new Message(FAILED_TO_SET_UP_SDK, Message.Type.INFO, NonNavigatable.INSTANCE, text);
messages.add(hint, new OpenAndroidSdkManagerHyperlink());
}
}
private static void checkSdkToolsVersion(@NotNull Project project) {
if (project.isDisposed() || ourNewSdkVersionToolsInfoAlreadyShown) {
return;
}
// Piggy-back off of the SDK update check (which is called from a handful of places) to also see if this is an expired preview build
checkExpiredPreviewBuild(project);
File androidHome = IdeSdks.getAndroidSdkPath();
if (androidHome != null && !VersionCheck.isCompatibleVersion(androidHome)) {
InstallSdkToolsHyperlink hyperlink = new InstallSdkToolsHyperlink(VersionCheck.MIN_TOOLS_REV);
String message = "Version " + VersionCheck.MIN_TOOLS_REV + " is available.";
AndroidGradleNotification.getInstance(project).showBalloon("Android SDK Tools", message, INFORMATION, hyperlink);
ourNewSdkVersionToolsInfoAlreadyShown = true;
}
}
private static void checkExpiredPreviewBuild(@NotNull Project project) {
if (project.isDisposed() || ourCheckedExpiration) {
return;
}
String fullVersion = ApplicationInfo.getInstance().getFullVersion();
if (fullVersion.contains("Preview") || fullVersion.contains("Beta") || fullVersion.contains("RC")) {
// Expire preview builds two months after their build date (which is going to be roughly six weeks after release; by
// then will definitely have updated the build
Calendar expirationDate = (Calendar)ApplicationInfo.getInstance().getBuildDate().clone();
expirationDate.add(Calendar.MONTH, 2);
Calendar now = Calendar.getInstance();
if (now.after(expirationDate)) {
OpenUrlHyperlink hyperlink = new OpenUrlHyperlink("http://tools.android.com/download/studio/", "Show Available Versions");
String message = String.format("This preview build (%1$s) is old; please update to a newer preview or a stable version",
fullVersion);
AndroidGradleNotification.getInstance(project).showBalloon("Old Preview Build", message, INFORMATION, hyperlink);
// If we show an expiration message, don't also show a second balloon regarding available SDKs
ourNewSdkVersionToolsInfoAlreadyShown = true;
}
}
ourCheckedExpiration = true;
}
private void ensureValidSdks() {
boolean checkJdkVersion = true;
Collection<Sdk> invalidAndroidSdks = Sets.newHashSet();
ModuleManager moduleManager = ModuleManager.getInstance(myProject);
for (Module module : moduleManager.getModules()) {
AndroidFacet androidFacet = AndroidFacet.getInstance(module);
if (androidFacet != null && androidFacet.getAndroidModel() != null) {
Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk != null && !invalidAndroidSdks.contains(sdk) && (isMissingAndroidLibrary(sdk) || shouldRemoveAnnotationsJar(sdk))) {
// First try to recreate SDK; workaround for issue 78072
AndroidSdkAdditionalData additionalData = getAndroidSdkAdditionalData(sdk);
AndroidSdkData sdkData = AndroidSdkData.getSdkData(sdk);
if (additionalData != null && sdkData != null) {
IAndroidTarget target = additionalData.getBuildTarget(sdkData);
if (target == null) {
LocalSdk localSdk = sdkData.getLocalSdk();
localSdk.clearLocalPkg(EnumSet.of(PkgType.PKG_PLATFORM));
target = localSdk.getTargetFromHashString(additionalData.getBuildTargetHashString());
}
if (target != null) {
SdkModificator sdkModificator = sdk.getSdkModificator();
sdkModificator.removeAllRoots();
for (OrderRoot orderRoot : getLibraryRootsForTarget(target, sdk.getHomePath(), true)) {
sdkModificator.addRoot(orderRoot.getFile(), orderRoot.getType());
}
attachJdkAnnotations(sdkModificator);
sdkModificator.commitChanges();
}
}
// If attempting to fix up the roots in the SDK fails, install the target over again
// (this is a truly corrupt install, as opposed to an incorrectly synced SDK which the
// above workaround deals with)
if (isMissingAndroidLibrary(sdk)) {
invalidAndroidSdks.add(sdk);
}
}
IdeaAndroidProject androidModel = androidFacet.getAndroidModel();
if (checkJdkVersion && !hasCorrectJdkVersion(module, androidModel)) {
// we already displayed the error, no need to check each module.
checkJdkVersion = false;
}
}
}
if (!invalidAndroidSdks.isEmpty()) {
reinstallMissingPlatforms(invalidAndroidSdks);
}
}
private static boolean isMissingAndroidLibrary(@NotNull Sdk sdk) {
if (isAndroidSdk(sdk)) {
for (VirtualFile library : sdk.getRootProvider().getFiles(CLASSES)) {
// This code does not through the classes in the Android SDK. It iterates through a list of 3 files in the IDEA SDK: android.jar,
// annotations.jar and res folder.
if (library.getName().equals(FN_FRAMEWORK_LIBRARY) && library.exists()) {
return false;
}
}
}
return true;
}
/*
* Indicates whether annotations.jar should be removed from the given SDK (if it is an Android SDK.)
* There are 2 issues:
* 1. annotations.jar is not needed for API level 16 and above. The annotations are already included in android.jar. Until recently, the
* IDE added annotations.jar to the IDEA Android SDK definition unconditionally.
* 2. Because annotations.jar is in the classpath, the IDE locks the file on Windows making automatic updates of SDK Tools fail. The
* update not only fails, it corrupts the 'tools' folder in the SDK.
* From now on, creating IDEA Android SDKs will not include annotations.jar if API level is 16 or above, but we still need to remove
* this jar from existing IDEA Android SDKs.
*/
private static boolean shouldRemoveAnnotationsJar(@NotNull Sdk sdk) {
if (isAndroidSdk(sdk)) {
AndroidSdkAdditionalData additionalData = getAndroidSdkAdditionalData(sdk);
AndroidSdkData sdkData = AndroidSdkData.getSdkData(sdk);
boolean needsAnnotationsJar = false;
if (additionalData != null && sdkData != null) {
IAndroidTarget target = additionalData.getBuildTarget(sdkData);
if (target != null) {
needsAnnotationsJar = needsAnnotationsJarInClasspath(target);
}
}
for (VirtualFile library : sdk.getRootProvider().getFiles(CLASSES)) {
// This code does not through the classes in the Android SDK. It iterates through a list of 3 files in the IDEA SDK: android.jar,
// annotations.jar and res folder.
if (library.getName().equals(FN_ANNOTATIONS_JAR) && library.exists() && !needsAnnotationsJar) {
return true;
}
}
}
return false;
}
private void reinstallMissingPlatforms(@NotNull Collection<Sdk> invalidAndroidSdks) {
ProjectSyncMessages messages = ProjectSyncMessages.getInstance(myProject);
List<AndroidVersion> versionsToInstall = Lists.newArrayList();
List<String> missingPlatforms = Lists.newArrayList();
for (Sdk sdk : invalidAndroidSdks) {
AndroidSdkAdditionalData additionalData = getAndroidSdkAdditionalData(sdk);
if (additionalData != null) {
String platform = additionalData.getBuildTargetHashString();
if (platform != null) {
missingPlatforms.add("'" + platform + "'");
AndroidVersion version = AndroidTargetHash.getPlatformVersion(platform);
if (version != null) {
versionsToInstall.add(version);
}
}
}
}
if (!versionsToInstall.isEmpty()) {
String group = String.format(FAILED_TO_SYNC_GRADLE_PROJECT_ERROR_GROUP_FORMAT, myProject.getName());
String text = "Missing Android platform(s) detected: " + Joiner.on(", ").join(missingPlatforms);
Message msg = new Message(group, Message.Type.ERROR, text);
messages.add(msg, new InstallPlatformHyperlink(versionsToInstall.toArray(new AndroidVersion[versionsToInstall.size()])));
}
}
public void setGenerateSourcesAfterSync(boolean generateSourcesAfterSync) {
myGenerateSourcesAfterSync = generateSourcesAfterSync;
}
public void setLastSyncTimestamp(long lastSyncTimestamp) {
myLastSyncTimestamp = lastSyncTimestamp;
}
public void setUsingCachedProjectData(boolean usingCachedProjectData) {
myUsingCachedProjectData = usingCachedProjectData;
}
private static class InstallSdkToolsHyperlink extends NotificationHyperlink {
@NotNull private final FullRevision myVersion;
InstallSdkToolsHyperlink(@NotNull FullRevision version) {
super("install.build.tools", "Install Tools " + version);
myVersion = version;
}
@Override
protected void execute(@NotNull Project project) {
List<IPkgDesc> requested = Lists.newArrayList();
if (myVersion.getMajor() == 23) {
FullRevision minBuildToolsRev = new FullRevision(20, 0, 0);
requested.add(PkgDesc.Builder.newPlatformTool(minBuildToolsRev).create());
}
requested.add(PkgDesc.Builder.newTool(myVersion, myVersion).create());
SdkQuickfixWizard wizard = new SdkQuickfixWizard(project, null, requested);
wizard.init();
if (wizard.showAndGet()) {
GradleProjectImporter.getInstance().requestProjectSync(project, null);
}
}
}
}