• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
setActivePart(IAction action, IWorkbenchPart targetPart)80     public void setActivePart(IAction action, IWorkbenchPart targetPart) {
81     }
82 
83     @Override
run(IAction action)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
selectionChanged(IAction action, ISelection selection)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      */
install(final IProject project)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 
installSupport()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      */
installLibrary(final IProject project, boolean waitForFinish)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      */
createLibraryProject( final File libraryPath, final IProject project, boolean waitForFinish)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      */
addLibraryDependency( final IProject libraryProject, final IProject dependentProject, boolean waitForFinish)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 
copyJarIntoProject( IProject project, File jarPath)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      */
init(IWorkbenchWindow window)407     public void init(IWorkbenchWindow window) {
408         // pass
409     }
410 
411 }
412