1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.actions; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.AdtUtils; 21 import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog; 22 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 23 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 24 import com.android.sdklib.SdkConstants; 25 import com.android.sdklib.internal.project.ProjectProperties; 26 import com.android.sdklib.internal.project.ProjectProperties.PropertyType; 27 import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; 28 import com.android.sdklib.io.FileOp; 29 import com.android.sdkuilib.internal.repository.sdkman2.AdtUpdateDialog; 30 import com.android.util.Pair; 31 32 import org.eclipse.core.filesystem.EFS; 33 import org.eclipse.core.filesystem.IFileStore; 34 import org.eclipse.core.filesystem.IFileSystem; 35 import org.eclipse.core.resources.IFile; 36 import org.eclipse.core.resources.IFolder; 37 import org.eclipse.core.resources.IProject; 38 import org.eclipse.core.resources.IResource; 39 import org.eclipse.core.resources.IWorkspaceRoot; 40 import org.eclipse.core.resources.ResourcesPlugin; 41 import org.eclipse.core.runtime.CoreException; 42 import org.eclipse.core.runtime.IAdaptable; 43 import org.eclipse.core.runtime.IPath; 44 import org.eclipse.core.runtime.IProgressMonitor; 45 import org.eclipse.core.runtime.IStatus; 46 import org.eclipse.core.runtime.NullProgressMonitor; 47 import org.eclipse.core.runtime.Status; 48 import org.eclipse.core.runtime.jobs.Job; 49 import org.eclipse.jdt.core.IJavaProject; 50 import org.eclipse.jdt.core.JavaCore; 51 import org.eclipse.jface.action.IAction; 52 import org.eclipse.jface.viewers.ISelection; 53 import org.eclipse.jface.viewers.IStructuredSelection; 54 import org.eclipse.ui.IObjectActionDelegate; 55 import org.eclipse.ui.IWorkbenchPart; 56 import org.eclipse.ui.IWorkbenchWindow; 57 import org.eclipse.ui.IWorkbenchWindowActionDelegate; 58 59 import java.io.File; 60 import java.io.IOException; 61 import java.util.Iterator; 62 63 /** 64 * An action to add the android-support-v4.jar compatibility library 65 * to the selected project. 66 * <p/> 67 * This should be used by the GLE. The action itself is currently more 68 * like an example of how to invoke the new {@link AdtUpdateDialog}. 69 * <p/> 70 * TODO: make this more configurable. 71 */ 72 public class AddCompatibilityJarAction implements IObjectActionDelegate { 73 74 private ISelection mSelection; 75 76 /** 77 * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart) 78 */ 79 @Override 80 public void setActivePart(IAction action, IWorkbenchPart targetPart) { 81 } 82 83 @Override 84 public void run(IAction action) { 85 if (mSelection instanceof IStructuredSelection) { 86 87 for (Iterator<?> it = ((IStructuredSelection) mSelection).iterator(); 88 it.hasNext();) { 89 Object element = it.next(); 90 IProject project = null; 91 if (element instanceof IProject) { 92 project = (IProject) element; 93 } else if (element instanceof IAdaptable) { 94 project = (IProject) ((IAdaptable) element) 95 .getAdapter(IProject.class); 96 } 97 if (project != null) { 98 install(project); 99 } 100 } 101 } 102 } 103 104 @Override 105 public void selectionChanged(IAction action, ISelection selection) { 106 mSelection = selection; 107 } 108 109 /** 110 * Install the compatibility jar into the given project. 111 * 112 * @param project The Android project to install the compatibility jar into 113 * @return true if the installation was successful 114 */ 115 public static boolean install(final IProject project) { 116 File jarPath = installSupport(); 117 if (jarPath != null) { 118 try { 119 return copyJarIntoProject(project, jarPath) != null; 120 } catch (Exception e) { 121 AdtPlugin.log(e, null); 122 } 123 } 124 125 return false; 126 } 127 128 private static File installSupport() { 129 130 final Sdk sdk = Sdk.getCurrent(); 131 if (sdk == null) { 132 AdtPlugin.printErrorToConsole( 133 AddCompatibilityJarAction.class.getSimpleName(), // tag 134 "Error: Android SDK is not loaded yet."); //$NON-NLS-1$ 135 return null; 136 } 137 138 // TODO: For the generic action, check the library isn't in the project already. 139 140 // First call the package manager to make sure the package is installed 141 // and get the installation path of the library. 142 143 AdtUpdateDialog window = new AdtUpdateDialog( 144 AdtPlugin.getDisplay().getActiveShell(), 145 new AdtConsoleSdkLog(), 146 sdk.getSdkLocation()); 147 148 Pair<Boolean, File> result = window.installExtraPackage( 149 "android", "support"); //$NON-NLS-1$ //$NON-NLS-2$ 150 151 // TODO: Make sure the version is at the required level; we know we need at least one 152 // containing the v7 support 153 154 if (!result.getFirst().booleanValue()) { 155 AdtPlugin.printErrorToConsole("Failed to install Android Compatibility library"); 156 return null; 157 } 158 159 // TODO these "v4" values needs to be dynamic, e.g. we could try to match 160 // vN/android-support-vN.jar. Eventually we'll want to rely on info from the 161 // package manifest anyway so this is irrelevant. 162 163 File path = new File(result.getSecond(), "v4"); //$NON-NLS-1$ 164 final File jarPath = new File(path, "android-support-v4.jar"); //$NON-NLS-1$ 165 166 if (!jarPath.isFile()) { 167 AdtPlugin.printErrorToConsole("Android Compatibility JAR not found:", 168 jarPath.getAbsolutePath()); 169 return null; 170 } 171 172 return jarPath; 173 } 174 175 /** 176 * Similar to {@link #install}, but rather than copy a jar into the given 177 * project, it creates a new library project in the workspace for the 178 * compatibility library, and adds a library dependency on the newly 179 * installed library from the given project. 180 * 181 * @param project the project to add a dependency on the library to 182 * @param waitForFinish If true, block until the task has finished 183 * @return true if the installation was successful (or if 184 * <code>waitForFinish</code> is false, if the installation is 185 * likely to be successful - e.g. the user has at least agreed to 186 * all installation prompts.) 187 */ 188 public static boolean installLibrary(final IProject project, boolean waitForFinish) { 189 final IJavaProject javaProject = JavaCore.create(project); 190 if (javaProject != null) { 191 192 File sdk = new File(Sdk.getCurrent().getSdkLocation()); 193 File supportPath = new File(sdk, 194 SdkConstants.FD_EXTRAS + File.separator 195 + "android" + File.separator //$NON-NLS-1$ 196 + "support"); //$NON-NLS-1$ 197 if (!supportPath.isDirectory()) { 198 File path = installSupport(); 199 if (path == null) { 200 return false; 201 } 202 assert path.equals(supportPath); 203 } 204 File libraryPath = new File(supportPath, 205 "v7" + File.separator //$NON-NLS-1$ 206 + "gridlayout"); //$NON-NLS-1$ 207 if (!libraryPath.isDirectory()) { 208 // Upgrade support package: it's out of date. The SDK manager will 209 // perform an upgrade to the latest version if the package is already installed. 210 File path = installSupport(); 211 if (path == null) { 212 return false; 213 } 214 assert path.equals(libraryPath) : path; 215 } 216 217 // Create workspace copy of the project and add library dependency 218 IProject libraryProject = createLibraryProject(libraryPath, project, waitForFinish); 219 if (libraryProject != null) { 220 return addLibraryDependency(libraryProject, project, waitForFinish); 221 } 222 } 223 224 return false; 225 } 226 227 /** 228 * Creates a library project in the Eclipse workspace out of the grid layout project 229 * in the SDK tree. 230 * 231 * @param libraryPath the path to the directory tree containing the project contents 232 * @param project the project to copy the SDK target out of 233 * @param waitForFinish whether the operation should finish before this method returns 234 * @return a library project, or null if it fails for some reason 235 */ 236 private static IProject createLibraryProject( 237 final File libraryPath, 238 final IProject project, 239 boolean waitForFinish) { 240 241 // Install a new library into the workspace. This is a copy rather than 242 // a reference to the compatibility library version such that modifications 243 // do not modify the pristine copy in the SDK install area. 244 245 final IProject newProject; 246 try { 247 IProgressMonitor monitor = new NullProgressMonitor(); 248 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 249 250 String name = AdtUtils.getUniqueProjectName( 251 "gridlayout_v7", "_"); //$NON-NLS-1$ //$NON-NLS-2$ 252 newProject = root.getProject(name); 253 newProject.create(monitor); 254 255 // Copy in the files recursively 256 IFileSystem fileSystem = EFS.getLocalFileSystem(); 257 IFileStore sourceDir = fileSystem.getStore(libraryPath.toURI()); 258 IFileStore destDir = fileSystem.getStore(newProject.getLocationURI()); 259 sourceDir.copy(destDir, EFS.OVERWRITE, null); 260 261 // Make sure the src folder exists 262 destDir.getChild("src").mkdir(0, null /*monitor*/); 263 264 // Set the android platform to the same level as the calling project 265 ProjectState state = Sdk.getProjectState(project); 266 String target = state.getProperties().getProperty(ProjectProperties.PROPERTY_TARGET); 267 if (target != null && target.length() > 0) { 268 ProjectProperties properties = ProjectProperties.load(libraryPath.getPath(), 269 PropertyType.PROJECT); 270 ProjectPropertiesWorkingCopy copy = properties.makeWorkingCopy(); 271 copy.setProperty(ProjectProperties.PROPERTY_TARGET, target); 272 try { 273 copy.save(); 274 } catch (Exception e) { 275 AdtPlugin.log(e, null); 276 } 277 } 278 279 newProject.open(monitor); 280 281 return newProject; 282 } catch (CoreException e) { 283 AdtPlugin.log(e, null); 284 return null; 285 } 286 } 287 288 /** 289 * Adds a library dependency on the given library into the given project. 290 * 291 * @param libraryProject the library project to depend on 292 * @param dependentProject the project to write the dependency into 293 * @param waitForFinish whether this method should wait for the job to 294 * finish 295 * @return true if the operation succeeded 296 */ 297 public static boolean addLibraryDependency( 298 final IProject libraryProject, 299 final IProject dependentProject, 300 boolean waitForFinish) { 301 302 // Now add library dependency 303 304 // Run an Eclipse asynchronous job to update the project 305 Job job = new Job("Add Compatibility Library Dependency to Project") { 306 @Override 307 protected IStatus run(IProgressMonitor monitor) { 308 try { 309 monitor.beginTask("Add library dependency to project build path", 3); 310 monitor.worked(1); 311 312 // TODO: Add library project to the project.properties file! 313 ProjectState state = Sdk.getProjectState(dependentProject); 314 ProjectPropertiesWorkingCopy mPropertiesWorkingCopy = 315 state.getProperties().makeWorkingCopy(); 316 317 // Get the highest version number of the libraries; there cannot be any 318 // gaps so we will assign the next library the next number 319 int nextVersion = 1; 320 for (String property : mPropertiesWorkingCopy.keySet()) { 321 if (property.startsWith(ProjectProperties.PROPERTY_LIB_REF)) { 322 String s = property.substring( 323 ProjectProperties.PROPERTY_LIB_REF.length()); 324 int version = Integer.parseInt(s); 325 if (version >= nextVersion) { 326 nextVersion = version + 1; 327 } 328 } 329 } 330 331 IPath relativePath = libraryProject.getLocation().makeRelativeTo( 332 dependentProject.getLocation()); 333 334 mPropertiesWorkingCopy.setProperty( 335 ProjectProperties.PROPERTY_LIB_REF + nextVersion, 336 relativePath.toString()); 337 try { 338 mPropertiesWorkingCopy.save(); 339 IResource projectProp = dependentProject.findMember( 340 SdkConstants.FN_PROJECT_PROPERTIES); 341 projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); 342 } catch (Exception e) { 343 String msg = String.format( 344 "Failed to save %1$s for project %2$s", 345 SdkConstants.FN_PROJECT_PROPERTIES, dependentProject.getName()); 346 AdtPlugin.log(e, msg); 347 } 348 349 // Project fix-ups 350 Job fix = FixProjectAction.createFixProjectJob(libraryProject); 351 fix.schedule(); 352 fix.join(); 353 354 monitor.worked(1); 355 356 return Status.OK_STATUS; 357 } catch (Exception e) { 358 return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR, 359 "Failed", e); //$NON-NLS-1$ 360 } finally { 361 if (monitor != null) { 362 monitor.done(); 363 } 364 } 365 } 366 }; 367 job.schedule(); 368 369 if (waitForFinish) { 370 try { 371 job.join(); 372 return job.getState() == IStatus.OK; 373 } catch (InterruptedException e) { 374 AdtPlugin.log(e, null); 375 } 376 } 377 378 return true; 379 } 380 381 private static IResource copyJarIntoProject( 382 IProject project, 383 File jarPath) throws IOException, CoreException { 384 IFolder resFolder = project.getFolder(SdkConstants.FD_NATIVE_LIBS); 385 if (!resFolder.exists()) { 386 resFolder.create(IResource.FORCE, true /*local*/, null); 387 } 388 389 IFile destFile = resFolder.getFile(jarPath.getName()); 390 IPath loc = destFile.getLocation(); 391 File destPath = loc.toFile(); 392 393 // Only modify the file if necessary so that we don't trigger unnecessary recompilations 394 FileOp f = new FileOp(); 395 if (!f.isFile(destPath) || !f.isSameFile(jarPath, destPath)) { 396 f.copyFile(jarPath, destPath); 397 // Make sure Eclipse discovers java.io file changes 398 resFolder.refreshLocal(1, new NullProgressMonitor()); 399 } 400 401 return destFile; 402 } 403 404 /** 405 * @see IWorkbenchWindowActionDelegate#init 406 */ 407 public void init(IWorkbenchWindow window) { 408 // pass 409 } 410 411 } 412