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.SdkConstants;
     20 import com.android.annotations.Nullable;
     21 import com.android.ide.eclipse.adt.AdtConstants;
     22 import com.android.ide.eclipse.adt.AdtPlugin;
     23 import com.android.ide.eclipse.adt.AdtUtils;
     24 import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
     25 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     26 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     27 import com.android.sdklib.SdkManager;
     28 import com.android.sdklib.internal.project.ProjectProperties;
     29 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
     30 import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
     31 import com.android.sdklib.io.FileOp;
     32 import com.android.sdkuilib.internal.repository.ui.AdtUpdateDialog;
     33 import com.android.utils.NullLogger;
     34 import com.android.utils.Pair;
     35 
     36 import org.eclipse.core.filesystem.EFS;
     37 import org.eclipse.core.filesystem.IFileStore;
     38 import org.eclipse.core.filesystem.IFileSystem;
     39 import org.eclipse.core.resources.IFile;
     40 import org.eclipse.core.resources.IFolder;
     41 import org.eclipse.core.resources.IProject;
     42 import org.eclipse.core.resources.IProjectDescription;
     43 import org.eclipse.core.resources.IResource;
     44 import org.eclipse.core.resources.IWorkspace;
     45 import org.eclipse.core.resources.IWorkspaceRoot;
     46 import org.eclipse.core.resources.ResourcesPlugin;
     47 import org.eclipse.core.runtime.CoreException;
     48 import org.eclipse.core.runtime.IAdaptable;
     49 import org.eclipse.core.runtime.IPath;
     50 import org.eclipse.core.runtime.IProgressMonitor;
     51 import org.eclipse.core.runtime.IStatus;
     52 import org.eclipse.core.runtime.NullProgressMonitor;
     53 import org.eclipse.core.runtime.Status;
     54 import org.eclipse.core.runtime.jobs.Job;
     55 import org.eclipse.jdt.core.IJavaProject;
     56 import org.eclipse.jdt.core.JavaCore;
     57 import org.eclipse.jface.action.IAction;
     58 import org.eclipse.jface.viewers.ISelection;
     59 import org.eclipse.jface.viewers.IStructuredSelection;
     60 import org.eclipse.ui.IObjectActionDelegate;
     61 import org.eclipse.ui.IWorkbenchPart;
     62 import org.eclipse.ui.IWorkbenchWindow;
     63 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
     64 
     65 import java.io.File;
     66 import java.io.IOException;
     67 import java.util.Iterator;
     68 import java.util.Map;
     69 
     70 /**
     71  * An action to add the android-support-v4.jar support library
     72  * to the selected project.
     73  * <p/>
     74  * This should be used by the GLE. The action itself is currently more
     75  * like an example of how to invoke the new {@link AdtUpdateDialog}.
     76  * <p/>
     77  * TODO: make this more configurable.
     78  */
     79 public class AddSupportJarAction implements IObjectActionDelegate {
     80 
     81     /** The vendor ID of the support library. */
     82     private static final String VENDOR_ID = "android";                             //$NON-NLS-1$
     83     /** The path ID of the support library. */
     84     private static final String SUPPORT_ID = "support";                            //$NON-NLS-1$
     85     /** The path ID of the compatibility library (which was its id for releases 1-3). */
     86     private static final String COMPATIBILITY_ID = "compatibility";                //$NON-NLS-1$
     87     private static final String FD_GRIDLAYOUT = "gridlayout";                      //$NON-NLS-1$
     88     private static final String FD_V7 = "v7";                                      //$NON-NLS-1$
     89     private static final String FD_V4 = "v4";                                      //$NON-NLS-1$
     90     private static final String ANDROID_SUPPORT_V4_JAR = "android-support-v4.jar"; //$NON-NLS-1$
     91     private ISelection mSelection;
     92 
     93     /**
     94      * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
     95      */
     96     @Override
     97     public void setActivePart(IAction action, IWorkbenchPart targetPart) {
     98     }
     99 
    100     @Override
    101     public void run(IAction action) {
    102         if (mSelection instanceof IStructuredSelection) {
    103 
    104             for (Iterator<?> it = ((IStructuredSelection) mSelection).iterator();
    105                     it.hasNext();) {
    106                 Object element = it.next();
    107                 IProject project = null;
    108                 if (element instanceof IProject) {
    109                     project = (IProject) element;
    110                 } else if (element instanceof IAdaptable) {
    111                     project = (IProject) ((IAdaptable) element)
    112                             .getAdapter(IProject.class);
    113                 }
    114                 if (project != null) {
    115                     install(project);
    116                 }
    117             }
    118         }
    119     }
    120 
    121     @Override
    122     public void selectionChanged(IAction action, ISelection selection) {
    123         mSelection = selection;
    124     }
    125 
    126     /**
    127      * Install the support jar into the given project.
    128      *
    129      * @param project The Android project to install the support jar into
    130      * @return true if the installation was successful
    131      */
    132     public static boolean install(final IProject project) {
    133         File jarPath = installSupport(-1);
    134         if (jarPath != null) {
    135             try {
    136                 return copyJarIntoProject(project, jarPath) != null;
    137             } catch (Exception e) {
    138                 AdtPlugin.log(e, null);
    139             }
    140         }
    141 
    142         return false;
    143     }
    144 
    145     /**
    146      * Installs the Android Support library into the SDK extras/ folder. If a minimum
    147      * revision number is specified, this method will check whether the package is already
    148      * installed, and if the installed revision is at least as high as the requested revision,
    149      * this method will exit without performing an update.
    150      *
    151      * @param minimumRevision a minimum revision, or -1 to upgrade
    152      *            unconditionally. Note that this does <b>NOT</b> specify which
    153      *            revision to install; the latest version will always be
    154      *            installed.
    155      * @return the location of the support jar file, or null if something went
    156      *            wrong
    157      */
    158     @Nullable
    159     public static File installSupport(int minimumRevision) {
    160 
    161         final Sdk sdk = Sdk.getCurrent();
    162         if (sdk == null) {
    163             AdtPlugin.printErrorToConsole(
    164                     AddSupportJarAction.class.getSimpleName(),   // tag
    165                     "Error: Android SDK is not loaded yet."); //$NON-NLS-1$
    166             return null;
    167         }
    168 
    169         String sdkLocation = sdk.getSdkLocation();
    170         if (minimumRevision > 0) {
    171             File path = getSupportJarFile();
    172             if (path != null) {
    173                 assert path.exists(); // guaranteed by the getSupportJarFile call
    174                 int installedRevision = getInstalledRevision();
    175                 if (installedRevision != -1 && minimumRevision <= installedRevision) {
    176                     return path;
    177                 }
    178             }
    179         }
    180 
    181         // TODO: For the generic action, check the library isn't in the project already.
    182 
    183         // First call the package manager to make sure the package is installed
    184         // and get the installation path of the library.
    185 
    186         AdtUpdateDialog window = new AdtUpdateDialog(
    187                 AdtPlugin.getShell(),
    188                 new AdtConsoleSdkLog(),
    189                 sdkLocation);
    190 
    191         Pair<Boolean, File> result = window.installExtraPackage(VENDOR_ID, SUPPORT_ID);
    192 
    193         // TODO: Make sure the version is at the required level; we know we need at least one
    194         // containing the v7 support
    195 
    196         if (!result.getFirst().booleanValue()) {
    197             AdtPlugin.printErrorToConsole("Failed to install Android Support library");
    198             return null;
    199         }
    200 
    201         // TODO these "v4" values needs to be dynamic, e.g. we could try to match
    202         // vN/android-support-vN.jar. Eventually we'll want to rely on info from the
    203         // package manifest anyway so this is irrelevant.
    204 
    205         File path = new File(result.getSecond(), FD_V4);
    206         final File jarPath = new File(path, ANDROID_SUPPORT_V4_JAR);
    207 
    208         if (!jarPath.isFile()) {
    209             AdtPlugin.printErrorToConsole("Android Support Jar not found:",
    210                     jarPath.getAbsolutePath());
    211             return null;
    212         }
    213 
    214         return jarPath;
    215     }
    216 
    217     /**
    218      * Returns the installed revision number of the Android Support
    219      * library, or -1 if the package is not installed.
    220      *
    221      * @return the installed revision number, or -1
    222      */
    223     public static int getInstalledRevision() {
    224         final Sdk sdk = Sdk.getCurrent();
    225         if (sdk != null) {
    226             String sdkLocation = sdk.getSdkLocation();
    227             SdkManager manager = SdkManager.createManager(sdkLocation, NullLogger.getLogger());
    228             Map<String, Integer> versions = manager.getExtrasVersions();
    229             Integer version = versions.get(VENDOR_ID + '/' + SUPPORT_ID);
    230             if (version == null) {
    231                 // Check the old compatibility library. When the library is updated in-place
    232                 // the manager doesn't change its folder name (since that is a source of
    233                 // endless issues on Windows.)
    234                 version = versions.get(VENDOR_ID + '/' + COMPATIBILITY_ID);
    235             }
    236             if (version != null) {
    237                 return version.intValue();
    238             }
    239         }
    240 
    241        return -1;
    242     }
    243 
    244     /**
    245      * Similar to {@link #install}, but rather than copy a jar into the given
    246      * project, it creates a new library project in the workspace for the
    247      * support library, and adds a library dependency on the newly
    248      * installed library from the given project.
    249      *
    250      * @param project the project to add a dependency on the library to
    251      * @param waitForFinish If true, block until the task has finished
    252      * @return true if the installation was successful (or if
    253      *         <code>waitForFinish</code> is false, if the installation is
    254      *         likely to be successful - e.g. the user has at least agreed to
    255      *         all installation prompts.)
    256      */
    257     public static boolean installGridLayoutLibrary(final IProject project, boolean waitForFinish) {
    258         final IJavaProject javaProject = JavaCore.create(project);
    259         if (javaProject != null) {
    260 
    261             File supportPath = getSupportPackageDir();
    262             if (!supportPath.isDirectory()) {
    263                 File path = installSupport(8); // GridLayout arrived in rev 7 and fixed in rev 8
    264                 if (path == null) {
    265                     return false;
    266                 }
    267                 assert path.equals(supportPath);
    268             }
    269             File libraryPath = new File(supportPath, FD_V7 + File.separator + FD_GRIDLAYOUT);
    270             if (!libraryPath.isDirectory()) {
    271                 // Upgrade support package: it's out of date. The SDK manager will
    272                 // perform an upgrade to the latest version if the package is already installed.
    273                 File path = installSupport(-1);
    274                 if (path == null) {
    275                     return false;
    276                 }
    277                 assert path.equals(libraryPath) : path;
    278             }
    279 
    280             // Create workspace copy of the project and add library dependency
    281             IProject libraryProject = createLibraryProject(libraryPath, project, waitForFinish);
    282             if (libraryProject != null) {
    283                 return addLibraryDependency(libraryProject, project, waitForFinish);
    284             }
    285         }
    286 
    287         return false;
    288     }
    289 
    290     /**
    291      * Returns the directory containing the support libraries (v4, v7, v13,
    292      * ...), which may or may not exist
    293      *
    294      * @return a path to the support library or null
    295      */
    296     private static File getSupportPackageDir() {
    297         final Sdk sdk = Sdk.getCurrent();
    298         if (sdk != null) {
    299             String sdkLocation = sdk.getSdkLocation();
    300             SdkManager manager = SdkManager.createManager(sdkLocation, NullLogger.getLogger());
    301             Map<String, Integer> versions = manager.getExtrasVersions();
    302             Integer version = versions.get(VENDOR_ID + '/' + SUPPORT_ID);
    303             if (version != null) {
    304                 File supportPath = new File(sdkLocation,
    305                         SdkConstants.FD_EXTRAS + File.separator
    306                         + VENDOR_ID + File.separator
    307                         + SUPPORT_ID);
    308                 return supportPath;
    309             }
    310 
    311             // Check the old compatibility library. When the library is updated in-place
    312             // the manager doesn't change its folder name (since that is a source of
    313             // endless issues on Windows.)
    314             version = versions.get(VENDOR_ID + '/' + COMPATIBILITY_ID);
    315             if (version != null) {
    316                 File supportPath = new File(sdkLocation,
    317                         SdkConstants.FD_EXTRAS + File.separator
    318                         + VENDOR_ID + File.separator
    319                         + COMPATIBILITY_ID);
    320                 return supportPath;
    321             }
    322         }
    323         return null;
    324     }
    325 
    326     /**
    327      * Returns a path to the installed jar file for the support library,
    328      * or null if it does not exist
    329      *
    330      * @return a path to the v4.jar or null
    331      */
    332     @Nullable
    333     public static File getSupportJarFile() {
    334         File supportDir = getSupportPackageDir();
    335         if (supportDir != null) {
    336             File path = new File(supportDir, FD_V4 + File.separator + ANDROID_SUPPORT_V4_JAR);
    337             if (path.exists()) {
    338                 return path;
    339             }
    340         }
    341 
    342         return null;
    343     }
    344 
    345     /**
    346      * Creates a library project in the Eclipse workspace out of the grid layout project
    347      * in the SDK tree.
    348      *
    349      * @param libraryPath the path to the directory tree containing the project contents
    350      * @param project the project to copy the SDK target out of
    351      * @param waitForFinish whether the operation should finish before this method returns
    352      * @return a library project, or null if it fails for some reason
    353      */
    354     private static IProject createLibraryProject(
    355             final File libraryPath,
    356             final IProject project,
    357             boolean waitForFinish) {
    358 
    359         // Install a new library into the workspace. This is a copy rather than
    360         // a reference to the support library version such that modifications
    361         // do not modify the pristine copy in the SDK install area.
    362 
    363         final IProject newProject;
    364         try {
    365             IProgressMonitor monitor = new NullProgressMonitor();
    366             IWorkspace workspace = ResourcesPlugin.getWorkspace();
    367             IWorkspaceRoot root = workspace.getRoot();
    368 
    369             String name = AdtUtils.getUniqueProjectName(
    370                     "gridlayout_v7", "_"); //$NON-NLS-1$ //$NON-NLS-2$
    371             newProject = root.getProject(name);
    372             IProjectDescription description = workspace.newProjectDescription(name);
    373             String[] natures = new String[] { AdtConstants.NATURE_DEFAULT, JavaCore.NATURE_ID };
    374             description.setNatureIds(natures);
    375             newProject.create(description, monitor);
    376 
    377             // Copy in the files recursively
    378             IFileSystem fileSystem = EFS.getLocalFileSystem();
    379             IFileStore sourceDir = fileSystem.getStore(libraryPath.toURI());
    380             IFileStore destDir = fileSystem.getStore(newProject.getLocationURI());
    381             sourceDir.copy(destDir, EFS.OVERWRITE, null);
    382 
    383             // Make sure the src folder exists
    384             destDir.getChild("src").mkdir(0, null /*monitor*/);
    385 
    386             // Set the android platform to the same level as the calling project
    387             ProjectState state = Sdk.getProjectState(project);
    388             String target = state.getProperties().getProperty(ProjectProperties.PROPERTY_TARGET);
    389             if (target != null && target.length() > 0) {
    390                 ProjectProperties properties = ProjectProperties.load(libraryPath.getPath(),
    391                         PropertyType.PROJECT);
    392                 ProjectPropertiesWorkingCopy copy = properties.makeWorkingCopy();
    393                 copy.setProperty(ProjectProperties.PROPERTY_TARGET, target);
    394                 try {
    395                     copy.save();
    396                 } catch (Exception e) {
    397                     AdtPlugin.log(e, null);
    398                 }
    399             }
    400 
    401             newProject.open(monitor);
    402 
    403             return newProject;
    404         } catch (CoreException e) {
    405             AdtPlugin.log(e, null);
    406             return null;
    407         }
    408     }
    409 
    410     /**
    411      * Adds a library dependency on the given library into the given project.
    412      *
    413      * @param libraryProject the library project to depend on
    414      * @param dependentProject the project to write the dependency into
    415      * @param waitForFinish whether this method should wait for the job to
    416      *            finish
    417      * @return true if the operation succeeded
    418      */
    419     public static boolean addLibraryDependency(
    420             final IProject libraryProject,
    421             final IProject dependentProject,
    422             boolean waitForFinish) {
    423 
    424         // Now add library dependency
    425 
    426         // Run an Eclipse asynchronous job to update the project
    427         Job job = new Job("Add Support Library Dependency to Project") {
    428             @Override
    429             protected IStatus run(IProgressMonitor monitor) {
    430                 try {
    431                     monitor.beginTask("Add library dependency to project build path", 3);
    432                     monitor.worked(1);
    433 
    434                     // TODO: Add library project to the project.properties file!
    435                     ProjectState state = Sdk.getProjectState(dependentProject);
    436                     ProjectPropertiesWorkingCopy mPropertiesWorkingCopy =
    437                             state.getProperties().makeWorkingCopy();
    438 
    439                     // Get the highest version number of the libraries; there cannot be any
    440                     // gaps so we will assign the next library the next number
    441                     int nextVersion = 1;
    442                     for (String property : mPropertiesWorkingCopy.keySet()) {
    443                         if (property.startsWith(ProjectProperties.PROPERTY_LIB_REF)) {
    444                             String s = property.substring(
    445                                     ProjectProperties.PROPERTY_LIB_REF.length());
    446                             int version = Integer.parseInt(s);
    447                             if (version >= nextVersion) {
    448                                 nextVersion = version + 1;
    449                             }
    450                         }
    451                     }
    452 
    453                     IPath relativePath = libraryProject.getLocation().makeRelativeTo(
    454                             dependentProject.getLocation());
    455 
    456                     mPropertiesWorkingCopy.setProperty(
    457                             ProjectProperties.PROPERTY_LIB_REF + nextVersion,
    458                             relativePath.toString());
    459                     try {
    460                         mPropertiesWorkingCopy.save();
    461                         IResource projectProp = dependentProject.findMember(
    462                                 SdkConstants.FN_PROJECT_PROPERTIES);
    463                         projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
    464                     } catch (Exception e) {
    465                         String msg = String.format(
    466                                 "Failed to save %1$s for project %2$s",
    467                                 SdkConstants.FN_PROJECT_PROPERTIES, dependentProject.getName());
    468                         AdtPlugin.log(e, msg);
    469                     }
    470 
    471                     // Project fix-ups
    472                     Job fix = FixProjectAction.createFixProjectJob(libraryProject);
    473                     fix.schedule();
    474                     fix.join();
    475 
    476                     monitor.worked(1);
    477 
    478                     return Status.OK_STATUS;
    479                 } catch (Exception e) {
    480                     return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
    481                                       "Failed", e); //$NON-NLS-1$
    482                 } finally {
    483                     if (monitor != null) {
    484                         monitor.done();
    485                     }
    486                 }
    487             }
    488         };
    489         job.schedule();
    490 
    491         if (waitForFinish) {
    492             try {
    493                 job.join();
    494                 return job.getState() == IStatus.OK;
    495             } catch (InterruptedException e) {
    496                 AdtPlugin.log(e, null);
    497             }
    498         }
    499 
    500         return true;
    501     }
    502 
    503     private static IResource copyJarIntoProject(
    504             IProject project,
    505             File jarPath) throws IOException, CoreException {
    506         IFolder resFolder = project.getFolder(SdkConstants.FD_NATIVE_LIBS);
    507         if (!resFolder.exists()) {
    508             resFolder.create(IResource.FORCE, true /*local*/, null);
    509         }
    510 
    511         IFile destFile = resFolder.getFile(jarPath.getName());
    512         IPath loc = destFile.getLocation();
    513         File destPath = loc.toFile();
    514 
    515         // Only modify the file if necessary so that we don't trigger unnecessary recompilations
    516         FileOp f = new FileOp();
    517         if (!f.isFile(destPath) || !f.isSameFile(jarPath, destPath)) {
    518             f.copyFile(jarPath, destPath);
    519             // Make sure Eclipse discovers java.io file changes
    520             resFolder.refreshLocal(1, new NullProgressMonitor());
    521         }
    522 
    523         return destFile;
    524     }
    525 
    526     /**
    527      * @see IWorkbenchWindowActionDelegate#init
    528      */
    529     public void init(IWorkbenchWindow window) {
    530         // pass
    531     }
    532 
    533 }
    534