Home | History | Annotate | Download | only in actions
      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