• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.sdk;
18 
19 import com.android.ddmlib.IDevice;
20 import com.android.ide.eclipse.adt.AdtPlugin;
21 import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer;
22 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor;
23 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IProjectListener;
24 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;
25 import com.android.prefs.AndroidLocation.AndroidLocationException;
26 import com.android.sdklib.AndroidVersion;
27 import com.android.sdklib.IAndroidTarget;
28 import com.android.sdklib.ISdkLog;
29 import com.android.sdklib.SdkConstants;
30 import com.android.sdklib.SdkManager;
31 import com.android.sdklib.internal.avd.AvdManager;
32 import com.android.sdklib.internal.project.ApkConfigurationHelper;
33 import com.android.sdklib.internal.project.ApkSettings;
34 import com.android.sdklib.internal.project.ProjectProperties;
35 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
36 
37 import org.eclipse.core.resources.IProject;
38 import org.eclipse.core.resources.IncrementalProjectBuilder;
39 import org.eclipse.core.runtime.CoreException;
40 import org.eclipse.core.runtime.IPath;
41 import org.eclipse.core.runtime.IStatus;
42 import org.eclipse.jdt.core.IJavaProject;
43 import org.eclipse.jdt.core.JavaCore;
44 
45 import java.io.File;
46 import java.io.IOException;
47 import java.net.MalformedURLException;
48 import java.net.URL;
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.Map;
52 
53 /**
54  * Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used
55  * at the same time.
56  *
57  * To start using an SDK, call {@link #loadSdk(String)} which returns the instance of
58  * the Sdk object.
59  *
60  * To get the list of platforms or add-ons present in the SDK, call {@link #getTargets()}.
61  */
62 public class Sdk implements IProjectListener {
63     private static Sdk sCurrentSdk = null;
64 
65     private final SdkManager mManager;
66     private final AvdManager mAvdManager;
67 
68     private final HashMap<IProject, IAndroidTarget> mProjectTargetMap =
69             new HashMap<IProject, IAndroidTarget>();
70     private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap =
71             new HashMap<IAndroidTarget, AndroidTargetData>();
72     private final HashMap<IProject, ApkSettings> mApkSettingsMap =
73             new HashMap<IProject, ApkSettings>();
74     private final String mDocBaseUrl;
75 
76     private final LayoutDeviceManager mLayoutDeviceManager = new LayoutDeviceManager();
77 
78     /**
79      * Classes implementing this interface will receive notification when targets are changed.
80      */
81     public interface ITargetChangeListener {
82         /**
83          * Sent when project has its target changed.
84          */
onProjectTargetChange(IProject changedProject)85         void onProjectTargetChange(IProject changedProject);
86 
87         /**
88          * Called when the targets are loaded (either the SDK finished loading when Eclipse starts,
89          * or the SDK is changed).
90          */
onTargetsLoaded()91         void onTargetsLoaded();
92     }
93 
94     /**
95      * Loads an SDK and returns an {@link Sdk} object if success.
96      * @param sdkLocation the OS path to the SDK.
97      */
loadSdk(String sdkLocation)98     public static Sdk loadSdk(String sdkLocation) {
99         if (sCurrentSdk != null) {
100             sCurrentSdk.dispose();
101             sCurrentSdk = null;
102         }
103 
104         final ArrayList<String> logMessages = new ArrayList<String>();
105         ISdkLog log = new ISdkLog() {
106             public void error(Throwable throwable, String errorFormat, Object... arg) {
107                 if (errorFormat != null) {
108                     logMessages.add(String.format("Error: " + errorFormat, arg));
109                 }
110 
111                 if (throwable != null) {
112                     logMessages.add(throwable.getMessage());
113                 }
114             }
115 
116             public void warning(String warningFormat, Object... arg) {
117                 logMessages.add(String.format("Warning: " + warningFormat, arg));
118             }
119 
120             public void printf(String msgFormat, Object... arg) {
121                 logMessages.add(String.format(msgFormat, arg));
122             }
123         };
124 
125         // get an SdkManager object for the location
126         SdkManager manager = SdkManager.createManager(sdkLocation, log);
127         if (manager != null) {
128             AvdManager avdManager = null;
129             try {
130                 avdManager = new AvdManager(manager, log);
131             } catch (AndroidLocationException e) {
132                 log.error(e, "Error parsing the AVDs");
133             }
134             sCurrentSdk = new Sdk(manager, avdManager);
135             return sCurrentSdk;
136         } else {
137             StringBuilder sb = new StringBuilder("Error Loading the SDK:\n");
138             for (String msg : logMessages) {
139                 sb.append('\n');
140                 sb.append(msg);
141             }
142             AdtPlugin.displayError("Android SDK", sb.toString());
143         }
144         return null;
145     }
146 
147     /**
148      * Returns the current {@link Sdk} object.
149      */
getCurrent()150     public static Sdk getCurrent() {
151         return sCurrentSdk;
152     }
153 
154     /**
155      * Returns the location (OS path) of the current SDK.
156      */
getSdkLocation()157     public String getSdkLocation() {
158         return mManager.getLocation();
159     }
160 
161     /**
162      * Returns the URL to the local documentation.
163      * Can return null if no documentation is found in the current SDK.
164      *
165      * @return A file:// URL on the local documentation folder if it exists or null.
166      */
getDocumentationBaseUrl()167     public String getDocumentationBaseUrl() {
168         return mDocBaseUrl;
169     }
170 
171     /**
172      * Returns the list of targets that are available in the SDK.
173      */
getTargets()174     public IAndroidTarget[] getTargets() {
175         return mManager.getTargets();
176     }
177 
178     /**
179      * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
180      *
181      * @param hash the {@link IAndroidTarget} hash string.
182      * @return The matching {@link IAndroidTarget} or null.
183      */
getTargetFromHashString(String hash)184     public IAndroidTarget getTargetFromHashString(String hash) {
185         return mManager.getTargetFromHashString(hash);
186     }
187 
188     /**
189      * Sets a new target and a new list of Apk configuration for a given project.
190      *
191      * @param project the project to receive the new apk configurations
192      * @param target The new target to set, or <code>null</code> to not change the current target.
193      * @param apkConfigMap a map of apk configurations. The map contains (name, filter) where name
194      * is the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
195      * resource configuration to include in the apk (see aapt -c). Can be <code>null</code> if the
196      * apk configurations should not be updated.
197      */
setProject(IProject project, IAndroidTarget target, ApkSettings settings)198     public void setProject(IProject project, IAndroidTarget target,
199             ApkSettings settings) {
200         synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
201             boolean resolveProject = false;
202 
203             ProjectProperties properties = ProjectProperties.load(
204                     project.getLocation().toOSString(), PropertyType.DEFAULT);
205             if (properties == null) {
206                 // doesn't exist yet? we create it.
207                 properties = ProjectProperties.create(project.getLocation().toOSString(),
208                         PropertyType.DEFAULT);
209             }
210 
211             if (target != null) {
212                 // look for the current target of the project
213                 IAndroidTarget previousTarget = mProjectTargetMap.get(project);
214 
215                 if (target != previousTarget) {
216                     // save the target hash string in the project persistent property
217                     properties.setAndroidTarget(target);
218 
219                     // put it in a local map for easy access.
220                     mProjectTargetMap.put(project, target);
221 
222                     resolveProject = true;
223                 }
224             }
225 
226             // if there's no settings, force default values (to reset possibly changed
227             // values in a previous call.
228             if (settings == null) {
229                 settings = new ApkSettings();
230             }
231 
232             // save the project settings into the project persistent property
233             ApkConfigurationHelper.setProperties(properties, settings);
234 
235             // put it in a local map for easy access.
236             mApkSettingsMap.put(project, settings);
237 
238             // we are done with the modification. Save the property file.
239             try {
240                 properties.save();
241             } catch (IOException e) {
242                 AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
243                         project.getName());
244             }
245 
246             if (resolveProject) {
247                 // force a resolve of the project by updating the classpath container.
248                 // This will also force a recompile.
249                 IJavaProject javaProject = JavaCore.create(project);
250                 AndroidClasspathContainerInitializer.updateProjects(
251                         new IJavaProject[] { javaProject });
252             } else {
253                 // always do a full clean/build.
254                 try {
255                     project.build(IncrementalProjectBuilder.CLEAN_BUILD, null);
256                 } catch (CoreException e) {
257                     // failed to build? force resolve instead.
258                     IJavaProject javaProject = JavaCore.create(project);
259                     AndroidClasspathContainerInitializer.updateProjects(
260                             new IJavaProject[] { javaProject });
261                 }
262             }
263 
264             // finally, update the opened editors.
265             if (resolveProject) {
266                 AdtPlugin.getDefault().updateTargetListener(project);
267             }
268         }
269     }
270 
271     /**
272      * Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
273      */
getTarget(IProject project)274     public IAndroidTarget getTarget(IProject project) {
275         synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
276             IAndroidTarget target = mProjectTargetMap.get(project);
277             if (target == null) {
278                 // get the value from the project persistent property.
279                 String targetHashString = loadProjectProperties(project, this);
280 
281                 if (targetHashString != null) {
282                     target = mManager.getTargetFromHashString(targetHashString);
283                 }
284             }
285 
286             return target;
287         }
288     }
289 
290 
291     /**
292      * Parses the project properties and returns the hash string uniquely identifying the
293      * target of the given project.
294      * <p/>
295      * This methods reads the content of the <code>default.properties</code> file present in
296      * the root folder of the project.
297      * <p/>The returned string is equivalent to the return of {@link IAndroidTarget#hashString()}.
298      * @param project The project for which to return the target hash string.
299      * @param sdkStorage The sdk in which to store the Apk Configs. Can be null.
300      * @return the hash string or null if the project does not have a target set.
301      */
loadProjectProperties(IProject project, Sdk sdkStorage)302     private static String loadProjectProperties(IProject project, Sdk sdkStorage) {
303         // load the default.properties from the project folder.
304         IPath location = project.getLocation();
305         if (location == null) {  // can return null when the project is being deleted.
306             // do nothing and return null;
307             return null;
308         }
309         ProjectProperties properties = ProjectProperties.load(location.toOSString(),
310                 PropertyType.DEFAULT);
311         if (properties == null) {
312             AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'",
313                     project.getName());
314             return null;
315         }
316 
317         if (sdkStorage != null) {
318             synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
319                 ApkSettings settings = ApkConfigurationHelper.getSettings(properties);
320 
321                 if (settings != null) {
322                     sdkStorage.mApkSettingsMap.put(project, settings);
323                 }
324             }
325         }
326 
327         return properties.getProperty(ProjectProperties.PROPERTY_TARGET);
328     }
329 
330     /**
331      * Returns the hash string uniquely identifying the target of a project.
332      * <p/>
333      * This methods reads the content of the <code>default.properties</code> file present in
334      * the root folder of the project.
335      * <p/>The string is equivalent to the return of {@link IAndroidTarget#hashString()}.
336      * @param project The project for which to return the target hash string.
337      * @return the hash string or null if the project does not have a target set.
338      */
getProjectTargetHashString(IProject project)339     public static String getProjectTargetHashString(IProject project) {
340         return loadProjectProperties(project, null /*storeConfigs*/);
341     }
342 
343     /**
344      * Sets a target hash string in given project's <code>default.properties</code> file.
345      * @param project The project in which to save the hash string.
346      * @param targetHashString The target hash string to save. This must be the result from
347      * {@link IAndroidTarget#hashString()}.
348      */
setProjectTargetHashString(IProject project, String targetHashString)349     public static void setProjectTargetHashString(IProject project, String targetHashString) {
350         // because we don't want to erase other properties from default.properties, we first load
351         // them
352         ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString(),
353                 PropertyType.DEFAULT);
354         if (properties == null) {
355             // doesn't exist yet? we create it.
356             properties = ProjectProperties.create(project.getLocation().toOSString(),
357                     PropertyType.DEFAULT);
358         }
359 
360         // add/change the target hash string.
361         properties.setProperty(ProjectProperties.PROPERTY_TARGET, targetHashString);
362 
363         // and rewrite the file.
364         try {
365             properties.save();
366         } catch (IOException e) {
367             AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
368                     project.getName());
369         }
370     }
371     /**
372      * Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}.
373      */
getTargetData(IAndroidTarget target)374     public AndroidTargetData getTargetData(IAndroidTarget target) {
375         synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
376             return mTargetDataMap.get(target);
377         }
378     }
379 
380     /**
381      * Return the {@link AndroidTargetData} for a given {@link IProject}.
382      */
getTargetData(IProject project)383     public AndroidTargetData getTargetData(IProject project) {
384         synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
385             IAndroidTarget target = getTarget(project);
386             if (target != null) {
387                 return getTargetData(target);
388             }
389         }
390 
391         return null;
392     }
393 
394     /**
395      * Returns the APK settings for a given project.
396      */
getApkSettings(IProject project)397     public ApkSettings getApkSettings(IProject project) {
398         synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
399             return mApkSettingsMap.get(project);
400         }
401     }
402 
403     /**
404      * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
405      * be <code>null</code>.
406      */
getAvdManager()407     public AvdManager getAvdManager() {
408         return mAvdManager;
409     }
410 
getDeviceVersion(IDevice device)411     public static AndroidVersion getDeviceVersion(IDevice device) {
412         try {
413             Map<String, String> props = device.getProperties();
414             String apiLevel = props.get(IDevice.PROP_BUILD_API_LEVEL);
415             if (apiLevel == null) {
416                 return null;
417             }
418 
419             return new AndroidVersion(Integer.parseInt(apiLevel),
420                     props.get((IDevice.PROP_BUILD_CODENAME)));
421         } catch (NumberFormatException e) {
422             return null;
423         }
424     }
425 
getLayoutDeviceManager()426     public LayoutDeviceManager getLayoutDeviceManager() {
427         return mLayoutDeviceManager;
428     }
429 
Sdk(SdkManager manager, AvdManager avdManager)430     private Sdk(SdkManager manager, AvdManager avdManager) {
431         mManager = manager;
432         mAvdManager = avdManager;
433 
434         // listen to projects closing
435         ResourceMonitor monitor = ResourceMonitor.getMonitor();
436         monitor.addProjectListener(this);
437 
438         // pre-compute some paths
439         mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
440                 SdkConstants.OS_SDK_DOCS_FOLDER);
441 
442         // load the built-in and user layout devices
443         mLayoutDeviceManager.loadDefaultAndUserDevices(mManager.getLocation());
444         // and the ones from the add-on
445         loadLayoutDevices();
446     }
447 
448     /**
449      *  Cleans and unloads the SDK.
450      */
dispose()451     private void dispose() {
452         ResourceMonitor.getMonitor().removeProjectListener(this);
453     }
454 
setTargetData(IAndroidTarget target, AndroidTargetData data)455     void setTargetData(IAndroidTarget target, AndroidTargetData data) {
456         synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
457             mTargetDataMap.put(target, data);
458         }
459     }
460 
461     /**
462      * Returns the URL to the local documentation.
463      * Can return null if no documentation is found in the current SDK.
464      *
465      * @param osDocsPath Path to the documentation folder in the current SDK.
466      *  The folder may not actually exist.
467      * @return A file:// URL on the local documentation folder if it exists or null.
468      */
getDocumentationBaseUrl(String osDocsPath)469     private String getDocumentationBaseUrl(String osDocsPath) {
470         File f = new File(osDocsPath);
471 
472         if (f.isDirectory()) {
473             try {
474                 // Note: to create a file:// URL, one would typically use something like
475                 // f.toURI().toURL().toString(). However this generates a broken path on
476                 // Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of
477                 // "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll
478                 // do the correct thing manually.
479 
480                 String path = f.getAbsolutePath();
481                 if (File.separatorChar != '/') {
482                     path = path.replace(File.separatorChar, '/');
483                 }
484 
485                 // For some reason the URL class doesn't add the mandatory "//" after
486                 // the "file:" protocol name, so it has to be hacked into the path.
487                 URL url = new URL("file", null, "//" + path);  //$NON-NLS-1$ //$NON-NLS-2$
488                 String result = url.toString();
489                 return result;
490             } catch (MalformedURLException e) {
491                 // ignore malformed URLs
492             }
493         }
494 
495         return null;
496     }
497 
498     /**
499      * Parses the SDK add-ons to look for files called {@link SdkConstants#FN_DEVICES_XML} to
500      * load {@link LayoutDevice} from them.
501      */
loadLayoutDevices()502     private void loadLayoutDevices() {
503         IAndroidTarget[] targets = mManager.getTargets();
504         for (IAndroidTarget target : targets) {
505             if (target.isPlatform() == false) {
506                 File deviceXml = new File(target.getLocation(), SdkConstants.FN_DEVICES_XML);
507                 if (deviceXml.isFile()) {
508                     mLayoutDeviceManager.parseAddOnLayoutDevice(deviceXml);
509                 }
510             }
511         }
512 
513         mLayoutDeviceManager.sealAddonLayoutDevices();
514     }
515 
projectClosed(IProject project)516     public void projectClosed(IProject project) {
517         // get the target project
518         synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
519             IAndroidTarget target = mProjectTargetMap.get(project);
520             if (target != null) {
521                 // get the bridge for the target, and clear the cache for this project.
522                 AndroidTargetData data = mTargetDataMap.get(target);
523                 if (data != null) {
524                     LayoutBridge bridge = data.getLayoutBridge();
525                     if (bridge != null && bridge.status == LoadStatus.LOADED) {
526                         bridge.bridge.clearCaches(project);
527                     }
528                 }
529             }
530 
531             // now remove the project for the maps.
532             mProjectTargetMap.remove(project);
533             mApkSettingsMap.remove(project);
534         }
535     }
536 
projectDeleted(IProject project)537     public void projectDeleted(IProject project) {
538         projectClosed(project);
539     }
540 
projectOpened(IProject project)541     public void projectOpened(IProject project) {
542         // ignore this. The project will be added to the map the first time the target needs
543         // to be resolved.
544     }
545 
projectOpenedWithWorkspace(IProject project)546     public void projectOpenedWithWorkspace(IProject project) {
547         // ignore this. The project will be added to the map the first time the target needs
548         // to be resolved.
549     }
550 }
551 
552