diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-02-13 12:57:48 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-02-13 12:57:48 -0800 |
commit | b58a893bf9ba96db9a544cd33af4828826b73061 (patch) | |
tree | fad6c5dfcfd5be12a702d9117d260db7bdc05254 /eclipse | |
parent | 6990abcbc03c25aeff94da27ed6893e7993d2709 (diff) | |
download | sdk-b58a893bf9ba96db9a544cd33af4828826b73061.zip sdk-b58a893bf9ba96db9a544cd33af4828826b73061.tar.gz sdk-b58a893bf9ba96db9a544cd33af4828826b73061.tar.bz2 |
auto import from //branches/cupcake/...@131421
Diffstat (limited to 'eclipse')
10 files changed, 833 insertions, 379 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 62bc7ed..ddc93ac 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -166,8 +166,12 @@ public class AdtPlugin extends AbstractUIPlugin { /** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */ private LoadStatus mSdkIsLoaded = LoadStatus.LOADING; /** Project to update once the SDK is loaded. - * Any access MUST be in a synchronized(mPostLoadProjects) block */ - private final ArrayList<IJavaProject> mPostLoadProjects = new ArrayList<IJavaProject>(); + * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */ + private final ArrayList<IJavaProject> mPostLoadProjectsToResolve = + new ArrayList<IJavaProject>(); + /** Project to check validity of cache vs actual once the SDK is loaded. + * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */ + private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>(); private ResourceMonitor mResourceMonitor; private ArrayList<Runnable> mResourceRefreshListener = new ArrayList<Runnable>(); @@ -306,12 +310,12 @@ public class AdtPlugin extends AbstractUIPlugin { if (checkSdkLocationAndId()) { // if sdk if valid, reparse it - // add the current Android project to the list of projects to be updated + // add all the opened Android projects to the list of projects to be updated // after the SDK is reloaded - synchronized (mPostLoadProjects) { + synchronized (getSdkLockObject()) { // get the project to refresh. IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(); - mPostLoadProjects.addAll(Arrays.asList(androidProjects)); + mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects)); } // parse the SDK resources at the new location @@ -869,19 +873,44 @@ public class AdtPlugin extends AbstractUIPlugin { } /** - * Returns whether the Sdk has been loaded. If the SDK has not been loaded, the given - * <var>project</var> is added to a list of projects to recompile after the SDK is loaded. + * Returns whether the Sdk has been loaded. */ - public LoadStatus getSdkLoadStatus(IJavaProject project) { - synchronized (mPostLoadProjects) { - // only add the project to the list, if we are still loading. - if (mSdkIsLoaded == LoadStatus.LOADING && project != null) { - mPostLoadProjects.add(project); - } - + public final LoadStatus getSdkLoadStatus() { + synchronized (getSdkLockObject()) { return mSdkIsLoaded; } } + + /** + * Returns the lock object for SDK loading. If you wish to do things while the SDK is loading, + * you must synchronize on this object. + * @return + */ + public final Object getSdkLockObject() { + return mPostLoadProjectsToResolve; + } + + /** + * Sets the given {@link IJavaProject} to have its target resolved again once the SDK finishes + * to load. + */ + public final void setProjectToResolve(IJavaProject javaProject) { + synchronized (getSdkLockObject()) { + mPostLoadProjectsToResolve.add(javaProject); + } + } + + /** + * Sets the given {@link IJavaProject} to have its target checked for consistency + * once the SDK finishes to load. This is used if the target is resolved using cached + * information while the SDK is loading. + */ + public final void setProjectToCheck(IJavaProject javaProject) { + // only lock on + synchronized (getSdkLockObject()) { + mPostLoadProjectsToCheck.add(javaProject); + } + } /** * Checks the location of the SDK is valid and if it is, grab the SDK API version @@ -1018,9 +1047,9 @@ public class AdtPlugin extends AbstractUIPlugin { for (IAndroidTarget target : sdk.getTargets()) { IStatus status = new AndroidTargetParser(target).run(progress); if (status.getCode() != IStatus.OK) { - synchronized (mPostLoadProjects) { + synchronized (getSdkLockObject()) { mSdkIsLoaded = LoadStatus.FAILED; - mPostLoadProjects.clear(); + mPostLoadProjectsToResolve.clear(); } return status; } @@ -1034,22 +1063,30 @@ public class AdtPlugin extends AbstractUIPlugin { IStatus res = DexWrapper.loadDex( mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_DX_JAR); if (res != Status.OK_STATUS) { - synchronized (mPostLoadProjects) { + synchronized (getSdkLockObject()) { mSdkIsLoaded = LoadStatus.FAILED; - mPostLoadProjects.clear(); + mPostLoadProjectsToResolve.clear(); } return res; } - synchronized (mPostLoadProjects) { + synchronized (getSdkLockObject()) { mSdkIsLoaded = LoadStatus.LOADED; + // check the projects that need checking. + // The method modifies the list (it removes the project that + // do not need to be resolved again). + AndroidClasspathContainerInitializer.checkProjectsCache( + mPostLoadProjectsToCheck); + + mPostLoadProjectsToResolve.addAll(mPostLoadProjectsToCheck); + // update the project that needs recompiling. - if (mPostLoadProjects.size() > 0) { - IJavaProject[] array = mPostLoadProjects.toArray( - new IJavaProject[mPostLoadProjects.size()]); + if (mPostLoadProjectsToResolve.size() > 0) { + IJavaProject[] array = mPostLoadProjectsToResolve.toArray( + new IJavaProject[mPostLoadProjectsToResolve.size()]); AndroidClasspathContainerInitializer.updateProjects(array); - mPostLoadProjects.clear(); + mPostLoadProjectsToResolve.clear(); } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java index 82bcea8..43971b0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java @@ -299,7 +299,7 @@ public class ApkBuilder extends BaseBuilder { // At this point, we can abort the build if we have to, as we have computed // our resource delta and stored the result. - abortOnBadSetup(javaProject); + abortOnBadSetup(project); if (dv != null && dv.mXmlError) { AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java index 04e9fbf..6b0810a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java @@ -854,15 +854,15 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { * @param javaProject The {@link IJavaProject} being compiled. * @throws CoreException */ - protected final void abortOnBadSetup(IJavaProject javaProject) throws CoreException { + protected final void abortOnBadSetup(IProject project) throws CoreException { // check if we have finished loading the SDK. - if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) { + if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADED) { // we exit silently stopBuild("SDK is not loaded yet"); } // check the compiler compliance level. - if (ProjectHelper.checkCompilerCompliance(getProject()) != + if (ProjectHelper.checkCompilerCompliance(project) != ProjectHelper.COMPILER_COMPLIANCE_OK) { // we exit silently stopBuild(Messages.Compiler_Compliance_Error); @@ -875,7 +875,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { stopBuild(Messages.No_SDK_Setup_Error); } - IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(javaProject.getProject()); + IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); if (projectTarget == null) { // no target. error has been output by the container initializer: // exit silently. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java index fd4d772..2c15d55 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java @@ -285,7 +285,7 @@ public class PreCompilerBuilder extends BaseBuilder { // At this point we have stored what needs to be build, so we can // do some high level test and abort if needed. - abortOnBadSetup(javaProject); + abortOnBadSetup(project); // if there was some XML errors, we just return w/o doing // anything since we've put some markers in the files anyway. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java index 1e7b77a..19d7185 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java @@ -31,8 +31,6 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; import java.util.Map; @@ -95,8 +93,7 @@ public class ResourceManagerBuilder extends BaseBuilder { } // check if we have finished loading the SDK. - IJavaProject javaProject = JavaCore.create(project); - if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) { + if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADED) { // we exit silently // This interrupts the build. The next builders will not run. stopBuild("SDK is not loaded yet"); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java index 87f902a..d4952b1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java @@ -54,6 +54,7 @@ import org.eclipse.debug.ui.DebugUITools; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jdt.launching.IVMConnector; import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; @@ -133,7 +134,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener boolean mCancelled = false; /** Basic constructor with activity and package info. */ - public DelayedLaunchInfo(IProject project, String packageName, String activity, + private DelayedLaunchInfo(IProject project, String packageName, String activity, IFile pack, Boolean debuggable, int requiredApiVersionNumber, int launchAction, AndroidLaunch launch, IProgressMonitor monitor) { mProject = project; @@ -195,7 +196,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } /** - * Represents a launch configuration. + * Launch configuration data. This stores the result of querying the + * {@link ILaunchConfiguration} so that it's only done once. */ static final class AndroidLaunchConfiguration { @@ -680,9 +682,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener for (Device d : devices) { String deviceAvd = d.getAvdName(); if (deviceAvd != null && deviceAvd.equals(config.mAvdName)) { - response.mustContinue = true; - response.mustLaunchEmulator = false; - response.deviceToUse = d; + response.setDeviceToUse(d); AdtPlugin.printToConsole(project, String.format( "Automatic Target Mode: Preferred AVD '%1$s' is available on emulator '%2$s'", @@ -695,9 +695,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // at this point we have a valid preferred AVD that is not running. // We need to start it. - response.mustContinue = true; - response.mustLaunchEmulator = true; - response.avdToLaunch = preferredAvd; + response.setAvdToLaunch(preferredAvd); AdtPlugin.printToConsole(project, String.format( "Automatic Target Mode: Preferred AVD '%1$s' is not available. Launching new emulator.", @@ -760,9 +758,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } if (defaultAvd != null) { - response.mustContinue = true; - response.mustLaunchEmulator = true; - response.avdToLaunch = defaultAvd; + response.setAvdToLaunch(defaultAvd); AdtPlugin.printToConsole(project, String.format( "Automatic Target Mode: launching new emulator with compatible AVD '%1$s'", @@ -781,18 +777,16 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } } else if (hasDevice == false && compatibleRunningAvds.size() == 1) { Entry<Device, AvdInfo> e = compatibleRunningAvds.entrySet().iterator().next(); - response.mustContinue = true; - response.mustLaunchEmulator = false; - response.deviceToUse = e.getKey(); + response.setDeviceToUse(e.getKey()); // get the AvdInfo, if null, the device is a physical device. AvdInfo avdInfo = e.getValue(); if (avdInfo != null) { message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible AVD '%2$s'", - response.deviceToUse, e.getValue().getName()); + response.getDeviceToUse(), e.getValue().getName()); } else { message = String.format("Automatic Target Mode: using device '%1$s'", - response.deviceToUse); + response.getDeviceToUse()); } AdtPlugin.printToConsole(project, message); @@ -813,13 +807,35 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // bring up the device chooser. AdtPlugin.getDisplay().asyncExec(new Runnable() { public void run() { - DeviceChooserDialog dialog = new DeviceChooserDialog( - AdtPlugin.getDisplay().getActiveShell()); - dialog.open(response, project, projectTarget, launch, launchInfo, config); + try { + // open the chooser dialog. It'll fill 'response' with the device to use + // or the AVD to launch. + DeviceChooserDialog dialog = new DeviceChooserDialog( + AdtPlugin.getDisplay().getActiveShell(), + response, launchInfo.mPackageName, projectTarget); + if (dialog.open() == Dialog.OK) { + AndroidLaunchController.this.continueLaunch(response, project, launch, + launchInfo, config); + } else { + AdtPlugin.printErrorToConsole(project, "Launch canceled!"); + launch.stopLaunch(); + return; + } + } catch (Exception e) { + // there seems to be some case where the shell will be null. (might be + // an OS X bug). Because of this the creation of the dialog will throw + // and IllegalArg exception interrupting the launch with no user feedback. + // So we trap all the exception and display something. + String msg = e.getMessage(); + if (msg == null) { + msg = e.getClass().getCanonicalName(); + } + AdtPlugin.printErrorToConsole(project, + String.format("Error during launch: %s", msg)); + launch.stopLaunch(); + } } }); - - return; } /** @@ -833,23 +849,21 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener void continueLaunch(final DeviceChooserResponse response, final IProject project, final AndroidLaunch launch, final DelayedLaunchInfo launchInfo, final AndroidLaunchConfiguration config) { - if (response.mustContinue == false) { - AdtPlugin.printErrorToConsole(project, "Launch canceled!"); - launch.stopLaunch(); - return; - } - // Since this is called from the DeviceChooserDialog open, we are in the UI - // thread. So we spawn a temporary new one to finish the launch. + // Since this is called from the UI thread we spawn a new thread + // to finish the launch. new Thread() { @Override public void run() { - if (response.mustLaunchEmulator) { + if (response.getAvdToLaunch() != null) { // there was no selected device, we start a new emulator. synchronized (sListLock) { + AvdInfo info = response.getAvdToLaunch(); mWaitingForEmulatorLaunches.add(launchInfo); - AdtPlugin.printToConsole(project, "Launching a new emulator."); - boolean status = launchEmulator(config, response.avdToLaunch); + AdtPlugin.printToConsole(project, String.format( + "Launching a new emulator with Virtual Device '%1$s'", + info.getName())); + boolean status = launchEmulator(config, info); if (status == false) { // launching the emulator failed! @@ -865,9 +879,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener return; } - } else if (response.deviceToUse != null) { - launchInfo.mDevice = response.deviceToUse; - simpleLaunch(launchInfo, response.deviceToUse); + } else if (response.getDeviceToUse() != null) { + launchInfo.mDevice = response.getDeviceToUse(); + simpleLaunch(launchInfo, launchInfo.mDevice); } } }.start(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java index 05bc171..d446e2b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java @@ -26,14 +26,15 @@ import com.android.ddmuilib.IImageLoader; import com.android.ddmuilib.ImageHelper; import com.android.ddmuilib.TableHelper; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.AndroidLaunchConfiguration; -import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.DelayedLaunchInfo; import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.ddms.DdmsPlugin; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.avd.AvdManager; import com.android.sdklib.avd.AvdManager.AvdInfo; +import com.android.sdkuilib.AvdSelector; -import org.eclipse.core.resources.IProject; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; @@ -50,23 +51,19 @@ import org.eclipse.swt.SWTException; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Dialog; +import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; +import java.util.ArrayList; + public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener { - private final static int DLG_WIDTH = 500; - private final static int DLG_HEIGHT = 300; private final static int ICON_WIDTH = 16; private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$ @@ -77,6 +74,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener private Table mDeviceTable; private TableViewer mViewer; + private AvdSelector mPreferredAvdSelector; private Image mDeviceImage; private Image mEmulatorImage; @@ -84,13 +82,16 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener private Image mNoMatchImage; private Image mWarningImage; - private Button mOkButton; - private Button mCreateButton; - - private DeviceChooserResponse mResponse; - private DelayedLaunchInfo mLaunchInfo; - private IAndroidTarget mProjectTarget; - private Sdk mSdk; + private final DeviceChooserResponse mResponse; + private final String mPackageName; + private final IAndroidTarget mProjectTarget; + private final Sdk mSdk; + + private final AvdInfo[] mFullAvdList; + + private Button mDeviceRadioButton; + + private boolean mDisableAvdSelectionChange = false; /** * Basic Content Provider for a table full of {@link Device} objects. The input is @@ -135,14 +136,18 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener try { String apiValue = device.getProperty( IDevice.PROP_BUILD_VERSION_NUMBER); - int api = Integer.parseInt(apiValue); - if (api >= mProjectTarget.getApiVersionNumber()) { - // if the project is compiling against an add-on, the optional - // API may be missing from the device. - return mProjectTarget.isPlatform() ? - mMatchImage : mWarningImage; + if (apiValue != null) { + int api = Integer.parseInt(apiValue); + if (api >= mProjectTarget.getApiVersionNumber()) { + // if the project is compiling against an add-on, the optional + // API may be missing from the device. + return mProjectTarget.isPlatform() ? + mMatchImage : mWarningImage; + } else { + return mNoMatchImage; + } } else { - return mNoMatchImage; + return mWarningImage; } } catch (NumberFormatException e) { // lets consider the device non compatible @@ -183,7 +188,11 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener } return info.getTarget().getFullName(); } else { - return device.getProperty(IDevice.PROP_BUILD_VERSION); + String deviceBuild = device.getProperty(IDevice.PROP_BUILD_VERSION); + if (deviceBuild == null) { + return "unknown"; + } + return deviceBuild; } case 3: String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE); @@ -219,62 +228,48 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener } public static class DeviceChooserResponse { - public boolean mustContinue; - public boolean mustLaunchEmulator; - public AvdInfo avdToLaunch; - public Device deviceToUse; + private AvdInfo mAvdToLaunch; + private Device mDeviceToUse; + + public void setDeviceToUse(Device d) { + mDeviceToUse = d; + mAvdToLaunch = null; + } + + public void setAvdToLaunch(AvdInfo avd) { + mAvdToLaunch = avd; + mDeviceToUse = null; + } + + public Device getDeviceToUse() { + return mDeviceToUse; + } + + public AvdInfo getAvdToLaunch() { + return mAvdToLaunch; + } } - public DeviceChooserDialog(Shell parent) { - super(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); - } - - /** - * Prepare and display the dialog. - * @param response - * @param project - * @param projectTarget - * @param launch - * @param launchInfo - * @param config - */ - public void open(DeviceChooserResponse response, IProject project, - IAndroidTarget projectTarget, AndroidLaunch launch, DelayedLaunchInfo launchInfo, - AndroidLaunchConfiguration config) { + public DeviceChooserDialog(Shell parent, DeviceChooserResponse response, String packageName, + IAndroidTarget projectTarget) { + super(parent); mResponse = response; + mPackageName = packageName; mProjectTarget = projectTarget; - mLaunchInfo = launchInfo; mSdk = Sdk.getCurrent(); - - Shell parent = getParent(); - Shell shell = new Shell(parent, getStyle()); - shell.setText("Device Chooser"); + + // get the full list of Android Virtual Devices + AvdManager avdManager = mSdk.getAvdManager(); + if (avdManager != null) { + mFullAvdList = avdManager.getAvds(); + } else { + mFullAvdList = null; + } loadImages(); - createContents(shell); - - // Set the dialog size. - shell.setMinimumSize(DLG_WIDTH, DLG_HEIGHT); - Rectangle r = parent.getBounds(); - // get the center new top left. - int cx = r.x + r.width/2; - int x = cx - DLG_WIDTH / 2; - int cy = r.y + r.height/2; - int y = cy - DLG_HEIGHT / 2; - shell.setBounds(x, y, DLG_WIDTH, DLG_HEIGHT); - - shell.pack(); - shell.open(); - - // start the listening. - AndroidDebugBridge.addDeviceChangeListener(this); + } - Display display = parent.getDisplay(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - + private void cleanup() { // done listening. AndroidDebugBridge.removeDeviceChangeListener(this); @@ -283,30 +278,73 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener mMatchImage.dispose(); mNoMatchImage.dispose(); mWarningImage.dispose(); + } + + @Override + protected void okPressed() { + cleanup(); + super.okPressed(); + } + + @Override + protected void cancelPressed() { + cleanup(); + super.cancelPressed(); + } + + @Override + protected Control createContents(Composite parent) { + Control content = super.createContents(parent); + + // this must be called after createContents() has happened so that the + // ok button has been created (it's created after the call to createDialogArea) + updateDefaultSelection(); - AndroidLaunchController.getInstance().continueLaunch(response, project, launch, - launchInfo, config); + return content; } + - /** - * Create the device chooser dialog contents. - * @param shell the parent shell. - */ - private void createContents(final Shell shell) { - shell.setLayout(new GridLayout(1, true)); + @Override + protected Control createDialogArea(Composite parent) { + Composite top = new Composite(parent, SWT.NONE); + top.setLayout(new GridLayout(1, true)); + + mDeviceRadioButton = new Button(top, SWT.RADIO); + mDeviceRadioButton.setText("Choose an Android running device"); + mDeviceRadioButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + boolean deviceMode = mDeviceRadioButton.getSelection(); + + mDeviceTable.setEnabled(deviceMode); + mPreferredAvdSelector.setEnabled(!deviceMode); - shell.addListener(SWT.Close, new Listener() { - public void handleEvent(Event event) { - event.doit = true; + if (deviceMode) { + handleDeviceSelection(); + } else { + mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected()); + } + + enableOkButton(); } }); + mDeviceRadioButton.setSelection(true); - Label l = new Label(shell, SWT.NONE); - l.setText("Select the target device."); + + // offset the selector from the radio button + Composite offsetComp = new Composite(top, SWT.NONE); + offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + GridLayout layout = new GridLayout(1, false); + layout.marginRight = layout.marginHeight = 0; + layout.marginLeft = 30; + offsetComp.setLayout(layout); IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); - mDeviceTable = new Table(shell, SWT.SINGLE | SWT.FULL_SELECTION); - mDeviceTable.setLayoutData(new GridData(GridData.FILL_BOTH)); + mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION); + GridData gd; + mDeviceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); + gd.heightHint = 100; + mDeviceTable.setHeaderVisible(true); mDeviceTable.setLinesVisible(true); @@ -342,91 +380,47 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener IStructuredSelection structuredSelection = (IStructuredSelection)selection; Object object = structuredSelection.getFirstElement(); if (object instanceof Device) { - Device selectedDevice = (Device)object; - - mResponse.deviceToUse = selectedDevice; - mResponse.mustContinue = true; - shell.close(); + mResponse.setDeviceToUse((Device)object); } } } }); - - // bottom part with the ok/cancel - Composite bottomComp = new Composite(shell, SWT.NONE); - bottomComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - // 3 items in the layout: createButton, spacer, composite with ok/cancel - // (to force same width). - bottomComp.setLayout(new GridLayout(3 /* numColums */, false /* makeColumnsEqualWidth */)); - - mCreateButton = new Button(bottomComp, SWT.NONE); - mCreateButton.setText("Launch Emulator"); - mCreateButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - mResponse.mustContinue = true; - mResponse.mustLaunchEmulator = true; - shell.close(); - } - }); - // the spacer - Composite spacer = new Composite(bottomComp, SWT.NONE); - GridData gd; - spacer.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - gd.heightHint = 0; + Button radio2 = new Button(top, SWT.RADIO); + radio2.setText("Launch a new Virtual Device"); + + // offset the selector from the radio button + offsetComp = new Composite(top, SWT.NONE); + offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout = new GridLayout(1, false); + layout.marginRight = layout.marginHeight = 0; + layout.marginLeft = 30; + offsetComp.setLayout(layout); - // the composite to contain ok/cancel - Composite buttonContainer = new Composite(bottomComp, SWT.NONE); - GridLayout gl = new GridLayout(2 /* numColums */, true /* makeColumnsEqualWidth */); - gl.marginHeight = gl.marginWidth = 0; - buttonContainer.setLayout(gl); - - mOkButton = new Button(buttonContainer, SWT.NONE); - mOkButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mOkButton.setEnabled(false); - mOkButton.setText("OK"); - mOkButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - mResponse.mustContinue = true; - shell.close(); - } - }); - - Button cancelButton = new Button(buttonContainer, SWT.NONE); - cancelButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - cancelButton.setText("Cancel"); - cancelButton.addSelectionListener(new SelectionAdapter() { + mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget, + false /*allowMultipleSelection*/); + mPreferredAvdSelector.setTableHeightHint(100); + mPreferredAvdSelector.setEnabled(false); + mDeviceTable.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - mResponse.mustContinue = false; - shell.close(); + handleDeviceSelection(); } }); - - mDeviceTable.addSelectionListener(new SelectionAdapter() { + + mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - int count = mDeviceTable.getSelectionCount(); - if (count != 1) { - handleSelection(null); - } else { - int index = mDeviceTable.getSelectionIndex(); - Object data = mViewer.getElementAt(index); - if (data instanceof Device) { - handleSelection((Device)data); - } else { - handleSelection(null); - } + if (mDisableAvdSelectionChange == false) { + mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected()); + enableOkButton(); } } }); - mDeviceTable.setFocus(); - shell.setDefaultButton(mOkButton); - - updateDefaultSelection(); + AndroidDebugBridge.addDeviceChangeListener(this); + + return top; } private void loadImages() { @@ -504,6 +498,10 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener // update the selection updateDefaultSelection(); + + // update the display of AvdInfo (since it's filtered to only display + // non running AVD.) + refillAvdList(); } else { // table is disposed, we need to do something. // lets remove ourselves from the listener. @@ -546,24 +544,50 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener // update the defaultSelection. updateDefaultSelection(); - + + // update the display of AvdInfo (since it's filtered to only display + // non running AVD). This is done on deviceChanged because the avd name + // of a (emulator) device may be updated as the emulator boots. + refillAvdList(); + // if the changed device is the current selection, // we update the OK button based on its state. - if (device == mResponse.deviceToUse) { - mOkButton.setEnabled(mResponse.deviceToUse.isOnline()); + if (device == mResponse.getDeviceToUse()) { + enableOkButton(); } + } else { // table is disposed, we need to do something. // lets remove ourselves from the listener. AndroidDebugBridge.removeDeviceChangeListener(dialog); } - } }); } } /** + * Returns whether the dialog is in "device" mode (true), or in "avd" mode (false). + */ + private boolean isDeviceMode() { + return mDeviceRadioButton.getSelection(); + } + + /** + * Enables or disables the OK button of the dialog based on various selections in the dialog. + */ + private void enableOkButton() { + Button okButton = getButton(IDialogConstants.OK_ID); + + if (isDeviceMode()) { + okButton.setEnabled(mResponse.getDeviceToUse() != null && + mResponse.getDeviceToUse().isOnline()); + } else { + okButton.setEnabled(mResponse.getAvdToLaunch() != null); + } + } + + /** * Executes the {@link Runnable} in the UI thread. * @param runnable the runnable to execute. */ @@ -577,16 +601,31 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener } } + private void handleDeviceSelection() { + int count = mDeviceTable.getSelectionCount(); + if (count != 1) { + handleSelection(null); + } else { + int index = mDeviceTable.getSelectionIndex(); + Object data = mViewer.getElementAt(index); + if (data instanceof Device) { + handleSelection((Device)data); + } else { + handleSelection(null); + } + } + } + private void handleSelection(Device device) { - mResponse.deviceToUse = device; - mOkButton.setEnabled(device != null && mResponse.deviceToUse.isOnline()); + mResponse.setDeviceToUse(device); + enableOkButton(); } /** * Look for a default device to select. This is done by looking for the running * clients on each device and finding one similar to the one being launched. * <p/> - * This is done every time the device list changed, until there is a selection.. + * This is done every time the device list changed unless there is a already selection. */ private void updateDefaultSelection() { if (mDeviceTable.getSelectionCount() == 0) { @@ -599,8 +638,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener for (Client client : clients) { - if (mLaunchInfo.mPackageName.equals( - client.getClientData().getClientDescription())) { + if (mPackageName.equals(client.getClientData().getClientDescription())) { // found a match! Select it. mViewer.setSelection(new StructuredSelection(device)); handleSelection(device); @@ -611,6 +649,57 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener } } } - } + handleDeviceSelection(); + } + + /** + * Returns the list of {@link AvdInfo} that are not already running in an emulator. + */ + private AvdInfo[] getNonRunningAvds() { + ArrayList<AvdInfo> list = new ArrayList<AvdInfo>(); + + Device[] devices = AndroidDebugBridge.getBridge().getDevices(); + + // loop through all the Avd and put the one that are not running in the list. + avdLoop: for (AvdInfo info : mFullAvdList) { + for (Device d : devices) { + if (info.getName().equals(d.getAvdName())) { + continue avdLoop; + } + } + list.add(info); + } + + return list.toArray(new AvdInfo[list.size()]); + } + + /** + * Refills the AVD list keeping the current selection. + */ + private void refillAvdList() { + AvdInfo[] array = getNonRunningAvds(); + + // save the current selection + AvdInfo selected = mPreferredAvdSelector.getFirstSelected(); + + // disable selection change. + mDisableAvdSelectionChange = true; + + // set the new list in the selector + mPreferredAvdSelector.setAvds(array, mProjectTarget); + + // attempt to reselect the proper avd if needed + if (selected != null) { + if (mPreferredAvdSelector.setSelection(selected) == false) { + // looks like the selection is lost. this can happen if an emulator + // running the AVD that was selected was launched from outside of Eclipse). + mResponse.setAvdToLaunch(null); + enableOkButton(); + } + } + + // enable the selection change + mDisableAvdSelectionChange = false; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java index a581e5c..d919c1f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java @@ -89,6 +89,8 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { private Button mNoBootAnimButton; + private Label mPreferredAvdLabel; + /** * Returns the emulator ready speed option value. * @param value The index of the combo selection. @@ -160,13 +162,26 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { @Override public void widgetSelected(SelectionEvent e) { updateLaunchConfigurationDialog(); + + boolean auto = mAutoTargetButton.getSelection(); + mPreferredAvdSelector.setEnabled(auto); + mPreferredAvdLabel.setEnabled(auto); } }); - new Label(targetModeGroup, SWT.NONE).setText("Preferred Android Virtual Device"); + Composite offsetComp = new Composite(targetModeGroup, SWT.NONE); + offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout = new GridLayout(1, false); + layout.marginRight = layout.marginHeight = 0; + layout.marginLeft = 30; + offsetComp.setLayout(layout); + + mPreferredAvdLabel = new Label(offsetComp, SWT.NONE); + mPreferredAvdLabel.setText("Select a preferred Android Virtual Device:"); AvdInfo[] avds = new AvdInfo[0]; - mPreferredAvdSelector = new AvdSelector(targetModeGroup, avds, + mPreferredAvdSelector = new AvdSelector(offsetComp, avds, false /*allowMultipleSelection*/); + mPreferredAvdSelector.setTableHeightHint(100); mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java index afa1fb5..30bf7ed 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java @@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.project.internal; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.adt.sdk.LoadStatus; import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.project.BaseProjectHelper; @@ -44,8 +45,12 @@ import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashSet; +import java.util.regex.Pattern; /** * Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to @@ -56,6 +61,21 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit private final static String CONTAINER_ID = "com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$ + /** path separator to store multiple paths in a single property. This is guaranteed to not + * be in a path. + */ + private final static String PATH_SEPARATOR = "\u001C"; //$NON-NLS-1$ + + private final static String PROPERTY_CONTAINER_CACHE = "androidContainerCache"; //$NON-NLS-1$ + private final static String PROPERTY_TARGET_NAME = "androidTargetCache"; //$NON-NLS-1$ + private final static String CACHE_VERSION = "01"; //$NON-NLS-1$ + private final static String CACHE_VERSION_SEP = CACHE_VERSION + PATH_SEPARATOR; + + private final static int PATH_ANDROID_JAR = 0; + private final static int PATH_ANDROID_SRC = 1; + private final static int PATH_ANDROID_DOCS = 2; + private final static int PATH_ANDROID_OPT_DOCS = 3; + public AndroidClasspathContainerInitializer() { // pass } @@ -71,7 +91,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit if (CONTAINER_ID.equals(containerPath.toString())) { JavaCore.setClasspathContainer(new Path(CONTAINER_ID), new IJavaProject[] { project }, - new IClasspathContainer[] { allocateAndroidContainer(CONTAINER_ID, project) }, + new IClasspathContainer[] { allocateAndroidContainer(project) }, new NullProgressMonitor()); } } @@ -111,7 +131,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit IClasspathContainer[] containers = new IClasspathContainer[projectCount]; for (int i = 0 ; i < projectCount; i++) { - containers[i] = allocateAndroidContainer(CONTAINER_ID, androidProjects[i]); + containers[i] = allocateAndroidContainer(androidProjects[i]); } // give each project their new container in one call. @@ -128,133 +148,180 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit /** * Allocates and returns an {@link AndroidClasspathContainer} object with the proper * path to the framework jar file. - * @param containerId the container id to be used. * @param javaProject The java project that will receive the container. */ - private static IClasspathContainer allocateAndroidContainer(String containerId, - IJavaProject javaProject) { + private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) { final IProject iProject = javaProject.getProject(); - // remove potential MARKER_TARGETs. - try { - if (iProject.exists()) { - iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true, - IResource.DEPTH_INFINITE); - } - } catch (CoreException ce) { - // just log the error - AdtPlugin.log(ce, "Error removing target marker."); - } - - // First we check if the SDK has been loaded. - // By passing the javaProject to getSdkLoadStatus(), we ensure that, should the SDK - // not be loaded yet, the classpath container will be resolved again once the SDK is loaded. - boolean sdkIsLoaded = AdtPlugin.getDefault().getSdkLoadStatus(javaProject) == - LoadStatus.LOADED; - - // then we check if the project has a valid target. - IAndroidTarget target = null; - if (sdkIsLoaded) { - target = Sdk.getCurrent().getTarget(iProject); - } - - // if we are loaded and the target is non null, we create a valid ClassPathContainer - if (sdkIsLoaded && target != null) { - String targetName = null; - if (target.isPlatform()) { - targetName = target.getName(); - } else { - targetName = String.format("%1$s (%2$s)", target.getName(), - target.getApiVersionName()); - } - - return new AndroidClasspathContainer(createFrameworkClasspath(target), - new Path(containerId), targetName); - } - - // else we put a marker on the project, and return a dummy container (to replace the - // previous one if there was one.) - - // Get the project's target's hash string (if it exists) - String hashString = Sdk.getProjectTargetHashString(iProject); - - String message = null; + String markerMessage = null; boolean outputToConsole = true; - if (hashString == null || hashString.length() == 0) { - // if there is no hash string we only show this if the SDK is loaded. - // For a project opened at start-up with no target, this would be displayed twice, - // once when the project is opened, and once after the SDK has finished loading. - // By testing the sdk is loaded, we only show this once in the console. - if (sdkIsLoaded) { - message = String.format( - "Project has no target set. Edit the project properties to set one."); - } - } else if (sdkIsLoaded) { - message = String.format( - "Unable to resolve target '%s'", hashString); - } else { - // this is the case where there is a hashString but the SDK is not yet - // loaded and therefore we can't get the target yet. - message = String.format( - "Unable to resolve target '%s' until the SDK is loaded.", hashString); - - // let's not log this one to the console as it will happen at every boot, - // and it's expected. (we do keep the error marker though). - outputToConsole = false; - } - if (message != null) { - // log the error and put the marker on the project if we can. - if (outputToConsole) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, message); - } + try { + AdtPlugin plugin = AdtPlugin.getDefault(); - try { - BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, message, -1, - IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH); - } catch (CoreException e) { - // In some cases, the workspace may be locked for modification when we pass here. - // We schedule a new job to put the marker after. - final String fmessage = message; - Job markerJob = new Job("Android SDK: Resolving error markers") { - @SuppressWarnings("unchecked") - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, - fmessage, -1, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH); - } catch (CoreException e2) { - return e2.getStatus(); - } + // get the lock object for project manipulation during SDK load. + Object lock = plugin.getSdkLockObject(); + synchronized (lock) { + boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED; + + // check if the project has a valid target. + IAndroidTarget target = null; + if (sdkIsLoaded) { + target = Sdk.getCurrent().getTarget(iProject); + } - return Status.OK_STATUS; + // if we are loaded and the target is non null, we create a valid ClassPathContainer + if (sdkIsLoaded && target != null) { + String targetName = null; + if (target.isPlatform()) { + targetName = target.getName(); + } else { + targetName = String.format("%1$s (%2$s)", target.getName(), + target.getApiVersionName()); } - }; - // build jobs are run after other interactive jobs - markerJob.setPriority(Job.BUILD); - markerJob.schedule(); - } - } + return new AndroidClasspathContainer( + createClasspathEntries(iProject, target, targetName), + new Path(CONTAINER_ID), targetName); + } - // return a dummy container to replace the one we may have had before. - return new IClasspathContainer() { - public IClasspathEntry[] getClasspathEntries() { - return new IClasspathEntry[0]; - } + // In case of error, we'll try different thing to provide the best error message + // possible. + // Get the project's target's hash string (if it exists) + String hashString = Sdk.getProjectTargetHashString(iProject); - public String getDescription() { - return "Unable to get system library for the project"; - } + if (hashString == null || hashString.length() == 0) { + // if there is no hash string we only show this if the SDK is loaded. + // For a project opened at start-up with no target, this would be displayed + // twice, once when the project is opened, and once after the SDK has + // finished loading. + // By testing the sdk is loaded, we only show this once in the console. + if (sdkIsLoaded) { + markerMessage = String.format( + "Project has no target set. Edit the project properties to set one."); + } + } else if (sdkIsLoaded) { + markerMessage = String.format( + "Unable to resolve target '%s'", hashString); + } else { + // this is the case where there is a hashString but the SDK is not yet + // loaded and therefore we can't get the target yet. + // We check if there is a cache of the needed information. + AndroidClasspathContainer container = getContainerFromCache(iProject); + + if (container == null) { + // either the cache was wrong (ie folder does not exists anymore), or + // there was no cache. In this case we need to make sure the project + // is resolved again after the SDK is loaded. + plugin.setProjectToResolve(javaProject); + + markerMessage = String.format( + "Unable to resolve target '%s' until the SDK is loaded.", + hashString); + + // let's not log this one to the console as it will happen at every boot, + // and it's expected. (we do keep the error marker though). + outputToConsole = false; - public int getKind() { - return IClasspathContainer.K_DEFAULT_SYSTEM; + } else { + // we created a container from the cache, so we register the project + // to be checked for cache validity once the SDK is loaded + plugin.setProjectToCheck(javaProject); + + // and return the container + return container; + } + + } + + // return a dummy container to replace the one we may have had before. + // It'll be replaced by the real when if/when the target is resolved if/when the + // SDK finishes loading. + return new IClasspathContainer() { + public IClasspathEntry[] getClasspathEntries() { + return new IClasspathEntry[0]; + } + + public String getDescription() { + return "Unable to get system library for the project"; + } + + public int getKind() { + return IClasspathContainer.K_DEFAULT_SYSTEM; + } + + public IPath getPath() { + return null; + } + }; } + } finally { + if (markerMessage != null) { + // log the error and put the marker on the project if we can. + if (outputToConsole) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, + markerMessage); + } + + try { + BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, markerMessage, + -1, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH); + } catch (CoreException e) { + // In some cases, the workspace may be locked for modification when we + // pass here. + // We schedule a new job to put the marker after. + final String fmessage = markerMessage; + Job markerJob = new Job("Android SDK: Resolving error markers") { + @SuppressWarnings("unchecked") + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, + fmessage, -1, IMarker.SEVERITY_ERROR, + IMarker.PRIORITY_HIGH); + } catch (CoreException e2) { + return e2.getStatus(); + } - public IPath getPath() { - return null; + return Status.OK_STATUS; + } + }; + + // build jobs are run after other interactive jobs + markerJob.setPriority(Job.BUILD); + markerJob.schedule(); + } + } else { + // no error, remove potential MARKER_TARGETs. + try { + if (iProject.exists()) { + iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true, + IResource.DEPTH_INFINITE); + } + } catch (CoreException ce) { + // In some cases, the workspace may be locked for modification when we pass + // here, so we schedule a new job to put the marker after. + Job markerJob = new Job("Android SDK: Resolving error markers") { + @SuppressWarnings("unchecked") + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true, + IResource.DEPTH_INFINITE); + } catch (CoreException e2) { + return e2.getStatus(); + } + + return Status.OK_STATUS; + } + }; + + // build jobs are run after other interactive jobs + markerJob.setPriority(Job.BUILD); + markerJob.schedule(); + } } - }; + } } /** @@ -264,21 +331,114 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit * java doc directory. This is dynamically created when a project is opened, * and never saved in the project itself, so there's no risk of storing an * obsolete path. - * + * The method also stores the paths used to create the entries in the project persistent + * properties. A new {@link AndroidClasspathContainer} can be created from the stored path + * using the {@link #getContainerFromCache(IProject)} method. + * @param project * @param target The target that contains the libraries. + * @param targetName + */ + private static IClasspathEntry[] createClasspathEntries(IProject project, + IAndroidTarget target, String targetName) { + + // get the path from the target + String[] paths = getTargetPaths(target); + + // create the classpath entry from the paths + IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths); + + // paths now contains all the path required to recreate the IClasspathEntry with no + // target info. We encode them in a single string, with each path separated by + // OS path separator. + StringBuilder sb = new StringBuilder(CACHE_VERSION); + for (String p : paths) { + sb.append(PATH_SEPARATOR); + sb.append(p); + } + + // store this in a project persistent property + ProjectHelper.saveStringProperty(project, PROPERTY_CONTAINER_CACHE, sb.toString()); + ProjectHelper.saveStringProperty(project, PROPERTY_TARGET_NAME, targetName); + + return entries; + } + + /** + * Generates an {@link AndroidClasspathContainer} from the project cache, if possible. + */ + private static AndroidClasspathContainer getContainerFromCache(IProject project) { + // get the cached info from the project persistent properties. + String cache = ProjectHelper.loadStringProperty(project, PROPERTY_CONTAINER_CACHE); + String targetNameCache = ProjectHelper.loadStringProperty(project, PROPERTY_TARGET_NAME); + if (cache == null || targetNameCache == null) { + return null; + } + + // the first 2 chars must match CACHE_VERSION. The 3rd char is the normal separator. + if (cache.startsWith(CACHE_VERSION_SEP) == false) { + return null; + } + + cache = cache.substring(CACHE_VERSION_SEP.length()); + + // the cache contains multiple paths, separated by a character guaranteed to not be in + // the path (\u001C). + // The first 3 are for android.jar (jar, source, doc), the rest are for the optional + // libraries and should contain at least one doc and a jar (if there are any libraries). + // Therefore, the path count should be 3 or 5+ + String[] paths = cache.split(Pattern.quote(PATH_SEPARATOR)); + if (paths.length < 3 || paths.length == 4) { + return null; + } + + // now we check the paths actually exist. + // There's an exception: If the source folder for android.jar does not exist, this is + // not a problem, so we skip it. + // Also paths[PATH_ANDROID_DOCS] is a URI to the javadoc, so we test it a bit differently. + try { + if (new File(paths[PATH_ANDROID_JAR]).exists() == false || + new File(new URI(paths[PATH_ANDROID_DOCS])).exists() == false) { + return null; + } + } catch (URISyntaxException e) { + return null; + } finally { + + } + + for (int i = 3 ; i < paths.length; i++) { + String path = paths[i]; + if (path.length() > 0) { + File f = new File(path); + if (f.exists() == false) { + return null; + } + } + } + + IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths); + + return new AndroidClasspathContainer(entries, + new Path(CONTAINER_ID), targetNameCache); + } + + /** + * Generates an array of {@link IClasspathEntry} from a set of paths. + * @see #getTargetPaths(IAndroidTarget) */ - private static IClasspathEntry[] createFrameworkClasspath(IAndroidTarget target) { + private static IClasspathEntry[] createClasspathEntriesFromPaths(String[] paths) { ArrayList<IClasspathEntry> list = new ArrayList<IClasspathEntry>(); // First, we create the IClasspathEntry for the framework. // now add the android framework to the class path. // create the path object. - IPath android_lib = new Path(target.getPath(IAndroidTarget.ANDROID_JAR)); - IPath android_src = new Path(target.getPath(IAndroidTarget.SOURCES)); - + IPath android_lib = new Path(paths[PATH_ANDROID_JAR]); + IPath android_src = new Path(paths[PATH_ANDROID_SRC]); + // create the java doc link. IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute( - IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, AdtPlugin.getUrlDoc()); + IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, + paths[PATH_ANDROID_DOCS]); // create the access rule to restrict access to classes in com.android.internal IAccessRule accessRule = JavaCore.newAccessRule( @@ -292,42 +452,184 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit new IClasspathAttribute[] { cpAttribute }, false // not exported. ); - + list.add(frameworkClasspathEntry); // now deal with optional libraries + if (paths.length >= 5) { + String docPath = paths[PATH_ANDROID_OPT_DOCS]; + int i = 4; + while (i < paths.length) { + Path jarPath = new Path(paths[i++]); + + IClasspathAttribute[] attributes = null; + if (docPath.length() > 0) { + attributes = new IClasspathAttribute[] { + JavaCore.newClasspathAttribute( + IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, + docPath) + }; + } + + IClasspathEntry entry = JavaCore.newLibraryEntry( + jarPath, + null, // source attachment path + null, // default source attachment root path. + null, + attributes, + false // not exported. + ); + list.add(entry); + } + } + + return list.toArray(new IClasspathEntry[list.size()]); + } + + /** + * Checks the projects' caches. If the cache was valid, the project is removed from the list. + * @param projects the list of projects to check. + */ + public static void checkProjectsCache(ArrayList<IJavaProject> projects) { + int i = 0; + projectLoop: while (i < projects.size()) { + IJavaProject javaProject = projects.get(i); + IProject iProject = javaProject.getProject(); + + // get the target from the project and its paths + IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject()); + if (target == null) { + // this is really not supposed to happen. This would mean there are cached paths, + // but default.properties was deleted. Keep the project in the list to force + // a resolve which will display the error. + i++; + continue; + } + + String[] targetPaths = getTargetPaths(target); + + // now get the cached paths + String cache = ProjectHelper.loadStringProperty(iProject, PROPERTY_CONTAINER_CACHE); + if (cache == null) { + // this should not happen. We'll force resolve again anyway. + i++; + continue; + } + + String[] cachedPaths = cache.split(Pattern.quote(PATH_SEPARATOR)); + if (cachedPaths.length < 3 || cachedPaths.length == 4) { + // paths length is wrong. simply resolve the project again + i++; + continue; + } + + // Now we compare the paths. The first 4 can be compared directly. + // because of case sensitiveness we need to use File objects + + if (targetPaths.length != cachedPaths.length) { + // different paths, force resolve again. + i++; + continue; + } + + // compare the main paths (android.jar, main sources, main javadoc) + if (new File(targetPaths[PATH_ANDROID_JAR]).equals( + new File(cachedPaths[PATH_ANDROID_JAR])) == false || + new File(targetPaths[PATH_ANDROID_SRC]).equals( + new File(cachedPaths[PATH_ANDROID_SRC])) == false || + new File(targetPaths[PATH_ANDROID_DOCS]).equals( + new File(cachedPaths[PATH_ANDROID_DOCS])) == false) { + // different paths, force resolve again. + i++; + continue; + } + + if (cachedPaths.length > PATH_ANDROID_OPT_DOCS) { + // compare optional libraries javadoc + if (new File(targetPaths[PATH_ANDROID_OPT_DOCS]).equals( + new File(cachedPaths[PATH_ANDROID_OPT_DOCS])) == false) { + // different paths, force resolve again. + i++; + continue; + } + + // testing the optional jar files is a little bit trickier. + // The order is not guaranteed to be identical. + // From a previous test, we do know however that there is the same number. + // The number of libraries should be low enough that we can simply go through the + // lists manually. + targetLoop: for (int tpi = 4 ; tpi < targetPaths.length; tpi++) { + String targetPath = targetPaths[tpi]; + + // look for a match in the other array + for (int cpi = 4 ; cpi < cachedPaths.length; cpi++) { + if (new File(targetPath).equals(new File(cachedPaths[cpi]))) { + // found a match. Try the next targetPath + continue targetLoop; + } + } + + // if we stop here, we haven't found a match, which means there's a + // discrepancy in the libraries. We force a resolve. + i++; + continue projectLoop; + } + } + + // at the point the check passes, and we can remove the project from the list. + // we do not increment i in this case. + projects.remove(i); + } + } + + /** + * Returns the paths necessary to create the {@link IClasspathEntry} for this targets. + * <p/>The paths are always in the same order. + * <ul> + * <li>Path to android.jar</li> + * <li>Path to the source code for android.jar</li> + * <li>Path to the javadoc for the android platform</li> + * </ul> + * Additionally, if there are optional libraries, the array will contain: + * <ul> + * <li>Path to the librairies javadoc</li> + * <li>Path to the first .jar file</li> + * <li>(more .jar as needed)</li> + * </ul> + */ + private static String[] getTargetPaths(IAndroidTarget target) { + ArrayList<String> paths = new ArrayList<String>(); + + // first, we get the path for android.jar + // The order is: android.jar, source folder, docs folder + paths.add(target.getPath(IAndroidTarget.ANDROID_JAR)); + paths.add(target.getPath(IAndroidTarget.SOURCES)); + paths.add(AdtPlugin.getUrlDoc()); + + // now deal with optional libraries. IOptionalLibrary[] libraries = target.getOptionalLibraries(); if (libraries != null) { + // all the optional libraries use the same javadoc, so we start with this + String targetDocPath = target.getPath(IAndroidTarget.DOCS); + if (targetDocPath != null) { + paths.add(targetDocPath); + } else { + // we add an empty string, to always have the same count. + paths.add(""); + } + + // because different libraries could use the same jar file, we make sure we add + // each jar file only once. HashSet<String> visitedJars = new HashSet<String>(); for (IOptionalLibrary library : libraries) { String jarPath = library.getJarPath(); if (visitedJars.contains(jarPath) == false) { visitedJars.add(jarPath); - - // create the java doc link, if needed - String targetDocPath = target.getPath(IAndroidTarget.DOCS); - IClasspathAttribute[] attributes = null; - if (targetDocPath != null) { - attributes = new IClasspathAttribute[] { - JavaCore.newClasspathAttribute( - IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, - targetDocPath) - }; - } - - IClasspathEntry entry = JavaCore.newLibraryEntry( - new Path(library.getJarPath()), - null, // source attachment path - null, // default source attachment root path. - null, - attributes, - false // not exported. - ); - list.add(entry); + paths.add(jarPath); } } } - return list.toArray(new IClasspathEntry[list.size()]); + return paths.toArray(new String[paths.size()]); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java index 77467cd..ca7cac5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java @@ -1691,7 +1691,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette // In this case data could be null, but this is not an error. // We can just silently return, as all the opened editors are automatically // refreshed once the SDK finishes loading. - if (AdtPlugin.getDefault().getSdkLoadStatus(null) != LoadStatus.LOADING) { + if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADING) { showErrorInEditor(String.format( "The project target (%s) was not properly loaded.", target.getName())); |