aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--androidprefs/src/com/android/prefs/AndroidLocation.java28
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java28
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java119
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java124
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncPerf.java219
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java46
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java3
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/SyncCommon.java158
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java83
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java82
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java423
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java586
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java2
-rw-r--r--scripts/alias_rules.xml2
-rw-r--r--scripts/android_rules.xml2
-rw-r--r--scripts/build.alias.template2
-rw-r--r--scripts/build.template2
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java97
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/Main.java341
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java178
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java21
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java247
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java45
28 files changed, 1983 insertions, 891 deletions
diff --git a/androidprefs/src/com/android/prefs/AndroidLocation.java b/androidprefs/src/com/android/prefs/AndroidLocation.java
index 68a87b7..cfd9f53 100644
--- a/androidprefs/src/com/android/prefs/AndroidLocation.java
+++ b/androidprefs/src/com/android/prefs/AndroidLocation.java
@@ -22,15 +22,6 @@ import java.io.File;
* Manages the location of the android files (including emulator files, ddms config, debug keystore)
*/
public final class AndroidLocation {
-
- /**
- * Used to know where to store the user data image.
- * <p/>
- * This <em>must</em> match the constant ANDROID_SDK_VERSION used by the emulator
- * to find its own emulator images. It is defined in tools/qemu/android.h
- */
- private static final String ANDROID_SDK_VERSION = "SDK-1.0";
-
/**
* Virtual Device folder inside the path returned by {@link #getFolder()}
*/
@@ -80,25 +71,6 @@ public final class AndroidLocation {
}
/**
- * Returns the folder where the emulator is going to find its android related files.
- * @return an OS specific path, terminated by a separator.
- * @throws AndroidLocationException
- */
- public final static String getEmulatorFolder() throws AndroidLocationException {
- String path = getFolder() + ANDROID_SDK_VERSION + File.separator;
-
- File f = new File(path);
- if (f.exists() == false) {
- f.mkdir();
- } else if (f.isFile()) {
- throw new AndroidLocationException(path +
- " is not a directory! This is required to run Android tools.");
- }
-
- return path;
- }
-
- /**
* Checks a list of system properties and/or system environment variables for validity, and
* existing director, and returns the first one.
* @param names
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
index fa3f0e4..d168476 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
@@ -200,22 +200,26 @@ public final class DevicePanel extends Panel implements IDebugBridgeChangeListen
case DEVICE_COL_STATE:
return getStateString(device);
case DEVICE_COL_BUILD: {
- String avdName = device.getAvdName();
- String debuggable = device.getProperty(Device.PROP_DEBUGGABLE);
String version = device.getProperty(Device.PROP_BUILD_VERSION);
- if (device.isEmulator()) {
- if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
- return String.format("%1$s [%2$s, debug]", avdName, //$NON-NLS-1$
- version);
+ if (version != null) {
+ String debuggable = device.getProperty(Device.PROP_DEBUGGABLE);
+ if (device.isEmulator()) {
+ String avdName = device.getAvdName();
+ if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
+ return String.format("%1$s [%2$s, debug]", avdName,
+ version);
+ } else {
+ return String.format("%1$s [%2$s]", avdName, version); //$NON-NLS-1$
+ }
} else {
- return String.format("%1$s [%2$s]", avdName, version); //$NON-NLS-1$
+ if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
+ return String.format("%1$s, debug", version);
+ } else {
+ return String.format("%1$s", version); //$NON-NLS-1$
+ }
}
} else {
- if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
- return String.format("%1$s, debug", version); //$NON-NLS-1$
- } else {
- return String.format("%1$s", version); //$NON-NLS-1$
- }
+ return "unknown";
}
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java
index 0057c86..82cc7a4 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java
@@ -39,7 +39,7 @@ import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;
-public class DisplaySync extends EventDisplay {
+public class DisplaySync extends SyncCommon {
// Information to graph for each authority
private TimePeriodValues mDatasetsSync[];
@@ -49,27 +49,12 @@ public class DisplaySync extends EventDisplay {
// Dataset of error events to graph
private TimeSeries mDatasetError;
-
- // State information while processing the event stream
- private int mLastState; // 0 if event started, 1 if event stopped
- private long mLastStartTime; // ms
- private long mLastStopTime; //ms
- private String mLastDetails;
- private int mLastEvent; // server, poll, etc
public DisplaySync(String name) {
super(name);
}
/**
- * Resets the display.
- */
- @Override
- void resetUI() {
- initSyncDisplay();
- }
-
- /**
* Creates the UI for the event display.
* @param parent the parent composite.
* @param logParser the current log parser.
@@ -79,21 +64,22 @@ public class DisplaySync extends EventDisplay {
public Control createComposite(final Composite parent, EventLogParser logParser,
final ILogColumnListener listener) {
Control composite = createCompositeChart(parent, logParser, "Sync Status");
- initSyncDisplay();
+ resetUI();
return composite;
}
/**
- * Initialize the Plot and series data for the sync display.
+ * Resets the display.
*/
- void initSyncDisplay() {
+ @Override
+ void resetUI() {
+ super.resetUI();
XYPlot xyPlot = mChart.getXYPlot();
XYBarRenderer br = new XYBarRenderer();
mDatasetsSync = new TimePeriodValues[NUM_AUTHS];
mTooltipsSync = new List[NUM_AUTHS];
mTooltipGenerators = new CustomXYToolTipGenerator[NUM_AUTHS];
- mLastDetails = "";
TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection();
xyPlot.setDataset(tpvc);
@@ -123,62 +109,29 @@ public class DisplaySync extends EventDisplay {
br.setSeriesToolTipGenerator(i, mTooltipGenerators[i]);
mTooltipGenerators[i].addToolTipSeries(mTooltipsSync[i]);
- mDatasetsSyncTickle[i] = new TimeSeries(AUTH_NAMES[i] + " tickle", FixedMillisecond.class);
+ mDatasetsSyncTickle[i] = new TimeSeries(AUTH_NAMES[i] + " tickle",
+ FixedMillisecond.class);
tsc.addSeries(mDatasetsSyncTickle[i]);
ls.setSeriesShape(i, ShapeUtilities.createUpTriangle(2.5f));
}
}
/**
- * Updates the display with a new event. This is the main entry point for
- * each event. This method has the logic to tie together the start event,
- * stop event, and details event into one graph item. Note that the details
- * can happen before or after the stop event.
- * @param event The event
- * @param logParser the log parser (unused)
+ * Updates the display with a new event.
+ *
+ * @param event The event
+ * @param logParser The parser providing the event.
*/
@Override
void newEvent(EventContainer event, EventLogParser logParser) {
+ super.newEvent(event, logParser); // Handle sync operation
try {
- if (event.mTag == EVENT_SYNC) {
- int state = Integer.parseInt(event.getValueAsString(1));
- if (state == 0) { // start
- mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
- mLastState = 0;
- mLastEvent = Integer.parseInt(event.getValueAsString(2));
- mLastDetails = "";
- } else if (state == 1) { // stop
- if (mLastState == 0) {
- mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
- if (mLastStartTime == 0) {
- // Log starts with a stop event
- mLastStartTime = mLastStopTime;
- }
- addEvent(event);
- mLastState = 1;
- }
- }
- } else if (event.mTag == EVENT_TICKLE) {
+ if (event.mTag == EVENT_TICKLE) {
int auth = getAuth(event.getValueAsString(0));
if (auth >= 0) {
long msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
mDatasetsSyncTickle[auth].addOrUpdate(new FixedMillisecond(msec), -1);
}
- } else if (event.mTag == EVENT_SYNC_DETAILS) {
- int auth = getAuth(event.getValueAsString(0));
- mLastDetails = event.getValueAsString(3);
- if (mLastState != 0) { // Not inside event
- long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
- if (updateTime - mLastStopTime <= 250) {
- // Got details within 250ms after event, so delete and re-insert
- // Details later than 250ms (arbitrary) are discarded as probably
- // unrelated.
- int lastItem = mDatasetsSync[auth].getItemCount();
- mDatasetsSync[auth].delete(lastItem-1, lastItem-1);
- mTooltipsSync[auth].remove(lastItem-1);
- addEvent(event);
- }
- }
}
} catch (InvalidTypeException e) {
}
@@ -300,29 +253,31 @@ public class DisplaySync extends EventDisplay {
return sb.toString();
}
+
/**
- * Helper to add an event to the data series.
- * Also updates error series if appropriate (x or X in details).
- * @param event The event
+ * Callback to process a sync event.
*/
- private void addEvent(EventContainer event) {
- try {
- int auth = getAuth(event.getValueAsString(0));
- double height = getHeightFromDetails(mLastDetails);
- height = height / (mLastStopTime - mLastStartTime + 1) * 10000;
- if (height > 30) {
- height = 30;
- }
- mDatasetsSync[auth].add(new SimpleTimePeriod(mLastStartTime, mLastStopTime), height);
- mTooltipsSync[auth].add(getTextFromDetails(auth, mLastDetails,
- mLastEvent));
- mTooltipGenerators[auth].addToolTipSeries(mTooltipsSync[auth]);
- if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
- long msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
- mDatasetError.addOrUpdate(new FixedMillisecond(msec), -1);
- }
- } catch (InvalidTypeException e) {
- e.printStackTrace();
+ @Override
+ void processSyncEvent(EventContainer event, int auth, long startTime, long stopTime,
+ String details, boolean newEvent, int syncSource) {
+ if (!newEvent) {
+ // Details arrived for a previous sync event
+ // Remove event before reinserting.
+ int lastItem = mDatasetsSync[auth].getItemCount();
+ mDatasetsSync[auth].delete(lastItem-1, lastItem-1);
+ mTooltipsSync[auth].remove(lastItem-1);
+ }
+ double height = getHeightFromDetails(details);
+ height = height / (stopTime - startTime + 1) * 10000;
+ if (height > 30) {
+ height = 30;
+ }
+ mDatasetsSync[auth].add(new SimpleTimePeriod(startTime, stopTime), height);
+ mTooltipsSync[auth].add(getTextFromDetails(auth, details, syncSource));
+ mTooltipGenerators[auth].addToolTipSeries(mTooltipsSync[auth]);
+ if (details.indexOf('x') >= 0 || details.indexOf('X') >= 0) {
+ long msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
+ mDatasetError.addOrUpdate(new FixedMillisecond(msec), -1);
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java
index 3087997..36d90ce 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java
@@ -18,7 +18,6 @@ package com.android.ddmuilib.log.event;
import com.android.ddmlib.log.EventContainer;
import com.android.ddmlib.log.EventLogParser;
-import com.android.ddmlib.log.InvalidTypeException;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.jfree.chart.plot.XYPlot;
@@ -35,29 +34,18 @@ import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
-public class DisplaySyncHistogram extends EventDisplay {
+public class DisplaySyncHistogram extends SyncCommon {
+ Map<SimpleTimePeriod, Integer> mTimePeriodMap[];
- // State information while processing the event stream
- protected int mLastState; // 0 if event started, 1 if event stopped
- protected long mLastStartTime; // ms
- protected long mLastStopTime; //ms
- protected String mLastDetails;
- protected int mLastEvent; // server, poll, etc
+ // Information to graph for each authority
+ private TimePeriodValues mDatasetsSyncHist[];
public DisplaySyncHistogram(String name) {
super(name);
}
/**
- * Resets the display.
- */
- @Override
- void resetUI() {
- initSyncHistogramDisplay();
- }
-
- /**
* Creates the UI for the event display.
* @param parent the parent composite.
* @param logParser the current log parser.
@@ -67,22 +55,20 @@ public class DisplaySyncHistogram extends EventDisplay {
public Control createComposite(final Composite parent, EventLogParser logParser,
final ILogColumnListener listener) {
Control composite = createCompositeChart(parent, logParser, "Sync Histogram");
- initSyncHistogramDisplay();
+ resetUI();
return composite;
}
- // Information to graph for each authority
- private TimePeriodValues mDatasetsSyncHist[];
-
/**
- * Initializes the display.
+ * Resets the display.
*/
- private void initSyncHistogramDisplay() {
+ @Override
+ void resetUI() {
+ super.resetUI();
XYPlot xyPlot = mChart.getXYPlot();
AbstractXYItemRenderer br = new XYBarRenderer();
mDatasetsSyncHist = new TimePeriodValues[NUM_AUTHS+1];
- mLastDetails = "";
mTimePeriodMap = new HashMap[NUM_AUTHS + 1];
TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection();
@@ -99,72 +85,44 @@ public class DisplaySyncHistogram extends EventDisplay {
}
/**
- * Updates the display with a new event. This is the main entry point for
- * each event. This method has the logic to tie together the start event,
- * stop event, and details event into one graph item. Note that the details
- * can happen before or after the stop event.
- * @param event The event
+ * Callback to process a sync event.
+ *
+ * @param event The sync event
+ * @param startTime Start time (ms) of events
+ * @param stopTime Stop time (ms) of events
+ * @param details Details associated with the event.
+ * @param newEvent True if this event is a new sync event. False if this event
+ * @param syncSource
*/
@Override
- void newEvent(EventContainer event, EventLogParser logParser) {
- try {
- if (event.mTag == EVENT_SYNC) {
- int state = Integer.parseInt(event.getValueAsString(1));
- if (state == 0) { // start
- mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
- mLastState = 0;
- mLastEvent = Integer.parseInt(event.getValueAsString(2));
- mLastDetails = "";
- } else if (state == 1) { // stop
- if (mLastState == 0) {
- mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
- if (mLastStartTime == 0) {
- // Log starts with a stop event
- mLastStartTime = mLastStopTime;
- }
- int auth = getAuth(event.getValueAsString(0));
- if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
- auth = ERRORS;
- }
- double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour
- addHistEvent(event, auth, delta);
- mLastState = 1;
- }
- }
- } else if (event.mTag == EVENT_SYNC_DETAILS) {
- int auth = getAuth(event.getValueAsString(0));
- mLastDetails = event.getValueAsString(3);
- if (mLastState != 0) { // Not inside event
- long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
- if (updateTime - mLastStopTime <= 250) {
- // Got details within 250ms after event, so delete and re-insert
- // Details later than 250ms (arbitrary) are discarded as probably
- // unrelated.
- //int lastItem = mDatasetsSync[auth].getItemCount();
- //addHistEvent(event);
- if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
- // Item turns out to be in error, so transfer time from old auth to error.
-
- double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour
- addHistEvent(event, auth, -delta);
- addHistEvent(event, ERRORS, delta);
- }
- }
- }
+ void processSyncEvent(EventContainer event, int auth, long startTime, long stopTime,
+ String details, boolean newEvent, int syncSource) {
+ if (newEvent) {
+ if (details.indexOf('x') >= 0 || details.indexOf('X') >= 0) {
+ auth = ERRORS;
+ }
+ double delta = (stopTime - startTime) * 100. / 1000 / 3600; // Percent of hour
+ addHistEvent(0, auth, delta);
+ } else {
+ // sync_details arrived for an event that has already been graphed.
+ if (details.indexOf('x') >= 0 || details.indexOf('X') >= 0) {
+ // Item turns out to be in error, so transfer time from old auth to error.
+ double delta = (stopTime - startTime) * 100. / 1000 / 3600; // Percent of hour
+ addHistEvent(0, auth, -delta);
+ addHistEvent(0, ERRORS, delta);
}
- } catch (InvalidTypeException e) {
}
}
/**
* Helper to add an event to the data series.
* Also updates error series if appropriate (x or X in details).
- * @param event The event
- * @param auth
- * @param value
+ * @param stopTime Time event ends
+ * @param auth Sync authority
+ * @param value Value to graph for event
*/
- private void addHistEvent(EventContainer event, int auth, double value) {
- SimpleTimePeriod hour = getTimePeriod(mLastStopTime, mHistWidth);
+ private void addHistEvent(long stopTime, int auth, double value) {
+ SimpleTimePeriod hour = getTimePeriod(stopTime, mHistWidth);
// Loop over all datasets to do the stacking.
for (int i = auth; i <= ERRORS; i++) {
@@ -172,9 +130,8 @@ public class DisplaySyncHistogram extends EventDisplay {
}
}
- Map<SimpleTimePeriod, Integer> mTimePeriodMap[];
-
- private void addToPeriod(TimePeriodValues tpv[], int auth, SimpleTimePeriod period, double value) {
+ private void addToPeriod(TimePeriodValues tpv[], int auth, SimpleTimePeriod period,
+ double value) {
int index;
if (mTimePeriodMap[auth].containsKey(period)) {
index = mTimePeriodMap[auth].get(period);
@@ -198,7 +155,8 @@ public class DisplaySyncHistogram extends EventDisplay {
TimeZone zone = RegularTimePeriod.DEFAULT_TIME_ZONE;
Calendar calendar = Calendar.getInstance(zone);
calendar.setTime(date);
- long hoursOfYear = calendar.get(Calendar.HOUR_OF_DAY) + calendar.get(Calendar.DAY_OF_YEAR) * 24;
+ long hoursOfYear = calendar.get(Calendar.HOUR_OF_DAY) +
+ calendar.get(Calendar.DAY_OF_YEAR) * 24;
int year = calendar.get(Calendar.YEAR);
hoursOfYear = (hoursOfYear / numHoursWide) * numHoursWide;
calendar.clear();
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncPerf.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncPerf.java
new file mode 100644
index 0000000..9ce7045
--- /dev/null
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncPerf.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmuilib.log.event;
+
+import com.android.ddmlib.log.EventContainer;
+import com.android.ddmlib.log.EventLogParser;
+import com.android.ddmlib.log.InvalidTypeException;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.jfree.chart.labels.CustomXYToolTipGenerator;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYBarRenderer;
+import org.jfree.data.time.SimpleTimePeriod;
+import org.jfree.data.time.TimePeriodValues;
+import org.jfree.data.time.TimePeriodValuesCollection;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DisplaySyncPerf extends SyncCommon {
+
+ CustomXYToolTipGenerator mTooltipGenerator;
+ List mTooltips[];
+
+ // The series number for each graphed item.
+ // sync authorities are 0-3
+ private static final int DB_QUERY = 4;
+ private static final int DB_WRITE = 5;
+ private static final int HTTP_NETWORK = 6;
+ private static final int HTTP_PROCESSING = 7;
+ private static final int NUM_SERIES = (HTTP_PROCESSING + 1);
+ private static final String SERIES_NAMES[] = {"Calendar", "Gmail", "Feeds", "Contacts",
+ "DB Query", "DB Write", "HTTP Response", "HTTP Processing",};
+ private static final Color SERIES_COLORS[] = {Color.MAGENTA, Color.GREEN, Color.BLUE,
+ Color.ORANGE, Color.RED, Color.CYAN, Color.PINK, Color.DARK_GRAY};
+ private static final double SERIES_YCOORD[] = {0, 0, 0, 0, 1, 1, 2, 2};
+
+ // Values from data/etc/event-log-tags
+ private static final int EVENT_DB_OPERATION = 52000;
+ private static final int EVENT_HTTP_STATS = 52001;
+ // op types for EVENT_DB_OPERATION
+ final int EVENT_DB_QUERY = 0;
+ final int EVENT_DB_WRITE = 1;
+
+ // Information to graph for each authority
+ private TimePeriodValues mDatasets[];
+
+ /**
+ * TimePeriodValuesCollection that supports Y intervals. This allows the
+ * creation of "floating" bars, rather than bars rooted to the axis.
+ */
+ class YIntervalTimePeriodValuesCollection extends TimePeriodValuesCollection {
+ /** default serial UID */
+ private static final long serialVersionUID = 1L;
+
+ private double yheight;
+
+ /**
+ * Constructs a collection of bars with a fixed Y height.
+ *
+ * @param yheight The height of the bars.
+ */
+ YIntervalTimePeriodValuesCollection(double yheight) {
+ this.yheight = yheight;
+ }
+
+ /**
+ * Returns ending Y value that is a fixed amount greater than the starting value.
+ *
+ * @param series the series (zero-based index).
+ * @param item the item (zero-based index).
+ * @return The ending Y value for the specified series and item.
+ */
+ @Override
+ public Number getEndY(int series, int item) {
+ return getY(series, item).doubleValue() + yheight;
+ }
+ }
+
+ /**
+ * Constructs a graph of network and database stats.
+ *
+ * @param name The name of this graph in the graph list.
+ */
+ public DisplaySyncPerf(String name) {
+ super(name);
+ }
+
+ /**
+ * Creates the UI for the event display.
+ *
+ * @param parent the parent composite.
+ * @param logParser the current log parser.
+ * @return the created control (which may have children).
+ */
+ @Override
+ public Control createComposite(final Composite parent, EventLogParser logParser,
+ final ILogColumnListener listener) {
+ Control composite = createCompositeChart(parent, logParser, "Sync Performance");
+ resetUI();
+ return composite;
+ }
+
+ /**
+ * Resets the display.
+ */
+ @Override
+ void resetUI() {
+ super.resetUI();
+ XYPlot xyPlot = mChart.getXYPlot();
+ xyPlot.getRangeAxis().setVisible(false);
+ mTooltipGenerator = new CustomXYToolTipGenerator();
+ mTooltips = new List[NUM_SERIES];
+
+ XYBarRenderer br = new XYBarRenderer();
+ br.setUseYInterval(true);
+ mDatasets = new TimePeriodValues[NUM_SERIES];
+
+ TimePeriodValuesCollection tpvc = new YIntervalTimePeriodValuesCollection(1);
+ xyPlot.setDataset(tpvc);
+ xyPlot.setRenderer(br);
+
+ for (int i = 0; i < NUM_SERIES; i++) {
+ br.setSeriesPaint(i, SERIES_COLORS[i]);
+ mDatasets[i] = new TimePeriodValues(SERIES_NAMES[i]);
+ tpvc.addSeries(mDatasets[i]);
+ mTooltips[i] = new ArrayList<String>();
+ mTooltipGenerator.addToolTipSeries(mTooltips[i]);
+ br.setSeriesToolTipGenerator(i, mTooltipGenerator);
+ }
+ }
+
+ /**
+ * Updates the display with a new event.
+ *
+ * @param event The event
+ * @param logParser The parser providing the event.
+ */
+ @Override
+ void newEvent(EventContainer event, EventLogParser logParser) {
+ super.newEvent(event, logParser); // Handle sync operation
+ try {
+ if (event.mTag == EVENT_DB_OPERATION) {
+ // 52000 db_operation (name|3),(op_type|1|5),(time|2|3)
+ String tip = event.getValueAsString(0);
+ long endTime = (long) event.sec * 1000L + (event.nsec / 1000000L);
+ int opType = Integer.parseInt(event.getValueAsString(1));
+ long duration = Long.parseLong(event.getValueAsString(2));
+
+ if (opType == EVENT_DB_QUERY) {
+ mDatasets[DB_QUERY].add(new SimpleTimePeriod(endTime - duration, endTime),
+ SERIES_YCOORD[DB_QUERY]);
+ mTooltips[DB_QUERY].add(tip);
+ } else if (opType == EVENT_DB_WRITE) {
+ mDatasets[DB_WRITE].add(new SimpleTimePeriod(endTime - duration, endTime),
+ SERIES_YCOORD[DB_WRITE]);
+ mTooltips[DB_WRITE].add(tip);
+ }
+ } else if (event.mTag == EVENT_HTTP_STATS) {
+ // 52001 http_stats (useragent|3),(response|2|3),(processing|2|3),(tx|1|2),(rx|1|2)
+ String tip = event.getValueAsString(0) + ", tx:" + event.getValueAsString(3) +
+ ", rx: " + event.getValueAsString(4);
+ long endTime = (long) event.sec * 1000L + (event.nsec / 1000000L);
+ long netEndTime = endTime - Long.parseLong(event.getValueAsString(2));
+ long netStartTime = netEndTime - Long.parseLong(event.getValueAsString(1));
+ mDatasets[HTTP_NETWORK].add(new SimpleTimePeriod(netStartTime, netEndTime),
+ SERIES_YCOORD[HTTP_NETWORK]);
+ mDatasets[HTTP_PROCESSING].add(new SimpleTimePeriod(netEndTime, endTime),
+ SERIES_YCOORD[HTTP_PROCESSING]);
+ mTooltips[HTTP_NETWORK].add(tip);
+ mTooltips[HTTP_PROCESSING].add(tip);
+ }
+ } catch (InvalidTypeException e) {
+ }
+ }
+
+ /**
+ * Callback from super.newEvent to process a sync event.
+ *
+ * @param event The sync event
+ * @param startTime Start time (ms) of events
+ * @param stopTime Stop time (ms) of events
+ * @param details Details associated with the event.
+ * @param newEvent True if this event is a new sync event. False if this event
+ * @param syncSource
+ */
+ @Override
+ void processSyncEvent(EventContainer event, int auth, long startTime, long stopTime,
+ String details, boolean newEvent, int syncSource) {
+ if (newEvent) {
+ mDatasets[auth].add(new SimpleTimePeriod(startTime, stopTime), SERIES_YCOORD[auth]);
+ }
+ }
+
+ /**
+ * Gets display type
+ *
+ * @return display type as an integer
+ */
+ @Override
+ int getDisplayType() {
+ return DISPLAY_TYPE_SYNC_PERF;
+ }
+}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java
index bbd3e1b..2223a4d 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java
@@ -45,7 +45,6 @@ import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.experimental.chart.swt.ChartComposite;
import org.jfree.experimental.swt.SWTUtils;
-import java.awt.Color;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
@@ -71,27 +70,12 @@ abstract class EventDisplay {
public final static int DISPLAY_TYPE_GRAPH = 2;
public final static int DISPLAY_TYPE_SYNC = 3;
public final static int DISPLAY_TYPE_SYNC_HIST = 4;
+ public final static int DISPLAY_TYPE_SYNC_PERF = 5;
private final static int EVENT_CHECK_FAILED = 0;
protected final static int EVENT_CHECK_SAME_TAG = 1;
protected final static int EVENT_CHECK_SAME_VALUE = 2;
- // Some common variables for sync display. These define the sync backends
- //and how they should be displayed.
- protected static final int CALENDAR = 0;
- protected static final int GMAIL = 1;
- protected static final int FEEDS = 2;
- protected static final int CONTACTS = 3;
- protected static final int ERRORS = 4;
- protected static final int NUM_AUTHS = (CONTACTS + 1);
- protected static final String AUTH_NAMES[] = {"Calendar", "Gmail", "Feeds", "Contacts", "Errors"};
- protected static final Color AUTH_COLORS[] = {Color.MAGENTA, Color.GREEN, Color.BLUE, Color.ORANGE, Color.RED};
-
- // Values from data/etc/event-log-tags
- final int EVENT_SYNC = 2720;
- final int EVENT_TICKLE = 2742;
- final int EVENT_SYNC_DETAILS = 2743;
-
/**
* Creates the appropriate EventDisplay subclass.
*
@@ -111,8 +95,10 @@ abstract class EventDisplay {
return new DisplaySyncHistogram(name);
case DISPLAY_TYPE_GRAPH:
return new DisplayGraph(name);
+ case DISPLAY_TYPE_SYNC_PERF:
+ return new DisplaySyncPerf(name);
default:
- throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$
+ throw new InvalidParameterException("Unknown Display Type " + type); //$NON-NLS-1$
}
}
@@ -982,28 +968,4 @@ abstract class EventDisplay {
long getHistWidth() {
return mHistWidth;
}
-
- /**
- * Convert authority name to auth number.
- *
- * @param authname "calendar", etc.
- * @return number series number associated with the authority
- */
- protected int getAuth(String authname) throws InvalidTypeException {
- if ("calendar".equals(authname) || "cl".equals(authname)) {
- return CALENDAR;
- } else if ("contacts".equals(authname) || "cp".equals(authname)) {
- return CONTACTS;
- } else if ("subscribedfeeds".equals(authname)) {
- return FEEDS;
- } else if ("gmail-ls".equals(authname) || "mail".equals(authname)) {
- return GMAIL;
- } else if ("gmail-live".equals(authname)) {
- return GMAIL;
- } else if ("unknown".equals(authname)) {
- return -1; // Unknown tickles; discard
- } else {
- throw new InvalidTypeException("Unknown authname " + authname);
- }
- }
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java
index b9daa41..88c3cb2 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java
@@ -438,7 +438,8 @@ class EventDisplayOptions extends Dialog {
mDisplayTypeCombo.add("Filtered Log");
mDisplayTypeCombo.add("Graph");
mDisplayTypeCombo.add("Sync");
- mDisplayTypeCombo.add("Sync histogram");
+ mDisplayTypeCombo.add("Sync Histogram");
+ mDisplayTypeCombo.add("Sync Performance");
mDisplayTypeCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/SyncCommon.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/SyncCommon.java
new file mode 100644
index 0000000..108c097
--- /dev/null
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/SyncCommon.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmuilib.log.event;
+
+import com.android.ddmlib.log.EventContainer;
+import com.android.ddmlib.log.EventLogParser;
+import com.android.ddmlib.log.InvalidTypeException;
+
+import java.awt.Color;
+
+abstract public class SyncCommon extends EventDisplay {
+
+ // State information while processing the event stream
+ private int mLastState; // 0 if event started, 1 if event stopped
+ private long mLastStartTime; // ms
+ private long mLastStopTime; //ms
+ private String mLastDetails;
+ private int mLastSyncSource; // poll, server, user, etc.
+
+ // Some common variables for sync display. These define the sync backends
+ //and how they should be displayed.
+ protected static final int CALENDAR = 0;
+ protected static final int GMAIL = 1;
+ protected static final int FEEDS = 2;
+ protected static final int CONTACTS = 3;
+ protected static final int ERRORS = 4;
+ protected static final int NUM_AUTHS = (CONTACTS + 1);
+ protected static final String AUTH_NAMES[] = {"Calendar", "Gmail", "Feeds", "Contacts",
+ "Errors"};
+ protected static final Color AUTH_COLORS[] = {Color.MAGENTA, Color.GREEN, Color.BLUE,
+ Color.ORANGE, Color.RED};
+
+ // Values from data/etc/event-log-tags
+ final int EVENT_SYNC = 2720;
+ final int EVENT_TICKLE = 2742;
+ final int EVENT_SYNC_DETAILS = 2743;
+
+ protected SyncCommon(String name) {
+ super(name);
+ }
+
+ /**
+ * Resets the display.
+ */
+ @Override
+ void resetUI() {
+ mLastStartTime = 0;
+ mLastStopTime = 0;
+ mLastState = -1;
+ mLastSyncSource = -1;
+ mLastDetails = "";
+ }
+
+ /**
+ * Updates the display with a new event. This is the main entry point for
+ * each event. This method has the logic to tie together the start event,
+ * stop event, and details event into one graph item. The combined sync event
+ * is handed to the subclass via processSycnEvent. Note that the details
+ * can happen before or after the stop event.
+ *
+ * @param event The event
+ * @param logParser The parser providing the event.
+ */
+ @Override
+ void newEvent(EventContainer event, EventLogParser logParser) {
+ try {
+ if (event.mTag == EVENT_SYNC) {
+ int state = Integer.parseInt(event.getValueAsString(1));
+ if (state == 0) { // start
+ mLastStartTime = (long) event.sec * 1000L + (event.nsec / 1000000L);
+ mLastState = 0;
+ mLastSyncSource = Integer.parseInt(event.getValueAsString(2));
+ mLastDetails = "";
+ } else if (state == 1) { // stop
+ if (mLastState == 0) {
+ mLastStopTime = (long) event.sec * 1000L + (event.nsec / 1000000L);
+ if (mLastStartTime == 0) {
+ // Log starts with a stop event
+ mLastStartTime = mLastStopTime;
+ }
+ int auth = getAuth(event.getValueAsString(0));
+ processSyncEvent(event, auth, mLastStartTime, mLastStopTime, mLastDetails,
+ true, mLastSyncSource);
+ mLastState = 1;
+ }
+ }
+ } else if (event.mTag == EVENT_SYNC_DETAILS) {
+ mLastDetails = event.getValueAsString(3);
+ if (mLastState != 0) { // Not inside event
+ long updateTime = (long) event.sec * 1000L + (event.nsec / 1000000L);
+ if (updateTime - mLastStopTime <= 250) {
+ // Got details within 250ms after event, so delete and re-insert
+ // Details later than 250ms (arbitrary) are discarded as probably
+ // unrelated.
+ int auth = getAuth(event.getValueAsString(0));
+ processSyncEvent(event, auth, mLastStartTime, mLastStopTime, mLastDetails,
+ false, mLastSyncSource);
+ }
+ }
+ }
+ } catch (InvalidTypeException e) {
+ }
+ }
+
+ /**
+ * Callback hook for subclass to process a sync event. newEvent has the logic
+ * to combine start and stop events and passes a processed event to the
+ * subclass.
+ *
+ * @param event The sync event
+ * @param auth The sync authority
+ * @param startTime Start time (ms) of events
+ * @param stopTime Stop time (ms) of events
+ * @param details Details associated with the event.
+ * @param newEvent True if this event is a new sync event. False if this event
+ * @param syncSource Poll, user, server, etc.
+ */
+ abstract void processSyncEvent(EventContainer event, int auth, long startTime, long stopTime,
+ String details, boolean newEvent, int syncSource);
+
+ /**
+ * Converts authority name to auth number.
+ *
+ * @param authname "calendar", etc.
+ * @return number series number associated with the authority
+ */
+ protected int getAuth(String authname) throws InvalidTypeException {
+ if ("calendar".equals(authname) || "cl".equals(authname)) {
+ return CALENDAR;
+ } else if ("contacts".equals(authname) || "cp".equals(authname)) {
+ return CONTACTS;
+ } else if ("subscribedfeeds".equals(authname)) {
+ return FEEDS;
+ } else if ("gmail-ls".equals(authname) || "mail".equals(authname)) {
+ return GMAIL;
+ } else if ("gmail-live".equals(authname)) {
+ return GMAIL;
+ } else if ("unknown".equals(authname)) {
+ return -1; // Unknown tickles; discard
+ } else {
+ throw new InvalidTypeException("Unknown authname " + authname);
+ }
+ }
+}
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()));
diff --git a/scripts/alias_rules.xml b/scripts/alias_rules.xml
index 0443193..bc7de7c 100644
--- a/scripts/alias_rules.xml
+++ b/scripts/alias_rules.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" ?>
+<?xml version="1.0" encoding="UTF-8"?>
<project name="alias_rules" default="package">
<!-- No user servicable parts below. -->
diff --git a/scripts/android_rules.xml b/scripts/android_rules.xml
index ce34e28..8e4f7a2 100644
--- a/scripts/android_rules.xml
+++ b/scripts/android_rules.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" ?>
+<?xml version="1.0" encoding="UTF-8"?>
<project name="android_rules" default="debug">
<property name="android-tools" value="${sdk-location}/tools" />
diff --git a/scripts/build.alias.template b/scripts/build.alias.template
index f7de2e8..b605295 100644
--- a/scripts/build.alias.template
+++ b/scripts/build.alias.template
@@ -1,4 +1,4 @@
-<?xml version="1.0" ?>
+<?xml version="1.0" encoding="UTF-8"?>
<project name="PROJECT_NAME" default="package">
<!-- The build.properties file can be created by you and is never touched
diff --git a/scripts/build.template b/scripts/build.template
index 350d0f4..1f7f908 100644
--- a/scripts/build.template
+++ b/scripts/build.template
@@ -1,4 +1,4 @@
-<?xml version="1.0" ?>
+<?xml version="1.0" encoding="UTF-8"?>
<project name="PROJECT_NAME" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
diff --git a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
index f1531e0..9f3fb99 100644
--- a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
+++ b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
@@ -128,15 +128,49 @@ public class CommandLineProcessor {
/**
* Raw access to parsed parameter values.
- * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
- * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
- * @param longFlagName The long flag name for the given action.
+ * <p/>
+ * The default is to scan all parameters. Parameters that have been explicitly set on the
+ * command line are returned first. Otherwise one with a non-null value is returned.
+ * <p/>
+ * Both a verb and a direct object filter can be specified. When they are non-null they limit
+ * the scope of the search.
+ * <p/>
+ * If nothing has been found, return the last default value seen matching the filter.
+ *
+ * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible
+ * verbs that match the direct object condition will be examined and the first
+ * value set will be used.
+ * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null,
+ * all possible direct objects that match the verb condition will be examined and
+ * the first value set will be used.
+ * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null.
* @return The current value object stored in the parameter, which depends on the argument mode.
*/
public Object getValue(String verb, String directObject, String longFlagName) {
- String key = verb + "/" + directObject + "/" + longFlagName;
- Arg arg = mArguments.get(key);
- return arg.getCurrentValue();
+
+ if (verb != null && directObject != null) {
+ String key = verb + "/" + directObject + "/" + longFlagName;
+ Arg arg = mArguments.get(key);
+ return arg.getCurrentValue();
+ }
+
+ Object lastDefault = null;
+ for (Arg arg : mArguments.values()) {
+ if (arg.getLongArg().equals(longFlagName)) {
+ if (verb == null || arg.getVerb().equals(verb)) {
+ if (directObject == null || arg.getDirectObject().equals(directObject)) {
+ if (arg.isInCommandLine()) {
+ return arg.getCurrentValue();
+ }
+ if (arg.getCurrentValue() != null) {
+ lastDefault = arg.getCurrentValue();
+ }
+ }
+ }
+ }
+ }
+
+ return lastDefault;
}
/**
@@ -243,6 +277,9 @@ public class CommandLineProcessor {
}
}
} else if (arg != null) {
+ // This argument was present on the command line
+ arg.setInCommandLine(true);
+
// Process keyword
String error = null;
if (arg.getMode().needsExtra()) {
@@ -408,13 +445,15 @@ public class CommandLineProcessor {
"Global options:");
listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
- stdout("\nValid actions are composed of a verb and an optional direct object:");
- for (String[] action : mActions) {
-
- stdout("- %1$6s %2$-7s: %3$s",
- action[ACTION_VERB_INDEX],
- action[ACTION_OBJECT_INDEX],
- action[ACTION_DESC_INDEX]);
+ if (verb == null || directObject == null) {
+ stdout("\nValid actions are composed of a verb and an optional direct object:");
+ for (String[] action : mActions) {
+
+ stdout("- %1$6s %2$-7s: %3$s",
+ action[ACTION_VERB_INDEX],
+ action[ACTION_OBJECT_INDEX],
+ action[ACTION_DESC_INDEX]);
+ }
}
for (String[] action : mActions) {
@@ -575,15 +614,26 @@ public class CommandLineProcessor {
* or a String array (in which case the first item is the current by default.)
*/
static class Arg {
+ /** Verb for that argument. Never null. */
private final String mVerb;
+ /** Direct Object for that argument. Never null, but can be empty string. */
private final String mDirectObject;
+ /** The 1-letter short name of the argument, e.g. -v. */
private final String mShortName;
+ /** The long name of the argument, e.g. --verbose. */
private final String mLongName;
+ /** A description. Never null. */
private final String mDescription;
+ /** A default value. Can be null. */
private final Object mDefaultValue;
+ /** The argument mode (type + process method). Never null. */
private final MODE mMode;
+ /** True if this argument is mandatory for this verb/directobject. */
private final boolean mMandatory;
+ /** Current value. Initially set to the default value. */
private Object mCurrentValue;
+ /** True if the argument has been used on the command line. */
+ private boolean mInCommandLine;
/**
* Creates a new argument flag description.
@@ -612,6 +662,7 @@ public class CommandLineProcessor {
mLongName = longName;
mDescription = description;
mDefaultValue = defaultValue;
+ mInCommandLine = false;
if (defaultValue instanceof String[]) {
mCurrentValue = ((String[])defaultValue)[0];
} else {
@@ -619,45 +670,65 @@ public class CommandLineProcessor {
}
}
+ /** Return true if this argument is mandatory for this verb/directobject. */
public boolean isMandatory() {
return mMandatory;
}
+ /** Returns the 1-letter short name of the argument, e.g. -v. */
public String getShortArg() {
return mShortName;
}
+ /** Returns the long name of the argument, e.g. --verbose. */
public String getLongArg() {
return mLongName;
}
+ /** Returns the description. Never null. */
public String getDescription() {
return mDescription;
}
+ /** Returns the verb for that argument. Never null. */
public String getVerb() {
return mVerb;
}
+ /** Returns the direct Object for that argument. Never null, but can be empty string. */
public String getDirectObject() {
return mDirectObject;
}
+ /** Returns the default value. Can be null. */
public Object getDefaultValue() {
return mDefaultValue;
}
+ /** Returns the current value. Initially set to the default value. Can be null. */
public Object getCurrentValue() {
return mCurrentValue;
}
+ /** Sets the current value. Can be null. */
public void setCurrentValue(Object currentValue) {
mCurrentValue = currentValue;
}
+ /** Returns the argument mode (type + process method). Never null. */
public MODE getMode() {
return mMode;
}
+
+ /** Returns true if the argument has been used on the command line. */
+ public boolean isInCommandLine() {
+ return mInCommandLine;
+ }
+
+ /** Sets if the argument has been used on the command line. */
+ public void setInCommandLine(boolean inCommandLine) {
+ mInCommandLine = inCommandLine;
+ }
}
/**
diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java
index 7965e87..3370e76 100644
--- a/sdkmanager/app/src/com/android/sdkmanager/Main.java
+++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java
@@ -56,8 +56,6 @@ class Main {
private ISdkLog mSdkLog;
/** The SDK manager parses the SDK folder and gives access to the content. */
private SdkManager mSdkManager;
- /** Virtual Machine manager to access the list of AVDs or create new ones. */
- private AvdManager mAvdManager;
/** Command-line processor with options specific to SdkManager. */
private SdkCommandLine mSdkCommandLine;
/** The working directory, either null or set to an existing absolute canonical directory. */
@@ -201,63 +199,85 @@ class Main {
SdkCommandLine.OBJECT_AVD.equals(directObject)) {
createAvd();
+ } else if (SdkCommandLine.VERB_DELETE.equals(verb) &&
+ SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ deleteAvd();
+
+ } else if (SdkCommandLine.VERB_MOVE.equals(verb) &&
+ SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ moveAvd();
+
} else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
- // get the target and try to resolve it.
- int targetId = mSdkCommandLine.getCreateProjectTargetId();
- IAndroidTarget[] targets = mSdkManager.getTargets();
- if (targetId < 1 || targetId > targets.length) {
- errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
- SdkConstants.androidCmdName());
- }
- IAndroidTarget target = targets[targetId - 1];
-
- ProjectCreator creator = new ProjectCreator(mSdkFolder,
- mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
- mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
- OutputLevel.NORMAL,
- mSdkLog);
-
- String projectDir = getProjectLocation(mSdkCommandLine.getCreateProjectLocation());
-
- creator.createProject(projectDir,
- mSdkCommandLine.getCreateProjectName(),
- mSdkCommandLine.getCreateProjectPackage(),
- mSdkCommandLine.getCreateProjectActivity(),
- target,
- false /* isTestProject*/);
+ createProject();
} else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
- // get the target and try to resolve it.
- IAndroidTarget target = null;
- int targetId = mSdkCommandLine.getUpdateProjectTargetId();
- if (targetId >= 0) {
- IAndroidTarget[] targets = mSdkManager.getTargets();
- if (targetId < 1 || targetId > targets.length) {
- errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
- SdkConstants.androidCmdName());
- }
- target = targets[targetId - 1];
- }
-
- ProjectCreator creator = new ProjectCreator(mSdkFolder,
- mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
- mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
- OutputLevel.NORMAL,
- mSdkLog);
-
- String projectDir = getProjectLocation(mSdkCommandLine.getUpdateProjectLocation());
-
- creator.updateProject(projectDir,
- target,
- mSdkCommandLine.getUpdateProjectName());
+ updateProject();
} else {
mSdkCommandLine.printHelpAndExit(null);
}
}
/**
+ * Creates a new Android project based on command-line parameters
+ */
+ private void createProject() {
+ // get the target and try to resolve it.
+ int targetId = mSdkCommandLine.getParamTargetId();
+ IAndroidTarget[] targets = mSdkManager.getTargets();
+ if (targetId < 1 || targetId > targets.length) {
+ errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
+ SdkConstants.androidCmdName());
+ }
+ IAndroidTarget target = targets[targetId - 1];
+
+ ProjectCreator creator = new ProjectCreator(mSdkFolder,
+ mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
+ mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
+ OutputLevel.NORMAL,
+ mSdkLog);
+
+ String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
+
+ creator.createProject(projectDir,
+ mSdkCommandLine.getParamName(),
+ mSdkCommandLine.getParamProjectPackage(),
+ mSdkCommandLine.getParamProjectActivity(),
+ target,
+ false /* isTestProject*/);
+ }
+
+ /**
+ * Updates an existing Android project based on command-line parameters
+ */
+ private void updateProject() {
+ // get the target and try to resolve it.
+ IAndroidTarget target = null;
+ int targetId = mSdkCommandLine.getParamTargetId();
+ if (targetId >= 0) {
+ IAndroidTarget[] targets = mSdkManager.getTargets();
+ if (targetId < 1 || targetId > targets.length) {
+ errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
+ SdkConstants.androidCmdName());
+ }
+ target = targets[targetId - 1];
+ }
+
+ ProjectCreator creator = new ProjectCreator(mSdkFolder,
+ mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
+ mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
+ OutputLevel.NORMAL,
+ mSdkLog);
+
+ String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
+
+ creator.updateProject(projectDir,
+ target,
+ mSdkCommandLine.getParamName());
+ }
+
+ /**
* Adjusts the project location to make it absolute & canonical relative to the
* working directory, if any.
*
@@ -325,38 +345,45 @@ class Main {
}
// get the target skins
- String[] skins = target.getSkins();
- mSdkLog.printf(" Skins: ");
- if (skins != null) {
- boolean first = true;
- for (String skin : skins) {
- if (first == false) {
- mSdkLog.printf(", ");
- } else {
- first = false;
- }
- mSdkLog.printf(skin);
- }
- mSdkLog.printf("\n");
- } else {
- mSdkLog.printf("no skins.\n");
- }
+ displaySkinList(target, " Skins: ");
index++;
}
}
+
+ /**
+ * Displays the skins valid for the given target.
+ */
+ private void displaySkinList(IAndroidTarget target, String message) {
+ String[] skins = target.getSkins();
+ mSdkLog.printf(message);
+ if (skins != null) {
+ boolean first = true;
+ for (String skin : skins) {
+ if (first == false) {
+ mSdkLog.printf(", ");
+ } else {
+ first = false;
+ }
+ mSdkLog.printf(skin);
+ }
+ mSdkLog.printf("\n");
+ } else {
+ mSdkLog.printf("no skins.\n");
+ }
+ }
/**
* Displays the list of available AVDs.
*/
private void displayAvdList() {
try {
- mAvdManager = new AvdManager(mSdkManager, null /* sdklog */);
+ AvdManager avdManager = new AvdManager(mSdkManager, null /* sdklog */);
mSdkLog.printf("Available Android Virtual Devices:\n");
int index = 1;
- for (AvdInfo info : mAvdManager.getAvds()) {
+ for (AvdInfo info : avdManager.getAvds()) {
mSdkLog.printf("[%d] %s\n", index, info.getName());
mSdkLog.printf(" Path: %s\n", info.getPath());
@@ -384,7 +411,7 @@ class Main {
*/
private void createAvd() {
// find a matching target
- int targetId = mSdkCommandLine.getCreateAvdTargetId();
+ int targetId = mSdkCommandLine.getParamTargetId();
IAndroidTarget target = null;
if (targetId >= 1 && targetId <= mSdkManager.getTargets().length) {
@@ -396,12 +423,12 @@ class Main {
try {
boolean removePrevious = false;
- mAvdManager = new AvdManager(mSdkManager, mSdkLog);
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
- String avdName = mSdkCommandLine.getCreateAvdName();
- AvdInfo info = mAvdManager.getAvd(avdName);
+ String avdName = mSdkCommandLine.getParamName();
+ AvdInfo info = avdManager.getAvd(avdName);
if (info != null) {
- if (mSdkCommandLine.getCreateAvdForce()) {
+ if (mSdkCommandLine.getFlagForce()) {
removePrevious = true;
mSdkLog.warning(
"Android Virtual Device '%s' already exists and will be replaced.",
@@ -412,9 +439,13 @@ class Main {
}
}
- String avdParentFolder = mSdkCommandLine.getCreateAvdLocation();
- if (avdParentFolder == null) {
- avdParentFolder = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
+ String paramFolderPath = mSdkCommandLine.getParamLocationPath();
+ File avdFolder = null;
+ if (paramFolderPath != null) {
+ avdFolder = new File(paramFolderPath);
+ } else {
+ avdFolder = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ avdName + AvdManager.AVD_FOLDER_EXTENSION);
}
Map<String, String> hardwareConfig = null;
@@ -425,17 +456,45 @@ class Main {
errorAndExit(e.getMessage());
}
}
-
+
AvdInfo oldAvdInfo = null;
if (removePrevious) {
- oldAvdInfo = mAvdManager.getAvd(avdName);
+ oldAvdInfo = avdManager.getAvd(avdName);
+ }
+
+ // Validate skin is either default (empty) or NNNxMMM or a valid skin name.
+ String skin = mSdkCommandLine.getParamSkin();
+ if (skin != null && skin.length() == 0) {
+ skin = null;
+ }
+ if (skin != null) {
+ boolean valid = false;
+ // Is it a know skin name for this target?
+ for (String s : target.getSkins()) {
+ if (skin.equalsIgnoreCase(s)) {
+ skin = s; // Make skin names case-insensitive.
+ valid = true;
+ break;
+ }
+ }
+
+ // Is it NNNxMMM?
+ if (!valid) {
+ valid = skin.matches("[0-9]{2,}x[0-9]{2,}");
+ }
+
+ if (!valid) {
+ displaySkinList(target, "Valid skins: ");
+ errorAndExit("'%s' is not a valid skin name or size (NNNxMMM)", skin);
+ return;
+ }
}
- AvdInfo newAvdInfo = mAvdManager.createAvd(avdParentFolder,
+ AvdInfo newAvdInfo = avdManager.createAvd(avdFolder,
avdName,
target,
- mSdkCommandLine.getCreateAvdSkin(),
- mSdkCommandLine.getCreateAvdSdCard(),
+ skin,
+ mSdkCommandLine.getParamSdCard(),
hardwareConfig,
removePrevious,
mSdkLog);
@@ -446,10 +505,10 @@ class Main {
mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath());
// Remove the old data directory
File dir = new File(oldAvdInfo.getPath());
- mAvdManager.recursiveDelete(dir);
+ avdManager.recursiveDelete(dir);
dir.delete();
// Remove old avd info from manager
- mAvdManager.removeAvd(oldAvdInfo);
+ avdManager.removeAvd(oldAvdInfo);
}
} catch (AndroidLocationException e) {
@@ -458,6 +517,126 @@ class Main {
}
/**
+ * Delete an AVD.
+ */
+ private void deleteAvd() {
+ try {
+ String avdName = mSdkCommandLine.getParamName();
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+ AvdInfo info = avdManager.getAvd(avdName);
+
+ if (info == null) {
+ errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
+ return;
+ }
+
+ avdManager.deleteAvd(info, mSdkLog);
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Move an AVD.
+ */
+ private void moveAvd() {
+ try {
+ String avdName = mSdkCommandLine.getParamName();
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+ AvdInfo info = avdManager.getAvd(avdName);
+
+ if (info == null) {
+ errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
+ return;
+ }
+
+ // This is a rename if there's a new name for the AVD
+ String newName = mSdkCommandLine.getParamMoveNewName();
+ if (newName != null && newName.equals(info.getName())) {
+ // same name, not actually a rename operation
+ newName = null;
+ }
+
+ // This is a move (of the data files) if there's a new location path
+ String paramFolderPath = mSdkCommandLine.getParamLocationPath();
+ if (paramFolderPath != null) {
+ // check if paths are the same. Use File methods to account for OS idiosyncrasies.
+ try {
+ File f1 = new File(paramFolderPath).getCanonicalFile();
+ File f2 = new File(info.getPath()).getCanonicalFile();
+ if (f1.equals(f2)) {
+ // same canonical path, so not actually a move
+ paramFolderPath = null;
+ }
+ } catch (IOException e) {
+ // Fail to resolve canonical path. Fail now since a move operation might fail
+ // later and be harder to recover from.
+ errorAndExit(e.getMessage());
+ return;
+ }
+ }
+
+ if (newName == null && paramFolderPath == null) {
+ mSdkLog.warning("Move operation aborted: same AVD name, same canonical data path");
+ return;
+ }
+
+ // If a rename was requested and no data move was requested, check if the original
+ // data path is our default constructed from the AVD name. In this case we still want
+ // to rename that folder too.
+ if (newName != null && paramFolderPath == null) {
+ // Compute the original data path
+ File originalFolder = new File(
+ AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ info.getName() + AvdManager.AVD_FOLDER_EXTENSION);
+ if (originalFolder.equals(info.getPath())) {
+ try {
+ // The AVD is using the default data folder path based on the AVD name.
+ // That folder needs to be adjusted to use the new name.
+ File f = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ newName + AvdManager.AVD_FOLDER_EXTENSION);
+ paramFolderPath = f.getCanonicalPath();
+ } catch (IOException e) {
+ // Fail to resolve canonical path. Fail now rather than later.
+ errorAndExit(e.getMessage());
+ }
+ }
+ }
+
+ // Check for conflicts
+
+ if (newName != null && avdManager.getAvd(newName) != null) {
+ errorAndExit("There is already an AVD named '%s'.", newName);
+ return;
+ }
+ if (newName != null) {
+ if (avdManager.getAvd(newName) != null) {
+ errorAndExit("There is already an AVD named '%s'.", newName);
+ return;
+ }
+
+ File ini = info.getIniFile();
+ if (ini.equals(AvdInfo.getIniFile(newName))) {
+ errorAndExit("The AVD file '%s' is in the way.", ini.getCanonicalPath());
+ return;
+ }
+ }
+
+ if (paramFolderPath != null && new File(paramFolderPath).exists()) {
+ errorAndExit(
+ "There is already a file or directory at '%s'.\nUse --path to specify a different data folder.",
+ paramFolderPath);
+ }
+
+ avdManager.moveAvd(info, newName, paramFolderPath, mSdkLog);
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ } catch (IOException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
* Prompts the user to setup a hardware config for a Platform-based AVD.
* @throws IOException
*/
diff --git a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
index fe93396..34a69bd 100644
--- a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
+++ b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
@@ -27,7 +27,6 @@ public class SdkCommandLine extends CommandLineProcessor {
public final static String VERB_LIST = "list";
public final static String VERB_CREATE = "create";
- public final static String VERB_RENAME = "rename";
public final static String VERB_MOVE = "move";
public final static String VERB_DELETE = "delete";
public final static String VERB_UPDATE = "update";
@@ -51,6 +50,7 @@ public class SdkCommandLine extends CommandLineProcessor {
public static final String KEY_SKIN = "skin";
public static final String KEY_SDCARD = "sdcard";
public static final String KEY_FORCE = "force";
+ public static final String KEY_RENAME = "rename";
/**
* Action definitions for SdkManager command line.
@@ -64,92 +64,94 @@ public class SdkCommandLine extends CommandLineProcessor {
* </ul>
*/
private final static String[][] ACTIONS = {
- { VERB_LIST,
- NO_VERB_OBJECT,
+ { VERB_LIST, NO_VERB_OBJECT,
"Lists existing targets or virtual devices." },
- { VERB_LIST,
- OBJECT_AVD,
+ { VERB_LIST, OBJECT_AVD,
"Lists existing Android Virtual Devices.",
OBJECT_AVDS },
- { VERB_LIST,
- OBJECT_TARGET,
+ { VERB_LIST, OBJECT_TARGET,
"Lists existing targets.",
OBJECT_TARGETS },
- { VERB_CREATE,
- OBJECT_AVD,
+ { VERB_CREATE, OBJECT_AVD,
"Creates a new Android Virtual Device." },
- { VERB_RENAME,
- OBJECT_AVD,
- "Renames a new Android Virtual Device." },
- { VERB_MOVE,
- OBJECT_AVD,
- "Moves a new Android Virtual Device." },
- { VERB_DELETE,
- OBJECT_AVD,
- "Deletes a new Android Virtual Device." },
+ { VERB_MOVE, OBJECT_AVD,
+ "Moves or renames an Android Virtual Device." },
+ { VERB_DELETE, OBJECT_AVD,
+ "Deletes an Android Virtual Device." },
- { VERB_CREATE,
- OBJECT_PROJECT,
+ { VERB_CREATE, OBJECT_PROJECT,
"Creates a new Android Project." },
- { VERB_UPDATE,
- OBJECT_PROJECT,
+ { VERB_UPDATE, OBJECT_PROJECT,
"Updates an Android Project (must have an AndroidManifest.xml)." },
};
public SdkCommandLine(ISdkLog logger) {
super(logger, ACTIONS);
+ // --- create avd ---
+
define(MODE.STRING, false,
- VERB_CREATE, OBJECT_AVD,
- "p", KEY_PATH,
- "Location path of the parent directory where the new AVD will be created", null);
+ VERB_CREATE, OBJECT_AVD, "p", KEY_PATH,
+ "Location path of the directory where the new AVD will be created", null);
define(MODE.STRING, true,
- VERB_CREATE, OBJECT_AVD,
- "n", KEY_NAME,
+ VERB_CREATE, OBJECT_AVD, "n", KEY_NAME,
"Name of the new AVD", null);
define(MODE.INTEGER, true,
- VERB_CREATE, OBJECT_AVD,
- "t", KEY_TARGET_ID,
+ VERB_CREATE, OBJECT_AVD, "t", KEY_TARGET_ID,
"Target id of the new AVD", null);
- define(MODE.STRING, true,
- VERB_CREATE, OBJECT_AVD,
- "s", KEY_SKIN,
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_AVD, "s", KEY_SKIN,
"Skin of the new AVD", null);
define(MODE.STRING, false,
- VERB_CREATE, OBJECT_AVD,
- "c", KEY_SDCARD,
+ VERB_CREATE, OBJECT_AVD, "c", KEY_SDCARD,
"Path to a shared SD card image, or size of a new sdcard for the new AVD", null);
define(MODE.BOOLEAN, false,
- VERB_CREATE, OBJECT_AVD,
- "f", KEY_FORCE,
+ VERB_CREATE, OBJECT_AVD, "f", KEY_FORCE,
"Force creation (override an existing AVD)", false);
+ // --- delete avd ---
+
+ define(MODE.STRING, true,
+ VERB_DELETE, OBJECT_AVD, "n", KEY_NAME,
+ "Name of the AVD to delete", null);
+
+ // --- move avd ---
+
+ define(MODE.STRING, true,
+ VERB_MOVE, OBJECT_AVD, "n", KEY_NAME,
+ "Name of the AVD to move or rename", null);
+ define(MODE.STRING, false,
+ VERB_MOVE, OBJECT_AVD, "r", KEY_RENAME,
+ "New name of the AVD to rename", null);
+ define(MODE.STRING, false,
+ VERB_MOVE, OBJECT_AVD, "p", KEY_PATH,
+ "New location path of the directory where to move the AVD", null);
+
+ // --- create project ---
+
define(MODE.ENUM, true,
- VERB_CREATE, OBJECT_PROJECT,
- "m", KEY_MODE,
+ VERB_CREATE, OBJECT_PROJECT, "m", KEY_MODE,
"Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS });
define(MODE.STRING, true,
VERB_CREATE, OBJECT_PROJECT,
"p", KEY_PATH,
"Location path of new project", null);
define(MODE.INTEGER, true,
- VERB_CREATE, OBJECT_PROJECT,
- "t", KEY_TARGET_ID,
+ VERB_CREATE, OBJECT_PROJECT, "t", KEY_TARGET_ID,
"Target id of the new project", null);
define(MODE.STRING, true,
- VERB_CREATE, OBJECT_PROJECT,
- "k", KEY_PACKAGE,
+ VERB_CREATE, OBJECT_PROJECT, "k", KEY_PACKAGE,
"Package name", null);
define(MODE.STRING, true,
- VERB_CREATE, OBJECT_PROJECT,
- "a", KEY_ACTIVITY,
+ VERB_CREATE, OBJECT_PROJECT, "a", KEY_ACTIVITY,
"Activity name", null);
define(MODE.STRING, false,
- VERB_CREATE, OBJECT_PROJECT,
- "n", KEY_NAME,
+ VERB_CREATE, OBJECT_PROJECT, "n", KEY_NAME,
"Project name", null);
+ // --- update project ---
+
define(MODE.STRING, true,
VERB_UPDATE, OBJECT_PROJECT,
"p", KEY_PATH,
@@ -164,79 +166,55 @@ public class SdkCommandLine extends CommandLineProcessor {
"Project name", null);
}
- // -- some helpers for AVD action flags
+ // -- some helpers for generic action flags
- /** Helper to retrieve the --out location for the new AVD action. */
- public String getCreateAvdLocation() {
- return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_PATH));
+ /** Helper to retrieve the --path value. */
+ public String getParamLocationPath() {
+ return ((String) getValue(null, null, KEY_PATH));
}
- /** Helper to retrieve the --target id for the new AVD action. */
- public int getCreateAvdTargetId() {
- return ((Integer) getValue(VERB_CREATE, OBJECT_AVD, KEY_TARGET_ID)).intValue();
+ /** Helper to retrieve the --target id value. */
+ public int getParamTargetId() {
+ return ((Integer) getValue(null, null, KEY_TARGET_ID)).intValue();
}
- /** Helper to retrieve the --name for the new AVD action. */
- public String getCreateAvdName() {
- return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_NAME));
+ /** Helper to retrieve the --name value. */
+ public String getParamName() {
+ return ((String) getValue(null, null, KEY_NAME));
}
- /** Helper to retrieve the --skin name for the new AVD action. */
- public String getCreateAvdSkin() {
- return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_SKIN));
+ /** Helper to retrieve the --skin value. */
+ public String getParamSkin() {
+ return ((String) getValue(null, null, KEY_SKIN));
}
- /** Helper to retrieve the --sdcard data for the new AVD action. */
- public String getCreateAvdSdCard() {
- return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_SDCARD));
+ /** Helper to retrieve the --sdcard value. */
+ public String getParamSdCard() {
+ return ((String) getValue(null, null, KEY_SDCARD));
}
- public boolean getCreateAvdForce() {
- return ((Boolean) getValue(VERB_CREATE, OBJECT_AVD, KEY_FORCE)).booleanValue();
+ /** Helper to retrieve the --force flag. */
+ public boolean getFlagForce() {
+ return ((Boolean) getValue(null, null, KEY_FORCE)).booleanValue();
}
+ // -- some helpers for avd action flags
- // -- some helpers for project action flags
-
- /** Helper to retrieve the --out location for the new project action. */
- public String getCreateProjectLocation() {
- return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_PATH));
- }
-
- /** Helper to retrieve the --target id for the new project action. */
- public int getCreateProjectTargetId() {
- return ((Integer) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_TARGET_ID)).intValue();
+ /** Helper to retrieve the --rename value for a move verb. */
+ public String getParamMoveNewName() {
+ return ((String) getValue(VERB_MOVE, null, KEY_RENAME));
}
- /** Helper to retrieve the --name for the new project action. */
- public String getCreateProjectName() {
- return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_NAME));
- }
- /** Helper to retrieve the --package for the new project action. */
- public String getCreateProjectPackage() {
- return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_PACKAGE));
- }
-
- /** Helper to retrieve the --activity for the new project action. */
- public String getCreateProjectActivity() {
- return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_ACTIVITY));
- }
-
- // -- some helpers for update action flags
-
- /** Helper to retrieve the --out location for the update project action. */
- public String getUpdateProjectLocation() {
- return ((String) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_PATH));
- }
+ // -- some helpers for project action flags
- /** Helper to retrieve the --target id for the update project action. */
- public int getUpdateProjectTargetId() {
- return ((Integer) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_TARGET_ID)).intValue();
+ /** Helper to retrieve the --package value. */
+ public String getParamProjectPackage() {
+ return ((String) getValue(null, OBJECT_PROJECT, KEY_PACKAGE));
}
- /** Helper to retrieve the --name for the update project action. */
- public String getUpdateProjectName() {
- return ((String) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_NAME));
+ /** Helper to retrieve the --activity for the new project action. */
+ public String getParamProjectActivity() {
+ return ((String) getValue(null, OBJECT_PROJECT, KEY_ACTIVITY));
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
index 83a90e6..f6fb622 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
@@ -377,9 +377,11 @@ public final class SdkManager {
* @return the map of (key,value) pairs, or null if the parsing failed.
*/
public static Map<String, String> parsePropertyFile(File buildProp, ISdkLog log) {
+ FileInputStream fis = null;
+ BufferedReader reader = null;
try {
- FileInputStream fis = new FileInputStream(buildProp);
- BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
+ fis = new FileInputStream(buildProp);
+ reader = new BufferedReader(new InputStreamReader(fis));
String line = null;
Map<String, String> map = new HashMap<String, String>();
@@ -407,6 +409,21 @@ public final class SdkManager {
log.warning("Error parsing '%1$s': %2$s.", buildProp.getAbsolutePath(),
e.getMessage());
}
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
}
return null;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
index a43b8e6..d466182 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
@@ -39,11 +39,11 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * Virtual Device Manager to access the list of AVDs or create new ones.
+ * Android Virtual Device Manager to manage AVDs.
*/
public final class AvdManager {
- private static final String AVD_FOLDER_EXTENSION = ".avd";
+ public static final String AVD_FOLDER_EXTENSION = ".avd";
private final static String AVD_INFO_PATH = "path";
private final static String AVD_INFO_TARGET = "target";
@@ -55,21 +55,50 @@ public final class AvdManager {
private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?");
+ /** An immutable structure describing an Android Virtual Device. */
public static final class AvdInfo {
- String name;
- String path;
- IAndroidTarget target;
+ private String mName;
+ private String mPath;
+ private IAndroidTarget mTarget;
+
+ /** Creates a new AVD info. Valures are immutable. */
+ public AvdInfo(String name, String path, IAndroidTarget target) {
+ mName = name;
+ mPath = path;
+ mTarget = target;
+ }
+ /** Returns the name of the AVD. */
public String getName() {
- return name;
+ return mName;
}
+ /** Returns the path of the AVD data directory. */
public String getPath() {
- return path;
+ return mPath;
}
+ /** Returns the target of the AVD. */
public IAndroidTarget getTarget() {
- return target;
+ return mTarget;
+ }
+
+ /**
+ * Helper method that returns the .ini {@link File} for a given AVD name.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ */
+ public static File getIniFile(String name) throws AndroidLocationException {
+ String avdRoot;
+ avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
+ return new File(avdRoot, name + ".ini");
+ }
+
+ /**
+ * Returns the .ini {@link File} for this AVD.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ */
+ public File getIniFile() throws AndroidLocationException {
+ return getIniFile(mName);
}
}
@@ -97,7 +126,7 @@ public final class AvdManager {
*/
public AvdInfo getAvd(String name) {
for (AvdInfo info : mAvdList) {
- if (info.name.equals(name)) {
+ if (info.getName().equals(name)) {
return info;
}
}
@@ -107,30 +136,20 @@ public final class AvdManager {
/**
* Creates a new AVD. It is expected that there is no existing AVD with this name already.
- * @param parentFolder the folder to contain the AVD. A new folder will be created in this
- * folder with the name of the AVD
+ * @param avdFolder the data folder for the AVD. It will be created as needed.
* @param name the name of the AVD
* @param target the target of the AVD
- * @param skinName the name of the skin. Can be null.
+ * @param skinName the name of the skin. Can be null. Must have been verified by caller.
* @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
* an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
* @param hardwareConfig the hardware setup for the AVD
* @param removePrevious If true remove any previous files.
*/
- public AvdInfo createAvd(String parentFolder, String name, IAndroidTarget target,
+ public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target,
String skinName, String sdcard, Map<String,String> hardwareConfig,
boolean removePrevious, ISdkLog log) {
try {
- File rootDirectory = new File(parentFolder);
- if (rootDirectory.isDirectory() == false) {
- if (log != null) {
- log.error(null, "Folder %s does not exist.", parentFolder);
- }
- return null;
- }
-
- File avdFolder = new File(parentFolder, name + AVD_FOLDER_EXTENSION);
if (avdFolder.exists()) {
if (removePrevious) {
// AVD already exists and removePrevious is set, try to remove the
@@ -139,23 +158,19 @@ public final class AvdManager {
} else {
// AVD shouldn't already exist if removePrevious is false.
if (log != null) {
- log.error(null, "Folder %s is in the way.", avdFolder.getAbsolutePath());
+ log.error(null,
+ "Folder %s is in the way. Use --force if you want to overwrite.",
+ avdFolder.getAbsolutePath());
}
return null;
}
+ } else {
+ // create the AVD folder.
+ avdFolder.mkdir();
}
- // create the AVD folder.
- avdFolder.mkdir();
-
- HashMap<String, String> values = new HashMap<String, String>();
-
- // prepare the ini file.
- String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
- File iniFile = new File(avdRoot, name + ".ini");
- values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath());
- values.put(AVD_INFO_TARGET, target.hashString());
- createConfigIni(iniFile, values);
+ // actually write the ini file
+ createAvdIniFile(name, avdFolder, target);
// writes the userdata.img in it.
String imagePath = target.getPath(IAndroidTarget.IMAGES);
@@ -175,22 +190,10 @@ public final class AvdManager {
fis.close();
// Config file
- values.clear();
+ HashMap<String, String> values = new HashMap<String, String>();
if (skinName != null) {
- // check that the skin name is valid
- String[] skinNames = target.getSkins();
- boolean found = false;
- for (String n : skinNames) {
- if (n.equals(skinName)) {
- values.put("skin", skinName);
- found = true;
- break;
- }
- }
-
- if (found == false && log != null) {
- log.warning("Skin '%1$s' does not exists, using default skin.", skinName);
- }
+ // assume skin name is valid
+ values.put("skin", skinName);
}
if (sdcard != null) {
@@ -246,10 +249,7 @@ public final class AvdManager {
}
// create the AvdInfo object, and add it to the list
- AvdInfo avdInfo = new AvdInfo();
- avdInfo.name = name;
- avdInfo.path = avdFolder.getAbsolutePath();
- avdInfo.target = target;
+ AvdInfo avdInfo = new AvdInfo(name, avdFolder.getAbsolutePath(), target);
mAvdList.add(avdInfo);
@@ -268,6 +268,133 @@ public final class AvdManager {
}
/**
+ * Creates the ini file for an AVD.
+ *
+ * @param name of the AVD.
+ * @param avdFolder path for the data folder of the AVD.
+ * @param target of the AVD.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ * @throws IOException if {@link File#getAbsolutePath()} fails.
+ */
+ private void createAvdIniFile(String name, File avdFolder, IAndroidTarget target)
+ throws AndroidLocationException, IOException {
+ HashMap<String, String> values = new HashMap<String, String>();
+ File iniFile = AvdInfo.getIniFile(name);
+ values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath());
+ values.put(AVD_INFO_TARGET, target.hashString());
+ createConfigIni(iniFile, values);
+ }
+
+ /**
+ * Creates the ini file for an AVD.
+ *
+ * @param info of the AVD.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ * @throws IOException if {@link File#getAbsolutePath()} fails.
+ */
+ private void createAvdIniFile(AvdInfo info) throws AndroidLocationException, IOException {
+ createAvdIniFile(info.getName(), new File(info.getPath()), info.getTarget());
+ }
+
+ /**
+ * Actually deletes the files of an existing AVD.
+ * <p/>
+ * This also remove it from the manager's list, The caller does not need to
+ * call {@link #removeAvd(AvdInfo)} afterwards.
+ *
+ * @param avdInfo the information on the AVD to delete
+ */
+ public void deleteAvd(AvdInfo avdInfo, ISdkLog log) {
+ try {
+ String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
+
+ File f = avdInfo.getIniFile();
+ if (f.exists()) {
+ log.warning("Deleting file %s", f.getCanonicalPath());
+ if (!f.delete()) {
+ log.error(null, "Failed to delete %s", f.getCanonicalPath());
+ }
+ }
+
+ f = new File(avdInfo.getPath());
+ if (f.exists()) {
+ log.warning("Deleting folder %s", f.getCanonicalPath());
+ recursiveDelete(f);
+ if (!f.delete()) {
+ log.error(null, "Failed to delete %s", f.getCanonicalPath());
+ }
+ }
+
+ removeAvd(avdInfo);
+ } catch (AndroidLocationException e) {
+ log.error(e, null);
+ } catch (IOException e) {
+ log.error(e, null);
+ }
+ }
+
+ /**
+ * Moves and/or rename an existing AVD and its files.
+ * This also change it in the manager's list.
+ * <p/>
+ * The caller should make sure the name or path given are valid, do not exist and are
+ * actually different than current values.
+ *
+ * @param avdInfo the information on the AVD to move.
+ * @param newName the new name of the AVD if non null.
+ * @param paramFolderPath the new data folder if non null.
+ * @return True if the move succeeded or there was nothing to do.
+ * If false, this method will have had already output error in the log.
+ */
+ public boolean moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ISdkLog log) {
+
+ try {
+ if (paramFolderPath != null) {
+ File f = new File(avdInfo.getPath());
+ log.warning("Moving '%s' to '%s'.", avdInfo.getPath(), paramFolderPath);
+ if (!f.renameTo(new File(paramFolderPath))) {
+ log.error(null, "Failed to move '%s' to '%s'.",
+ avdInfo.getPath(), paramFolderPath);
+ return false;
+ }
+
+ // update avd info
+ AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath, avdInfo.getTarget());
+ mAvdList.remove(avdInfo);
+ mAvdList.add(info);
+ avdInfo = info;
+
+ // update the ini file
+ createAvdIniFile(avdInfo);
+ }
+
+ if (newName != null) {
+ File oldIniFile = avdInfo.getIniFile();
+ File newIniFile = AvdInfo.getIniFile(newName);
+
+ log.warning("Moving '%s' to '%s'.", oldIniFile.getPath(), newIniFile.getPath());
+ if (!oldIniFile.renameTo(newIniFile)) {
+ log.error(null, "Failed to move '%s' to '%s'.",
+ oldIniFile.getPath(), newIniFile.getPath());
+ return false;
+ }
+
+ // update avd info
+ AvdInfo info = new AvdInfo(newName, avdInfo.getPath(), avdInfo.getTarget());
+ mAvdList.remove(avdInfo);
+ mAvdList.add(info);
+ }
+ } catch (AndroidLocationException e) {
+ log.error(e, null);
+ } catch (IOException e) {
+ log.error(e, null);
+ }
+
+ // nothing to do or succeeded
+ return true;
+ }
+
+ /**
* Helper method to recursively delete a folder's content (but not the folder itself).
*
* @throws SecurityException like {@link File#delete()} does if file/folder is not writable.
@@ -332,15 +459,13 @@ public final class AvdManager {
return null;
}
- AvdInfo info = new AvdInfo();
Matcher matcher = INI_NAME_PATTERN.matcher(path.getName());
- if (matcher.matches()) {
- info.name = matcher.group(1);
- } else {
- info.name = path.getName(); // really this should not happen.
- }
- info.path = avdPath;
- info.target = target;
+
+ AvdInfo info = new AvdInfo(
+ matcher.matches() ? matcher.group(1) : path.getName(), // should not happen
+ avdPath,
+ target
+ );
return info;
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
index 4a0ee06..9d0b928 100644
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
@@ -56,14 +56,18 @@ public final class AvdSelector {
private Label mDescription;
/**
- * Creates a new SDK Target Selector.
+ * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
+ * by a {@link IAndroidTarget}.
+ * <p/>Only the {@link AvdInfo} able to run application developed for the given
+ * {@link IAndroidTarget} will be displayed.
*
* @param parent The parent composite where the selector will be added.
* @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
* @param allowMultipleSelection True if more than one SDK target can be selected at the same
* time.
*/
- public AvdSelector(Composite parent, AvdInfo[] avds, boolean allowMultipleSelection) {
+ public AvdSelector(Composite parent, AvdInfo[] avds, IAndroidTarget filter,
+ boolean allowMultipleSelection) {
mAvds = avds;
// Layout has 1 column
@@ -99,11 +103,34 @@ public final class AvdSelector {
adjustColumnsWidth(mTable, column0, column1, column2, column3);
setupSelectionListener(mTable);
- fillTable(mTable, null /* target filter */);
+ fillTable(mTable, filter);
setupTooltip(mTable);
}
/**
+ * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}.
+ *
+ * @param parent The parent composite where the selector will be added.
+ * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
+ * @param allowMultipleSelection True if more than one SDK target can be selected at the same
+ * time.
+ */
+ public AvdSelector(Composite parent, AvdInfo[] avds, boolean allowMultipleSelection) {
+ this(parent, avds, null /* filter */, allowMultipleSelection);
+ }
+
+
+ public void setTableHeightHint(int heightHint) {
+ GridData data = new GridData();
+ data.heightHint = heightHint;
+ data.grabExcessVerticalSpace = true;
+ data.grabExcessHorizontalSpace = true;
+ data.horizontalAlignment = GridData.FILL;
+ data.verticalAlignment = GridData.FILL;
+ mTable.setLayoutData(data);
+ }
+
+ /**
* Sets a new set of AVD, with an optional filter.
* <p/>This must be called from the UI thread.
*
@@ -208,6 +235,18 @@ public final class AvdSelector {
}
/**
+ * Enables the receiver if the argument is true, and disables it otherwise.
+ * A disabled control is typically not selectable from the user interface
+ * and draws with an inactive or "grayed" look.
+ *
+ * @param enabled the new enabled state.
+ */
+ public void setEnabled(boolean enabled) {
+ mTable.setEnabled(enabled);
+ mDescription.setEnabled(enabled);
+ }
+
+ /**
* Adds a listener to adjust the columns width when the parent is resized.
* <p/>
* If we need something more fancy, we might want to use this: