diff options
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: |