am 226f95c4: Fix typo in javadoc.
* commit '226f95c416fa52c879bb2d5e19a23e252ee8b120':
Fix typo in javadoc.
diff --git a/.gitignore b/.gitignore
index 80b6f7c..019a6f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
*.pyc
Thumbs.db
*.class
+*.DS_Store
testapps/testSensors/proguard.cfg
# Hide temporary files created by the build_server script
diff --git a/anttargetprint/.classpath b/anttargetprint/.classpath
index 901231a..fa310aa 100644
--- a/anttargetprint/.classpath
+++ b/anttargetprint/.classpath
@@ -2,6 +2,6 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/apigenerator/.classpath b/apigenerator/.classpath
index bd75119..613f84d 100644
--- a/apigenerator/.classpath
+++ b/apigenerator/.classpath
@@ -2,7 +2,7 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
diff --git a/apps/SdkController/assets/intro_help.html b/apps/SdkController/assets/intro_help.html
index 000efa4..c957f73 100755
--- a/apps/SdkController/assets/intro_help.html
+++ b/apps/SdkController/assets/intro_help.html
@@ -32,7 +32,7 @@
<ol>
<li>Connect your device to your computer via USB. Make sure to enable <i>USB Debugging</i> in <i>Settings > Developer Options</i>. </li>
<li>Start this application on your device. </li>
-<li>On the computer in a shell, run: <br/><i>adb forward tcp:1968 tcp:1968</i> </li>
+<li>On the computer in a shell, run: <br/><i>adb forward tcp:1970 localhost:android.sdk.controller</i> </li>
<li>Finally <b>run an emulator</b> with an AVD targetting <b>API 15</b>. </li>
</ol>
<a href="https://sites.google.com/a/android.com/tools/recent/sensoremulation">Read more.</a>
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
index f22f12f..06b29f1 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
@@ -30,15 +30,16 @@
import android.widget.TextView;
import com.android.tools.sdkcontroller.R;
-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
-import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;
+import com.android.tools.sdkcontroller.handlers.MultiTouchChannel;
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.lib.ProtocolConstants;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;
import com.android.tools.sdkcontroller.utils.ApiHelper;
import com.android.tools.sdkcontroller.views.MultiTouchView;
/**
- * Activity that controls and displays the {@link MultiTouchHandler}.
+ * Activity that controls and displays the {@link MultiTouchChannel}.
*/
public class MultiTouchActivity extends BaseBindingActivity
implements android.os.Handler.Callback {
@@ -47,14 +48,7 @@
private static String TAG = MultiTouchActivity.class.getSimpleName();
private static boolean DEBUG = true;
- /** Received frame is JPEG image. */
- private static final int FRAME_JPEG = 1;
- /** Received frame is RGB565 bitmap. */
- private static final int FRAME_RGB565 = 2;
- /** Received frame is RGB888 bitmap. */
- private static final int FRAME_RGB888 = 3;
-
- private volatile MultiTouchHandler mHandler;
+ private volatile MultiTouchChannel mHandler;
private TextView mTextError;
private TextView mTextStatus;
@@ -108,8 +102,9 @@
@Override
protected void onServiceConnected() {
if (DEBUG) Log.d(TAG, "onServiceConnected");
- mHandler = (MultiTouchHandler) getServiceBinder().getHandler(HandlerType.MultiTouch);
+ mHandler = (MultiTouchChannel) getServiceBinder().getChannel(Channel.MULTITOUCH_CHANNEL);
if (mHandler != null) {
+ mHandler.setViewSize(mImageView.getWidth(), mImageView.getHeight());
mHandler.addUiHandler(mUiHandler);
}
}
@@ -150,7 +145,7 @@
if (binder != null) {
boolean connected = binder.isEmuConnected();
mImageView.setEnabled(connected);
- updateStatus(connected ? "Emulated connected" : "Emulator disconnected");
+ updateStatus(connected ? "Emulator connected" : "Emulator disconnected");
}
}
});
@@ -169,43 +164,67 @@
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
- StringBuilder sb = new StringBuilder();
+ ByteBuffer bb = null;
final int action = event.getAction();
final int action_code = action & MotionEvent.ACTION_MASK;
final int action_pid_index = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
+ int msg_type = 0;
+ MultiTouchChannel h = mHandler;
// Build message for the emulator.
switch (action_code) {
case MotionEvent.ACTION_MOVE:
- sb.append("action=move");
- for (int n = 0; n < event.getPointerCount(); n++) {
- mImageView.constructEventMessage(sb, event, n);
+ if (h != null) {
+ bb = ByteBuffer.allocate(
+ event.getPointerCount() * ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ for (int n = 0; n < event.getPointerCount(); n++) {
+ mImageView.constructEventMessage(bb, event, n);
+ }
+ msg_type = ProtocolConstants.MT_MOVE;
}
break;
case MotionEvent.ACTION_DOWN:
- sb.append("action=down");
- mImageView.constructEventMessage(sb, event, action_pid_index);
+ if (h != null) {
+ bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ mImageView.constructEventMessage(bb, event, action_pid_index);
+ msg_type = ProtocolConstants.MT_FISRT_DOWN;
+ }
break;
case MotionEvent.ACTION_UP:
- sb.append("action=up pid=").append(event.getPointerId(action_pid_index));
+ if (h != null) {
+ bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ bb.putInt(event.getPointerId(action_pid_index));
+ msg_type = ProtocolConstants.MT_LAST_UP;
+ }
break;
case MotionEvent.ACTION_POINTER_DOWN:
- sb.append("action=pdown");
- mImageView.constructEventMessage(sb, event, action_pid_index);
+ if (h != null) {
+ bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ mImageView.constructEventMessage(bb, event, action_pid_index);
+ msg_type = ProtocolConstants.MT_POINTER_DOWN;
+ }
break;
case MotionEvent.ACTION_POINTER_UP:
- sb.append("action=pup pid=").append(event.getPointerId(action_pid_index));
+ if (h != null) {
+ bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ bb.putInt(event.getPointerId(action_pid_index));
+ msg_type = ProtocolConstants.MT_POINTER_UP;
+ }
break;
default:
Log.w(TAG, "Unknown action type: " + action_code);
return true;
}
- if (DEBUG) Log.d(TAG, sb.toString());
+ if (DEBUG && bb != null) Log.d(TAG, bb.toString());
- MultiTouchHandler h = mHandler;
- if (h != null) {
- h.sendEventToEmulator(sb.toString() + '\0');
+ if (h != null && bb != null) {
+ h.postMessage(msg_type, bb);
}
return true;
}
@@ -215,18 +234,18 @@
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
- case MultiTouchHandler.EVENT_MT_START:
- MultiTouchHandler h = mHandler;
+ case MultiTouchChannel.EVENT_MT_START:
+ MultiTouchChannel h = mHandler;
if (h != null) {
- mHandler.setViewSize(mImageView.getWidth(), mImageView.getHeight());
+ mImageView.setEnabled(true);
mImageView.setOnTouchListener(mTouchListener);
}
break;
- case MultiTouchHandler.EVENT_MT_STOP:
+ case MultiTouchChannel.EVENT_MT_STOP:
mImageView.setOnTouchListener(null);
break;
- case MultiTouchHandler.EVENT_FRAME_BUFFER:
- onFrameBuffer((byte[]) msg.obj);
+ case MultiTouchChannel.EVENT_FRAME_BUFFER:
+ onFrameBuffer(((ByteBuffer) msg.obj).array());
break;
}
return true; // we consumed this message
@@ -267,7 +286,7 @@
// Update application display.
updateDisplay(disp_width, disp_height);
- if (format == FRAME_JPEG) {
+ if (format == ProtocolConstants.MT_FRAME_JPEG) {
/*
* Framebuffer is in JPEG format.
*/
@@ -293,7 +312,7 @@
}
// Convert the blob bitmap into bitmap that we will display.
- if (format == FRAME_RGB565) {
+ if (format == ProtocolConstants.MT_FRAME_RGB565) {
for (int n = 0; n < pixel_num; n++) {
// Blob bitmap is in RGB565 format.
final int color = bb.getShort();
@@ -302,7 +321,7 @@
final int b = ((color & 0x1f) << 3) | ((color & 0x1f) >> 2);
mColors[n] = Color.rgb(r, g, b);
}
- } else if (format == FRAME_RGB888) {
+ } else if (format == ProtocolConstants.MT_FRAME_RGB888) {
for (int n = 0; n < pixel_num; n++) {
// Blob bitmap is in RGB565 format.
final int r = bb.getChar();
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
index 5055c23..001398b 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
@@ -35,14 +35,14 @@
import android.widget.TextView;
import com.android.tools.sdkcontroller.R;
-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
-import com.android.tools.sdkcontroller.handlers.SensorsHandler;
-import com.android.tools.sdkcontroller.handlers.SensorsHandler.MonitoredSensor;
+import com.android.tools.sdkcontroller.handlers.SensorChannel;
+import com.android.tools.sdkcontroller.handlers.SensorChannel.MonitoredSensor;
+import com.android.tools.sdkcontroller.lib.Channel;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;
/**
- * Activity that displays and controls the sensors from {@link SensorsHandler}.
+ * Activity that displays and controls the sensors from {@link SensorChannel}.
* For each sensor it displays a checkbox that is enabled if the sensor is supported
* by the emulator. The user can select whether the sensor is active. It also displays
* data from the sensor when available.
@@ -61,10 +61,10 @@
private TextView mTextStatus;
private TextView mTextTargetHz;
private TextView mTextActualHz;
- private SensorsHandler mSensorHandler;
+ private SensorChannel mSensorHandler;
private final Map<MonitoredSensor, DisplayInfo> mDisplayedSensors =
- new HashMap<SensorsHandler.MonitoredSensor, SensorActivity.DisplayInfo>();
+ new HashMap<SensorChannel.MonitoredSensor, SensorActivity.DisplayInfo>();
private final android.os.Handler mUiHandler = new android.os.Handler(this);
private int mTargetSampleRate;
private long mLastActualUpdateMs;
@@ -173,7 +173,7 @@
removeSensorUi();
}
- mSensorHandler = (SensorsHandler) getServiceBinder().getHandler(HandlerType.Sensor);
+ mSensorHandler = (SensorChannel) getServiceBinder().getChannel(Channel.SENSOR_CHANNEL);
if (mSensorHandler != null) {
mSensorHandler.addUiHandler(mUiHandler);
mUiHandler.sendEmptyMessage(MSG_UPDATE_ACTUAL_HZ);
@@ -261,19 +261,19 @@
public boolean handleMessage(Message msg) {
DisplayInfo info = null;
switch (msg.what) {
- case SensorsHandler.SENSOR_STATE_CHANGED:
+ case SensorChannel.SENSOR_STATE_CHANGED:
info = mDisplayedSensors.get(msg.obj);
if (info != null) {
info.updateState();
}
break;
- case SensorsHandler.SENSOR_DISPLAY_MODIFIED:
+ case SensorChannel.SENSOR_DISPLAY_MODIFIED:
info = mDisplayedSensors.get(msg.obj);
if (info != null) {
info.updateValue();
}
if (mSensorHandler != null) {
- updateStatus(Integer.toString(mSensorHandler.getEventSentCount()) + " events sent");
+ updateStatus(Integer.toString(mSensorHandler.getMsgSentCount()) + " events sent");
// Update the "actual rate" field if the value has changed
long ms = mSensorHandler.getActualUpdateMs();
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
deleted file mode 100755
index b15b8c1..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2012 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.tools.sdkcontroller.handlers;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import android.content.Context;
-import android.os.Message;
-import android.util.Log;
-
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
-import com.android.tools.sdkcontroller.lib.EmulatorListener;
-import com.android.tools.sdkcontroller.service.ControllerService;
-
-
-/**
- * An abstract base class for all "action handlers".
- * <p/>
- * The {@link ControllerService} can deal with several handlers, each have a specific
- * purpose as described by {@link HandlerType}.
- * <p/>
- * The {@link BaseHandler} class adds support for activities to connect by providing
- * an {@link android.os.Handler} (which we'll call a "UI Handler" to differentiate it
- * from our "Service Handler"). The service handler will provide events via this
- * UI handler directly on the activity's UI thread.
- * <p/>
- * The {@link BaseHandler} keeps track of the current {@link EmulatorConnection} given
- * via {@link #onStart(EmulatorConnection, Context)}.
- * <p/>
- * The {@link BaseHandler} provides a simple way for activities to send event messages
- * back to the emulator by using {@link #sendEventToEmulator(String)}. This method
- * is safe to call from any thread, even the UI thread.
- */
-public abstract class BaseHandler {
-
- protected static final boolean DEBUG = false;
- protected static final String TAG = null;
-
- private EmulatorConnection mConnection;
-
- private final AtomicInteger mEventCount = new AtomicInteger(0);
- private volatile boolean mRunEventQueue = true;
- private final BlockingQueue<String> mEventQueue = new LinkedBlockingQueue<String>();
- private static String EVENT_QUEUE_END = "@end@";
- private final List<android.os.Handler> mUiHandlers = new ArrayList<android.os.Handler>();
- private final HandlerType mHandlerType;
- private final Thread mEventThread;
- private int mPort;
-
- /**
- * The type of action that this handler manages.
- */
- public enum HandlerType {
- /** A handler to send multitouch events from the device to the emulator and display
- * the emulator screen on the device. */
- MultiTouch,
- /** A handler to send sensor events from the device to the emulaotr. */
- Sensor
- }
-
- /**
- * Initializes a new base handler.
- *
- * @param type A non-null {@link HandlerType} value.
- * @param port A non-null communication port number.
- */
- protected BaseHandler(HandlerType type, int port) {
- mHandlerType = type;
- mPort = port;
-
- final String name = type.toString();
- mEventThread = new Thread(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "EventThread.started-" + name);
- while(mRunEventQueue) {
- try {
- String msg = mEventQueue.take();
- if (msg != null && mConnection != null && !msg.equals(EVENT_QUEUE_END)) {
- mConnection.sendNotification(msg);
- mEventCount.incrementAndGet();
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "EventThread-" + name, e);
- }
- }
- if (DEBUG) Log.d(TAG, "EventThread.terminate-" + name);
- }
- }, "EventThread-" + name);
- }
-
- /**
- * Returns the type of this handler, as given to the constructor.
- *
- * @return One of the {@link HandlerType} values.
- */
- public HandlerType getType() {
- return mHandlerType;
- }
-
- /**
- * Returns he communication port used by this handler to communicate with the emulator,
- * as given to the constructor.
- * <p/>
- * Note that right now we have 2 handlers that each use their own port. The goal is
- * to move to a single-connection mechanism where all the handlers' data will be
- * multiplexed on top of a single {@link EmulatorConnection}.
- *
- * @return A non-null port value.
- */
- public int getPort() {
- return mPort;
- }
-
- /**
- * Returns the last {@link EmulatorConnection} passed to
- * {@link #onStart(EmulatorConnection, Context)}.
- * It becomes null when {@link #onStop()} is called.
- *
- * @return The current {@link EmulatorConnection}.
- */
- public EmulatorConnection getConnection() {
- return mConnection;
- }
-
- /**
- * Called once the {@link EmulatorConnection} has been successfully initialized.
- * <p/>
- * Note that this will <em>not</em> be called if the {@link EmulatorConnection}
- * fails to bind to the underlying socket.
- * <p/>
- * This base implementation keeps tracks of the connection.
- *
- * @param connection The connection that has just been created.
- * A handler might want to use this to send data to the emulator via
- * {@link EmulatorConnection#sendNotification(String)}. However handlers
- * need to be particularly careful in <em>not</em> sending network data
- * from the main UI thread.
- * @param context The controller service' context.
- * @see #getConnection()
- */
- public void onStart(EmulatorConnection connection, Context context) {
- assert connection != null;
- mConnection = connection;
- mRunEventQueue = true;
- mEventThread.start();
- }
-
- /**
- * Called once the {@link EmulatorConnection} is being disconnected.
- * This nullifies the connection returned by {@link #getConnection()}.
- */
- public void onStop() {
- // Stop the message queue
- mConnection = null;
- if (mRunEventQueue) {
- mRunEventQueue = false;
- mEventQueue.offer(EVENT_QUEUE_END);
- }
- }
-
- public int getEventSentCount() {
- return mEventCount.get();
- }
-
- /**
- * Utility for handlers or activities to sends a string event to the emulator.
- * This method is safe for the activity to call from any thread, including the UI thread.
- *
- * @param msg Event message. Must not be null.
- */
- public void sendEventToEmulator(String msg) {
- try {
- mEventQueue.put(msg);
- } catch (InterruptedException e) {
- Log.e(TAG, "EventQueue.put", e);
- }
- }
-
- // ------------
- // Interaction from the emulator connection towards the handler
-
- /**
- * Emulator query being forwarded to the handler.
- *
- * @see EmulatorListener#onEmulatorQuery(String, String)
- */
- public abstract String onEmulatorQuery(String query, String param);
-
- /**
- * Emulator blob query being forwarded to the handler.
- *
- * @see EmulatorListener#onEmulatorBlobQuery(byte[])
- */
- public abstract String onEmulatorBlobQuery(byte[] array);
-
- // ------------
- // Interaction from handler towards listening UI
-
- /**
- * Indicates any UI handler is currently registered with the handler.
- * If no UI is displaying the handler's state, maybe the handler can skip UI related tasks.
- *
- * @return True if there's at least one UI handler registered.
- */
- public boolean hasUiHandler() {
- return !mUiHandlers.isEmpty();
- }
-
- /**
- * Registers a new UI handler.
- *
- * @param uiHandler A non-null UI handler to register.
- * Ignored if the UI handler is null or already registered.
- */
- public void addUiHandler(android.os.Handler uiHandler) {
- assert uiHandler != null;
- if (uiHandler != null) {
- if (!mUiHandlers.contains(uiHandler)) {
- mUiHandlers.add(uiHandler);
- }
- }
- }
-
- /**
- * Unregisters an UI handler.
- *
- * @param uiHandler A non-null UI listener to unregister.
- * Ignored if the listener is null or already registered.
- */
- public void removeUiHandler(android.os.Handler uiHandler) {
- assert uiHandler != null;
- mUiHandlers.remove(uiHandler);
- }
-
- /**
- * Protected method to be used by handlers to send an event to all UI handlers.
- *
- * @param event An integer event code with no specific parameters.
- * To be defined by the handler itself.
- */
- protected void notifyUiHandlers(int event) {
- for (android.os.Handler uiHandler : mUiHandlers) {
- uiHandler.sendEmptyMessage(event);
- }
- }
-
- /**
- * Protected method to be used by handlers to send an event to all UI handlers.
- *
- * @param msg An event with parameters. To be defined by the handler itself.
- */
- protected void notifyUiHandlers(Message msg) {
- for (android.os.Handler uiHandler : mUiHandlers) {
- uiHandler.sendMessage(msg);
- }
- }
-
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
new file mode 100755
index 0000000..fc70ae1
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2012 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.tools.sdkcontroller.handlers;
+
+import android.graphics.Point;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.lib.ProtocolConstants;
+import com.android.tools.sdkcontroller.service.ControllerService;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implements multi-touch emulation.
+ */
+public class MultiTouchChannel extends Channel {
+
+ @SuppressWarnings("hiding")
+ private static final String TAG = MultiTouchChannel.class.getSimpleName();
+ /**
+ * A new frame buffer has been received from the emulator.
+ * Parameter {@code obj} is a {@code byte[] array} containing the screen data.
+ */
+ public static final int EVENT_FRAME_BUFFER = 1;
+ /**
+ * A multi-touch "start" command has been received from the emulator.
+ * Parameter {@code obj} is the string parameter from the start command.
+ */
+ public static final int EVENT_MT_START = 2;
+ /**
+ * A multi-touch "stop" command has been received from the emulator. There
+ * is no {@code obj} parameter associated.
+ */
+ public static final int EVENT_MT_STOP = 3;
+
+ private static final Point mViewSize = new Point(0, 0);
+
+ /**
+ * Constructs MultiTouchChannel instance.
+ */
+ public MultiTouchChannel(ControllerService service) {
+ super(service, Channel.MULTITOUCH_CHANNEL);
+ }
+
+ /**
+ * Sets size of the display view for emulated screen updates.
+ *
+ * @param width View width in pixels.
+ * @param height View height in pixels.
+ */
+ public void setViewSize(int width, int height) {
+ mViewSize.set(width, height);
+ }
+
+ /*
+ * Channel abstract implementation.
+ */
+
+ /**
+ * This method is invoked when this channel is fully connected with its
+ * counterpart in the emulator.
+ */
+ @Override
+ public void onEmulatorConnected() {
+ if (hasUiHandler()) {
+ enable();
+ notifyUiHandlers(EVENT_MT_START);
+ }
+ }
+
+ /**
+ * This method is invoked when this channel loses connection with its
+ * counterpart in the emulator.
+ */
+ @Override
+ public void onEmulatorDisconnected() {
+ if (hasUiHandler()) {
+ disable();
+ notifyUiHandlers(EVENT_MT_STOP);
+ }
+ }
+
+ /**
+ * A message has been received from the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg_data Packet received from the emulator.
+ */
+ @Override
+ public void onEmulatorMessage(int msg_type, ByteBuffer msg_data) {
+ switch (msg_type) {
+ case ProtocolConstants.MT_FB_UPDATE:
+ Message msg = Message.obtain();
+ msg.what = EVENT_FRAME_BUFFER;
+ msg.obj = msg_data;
+ notifyUiHandlers(msg);
+ break;
+
+ default:
+ Log.e(TAG, "Unknown message type " + msg_type);
+ }
+ }
+
+ /**
+ * A query has been received from the emulator.
+ *
+ * @param query_id Identifies the query. This ID must be used when replying
+ * to the query.
+ * @param query_type Query type.
+ * @param query_data Query data.
+ */
+ @Override
+ public void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data) {
+ Loge("Unexpected query " + query_type + " in multi-touch");
+ sendQueryResponse(query_id, (byte[]) null);
+ }
+
+ /**
+ * Registers a new UI handler.
+ *
+ * @param uiHandler A non-null UI handler to register. Ignored if the UI
+ * handler is null or already registered.
+ */
+ @Override
+ public void addUiHandler(android.os.Handler uiHandler) {
+ final boolean first_handler = !hasUiHandler();
+ super.addUiHandler(uiHandler);
+ if (first_handler && isConnected()) {
+ enable();
+ notifyUiHandlers(EVENT_MT_START);
+ }
+ }
+
+ /**
+ * Unregisters an UI handler.
+ *
+ * @param uiHandler A non-null UI listener to unregister. Ignored if the
+ * listener is null or already registered.
+ */
+ @Override
+ public void removeUiHandler(android.os.Handler uiHandler) {
+ super.removeUiHandler(uiHandler);
+ if (isConnected() && !hasUiHandler()) {
+ disable();
+ }
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java
deleted file mode 100755
index 6f64485..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2012 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.tools.sdkcontroller.handlers;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.os.Message;
-import android.util.Log;
-
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
-
-
-public class MultiTouchHandler extends BaseHandler {
-
- @SuppressWarnings("hiding")
- private static final String TAG = MultiTouchHandler.class.getSimpleName();
- /**
- * A new frame buffer has been received from the emulator.
- * Parameter {@code obj} is a {@code byte[] array} containing the screen data.
- */
- public static final int EVENT_FRAME_BUFFER = 1;
- /**
- * A multi-touch "start" command has been received from the emulator.
- * Parameter {@code obj} is the string parameter from the start command.
- */
- public static final int EVENT_MT_START = 2;
- /**
- * A multi-touch "stop" command has been received from the emulator.
- * There is no {@code obj} parameter associated.
- */
- public static final int EVENT_MT_STOP = 3;
-
- private static final Point mViewSize = new Point(0, 0);
-
- public MultiTouchHandler() {
- super(HandlerType.MultiTouch, EmulatorConnection.MULTITOUCH_PORT);
- }
-
- public void setViewSize(int width, int height) {
- mViewSize.set(width, height);
- }
-
- @Override
- public void onStart(EmulatorConnection connection, Context context) {
- super.onStart(connection, context);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- }
-
- /**
- * Called when a query is received from the emulator. NOTE: This method is
- * called from the I/O loop.
- *
- * @param query Name of the query received from the emulator. The allowed
- * queries are: - 'start' - Starts delivering touch screen events
- * to the emulator. - 'stop' - Stops delivering touch screen
- * events to the emulator.
- * @param param Query parameters.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
- */
- @Override
- public String onEmulatorQuery(String query, String param) {
- if (query.contentEquals("start")) {
- Message msg = Message.obtain();
- msg.what = EVENT_MT_START;
- msg.obj = param;
- notifyUiHandlers(msg);
- return "ok:" + mViewSize.x + "x" + mViewSize.y + "\0";
-
- } else if (query.contentEquals("stop")) {
- notifyUiHandlers(EVENT_MT_STOP);
- return "ok\0";
-
- } else {
- Log.e(TAG, "Unknown query " + query + "(" + param + ")");
- return "ko:Unknown query\0";
- }
- }
-
- /**
- * Called when a BLOB query is received from the emulator.
- * <p/>
- * This query is used to deliver framebuffer updates in the emulator. The
- * blob contains an update header, followed by the bitmap containing updated
- * rectangle. The header is defined as MTFrameHeader structure in
- * external/qemu/android/multitouch-port.h
- * <p/>
- * NOTE: This method is called from the I/O loop, so all communication with
- * the emulator will be "on hold" until this method returns.
- *
- * @param array contains BLOB data for the query.
- * @return Empty string: this query doesn't require any response.
- */
- @Override
- public String onEmulatorBlobQuery(byte[] array) {
- Message msg = Message.obtain();
- msg.what = EVENT_FRAME_BUFFER;
- msg.obj = array;
- notifyUiHandlers(msg);
- return "";
- }
-
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
similarity index 66%
rename from apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
index 498b86d..d1ab836 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
@@ -16,6 +16,7 @@
package com.android.tools.sdkcontroller.handlers;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -28,13 +29,17 @@
import android.os.SystemClock;
import android.util.Log;
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.lib.ProtocolConstants;
+import com.android.tools.sdkcontroller.service.ControllerService;
-
-public class SensorsHandler extends BaseHandler {
+/**
+ * Implements sensors emulation.
+ */
+public class SensorChannel extends Channel {
@SuppressWarnings("hiding")
- private static String TAG = SensorsHandler.class.getSimpleName();
+ private static String TAG = SensorChannel.class.getSimpleName();
@SuppressWarnings("hiding")
private static boolean DEBUG = false;
/**
@@ -46,31 +51,63 @@
* Default value should match res/values/strings.xml > sensors_default_sample_rate.
*/
private long mUpdateTargetMs = 1000/20; // 20 fps in milliseconds
+ /** Accumulates average update frequency. */
private long mGlobalAvgUpdateMs = 0;
-
- /**
- * Sensor "enabled by emulator" state has changed.
- * Parameter {@code obj} is the {@link MonitoredSensor}.
- */
- public static final int SENSOR_STATE_CHANGED = 1;
- /**
- * Sensor display value has changed.
- * Parameter {@code obj} is the {@link MonitoredSensor}.
- */
- public static final int SENSOR_DISPLAY_MODIFIED = 2;
-
/** Array containing monitored sensors. */
private final List<MonitoredSensor> mSensors = new ArrayList<MonitoredSensor>();
+ /** Sensor manager. */
private SensorManager mSenMan;
- public SensorsHandler() {
- super(HandlerType.Sensor, EmulatorConnection.SENSORS_PORT);
+ /*
+ * Messages exchanged with the UI.
+ */
+
+ /**
+ * Sensor "enabled by emulator" state has changed. Parameter {@code obj} is
+ * the {@link MonitoredSensor}.
+ */
+ public static final int SENSOR_STATE_CHANGED = 1;
+ /**
+ * Sensor display value has changed. Parameter {@code obj} is the
+ * {@link MonitoredSensor}.
+ */
+ public static final int SENSOR_DISPLAY_MODIFIED = 2;
+
+ /**
+ * Constructs SensorChannel instance.
+ *
+ * @param service Service context.
+ */
+ public SensorChannel(ControllerService service) {
+ super(service, Channel.SENSOR_CHANNEL);
+ mSenMan = (SensorManager) service.getSystemService(Context.SENSOR_SERVICE);
+ // Iterate through the available sensors, adding them to the array.
+ List<Sensor> sensors = mSenMan.getSensorList(Sensor.TYPE_ALL);
+ int cur_index = 0;
+ for (int n = 0; n < sensors.size(); n++) {
+ Sensor avail_sensor = sensors.get(n);
+
+ // There can be multiple sensors of the same type. We need only one.
+ if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) {
+ // The first sensor we've got for the given type is not
+ // necessarily the right one. So, use the default sensor
+ // for the given type.
+ Sensor def_sens = mSenMan.getDefaultSensor(avail_sensor.getType());
+ MonitoredSensor to_add = new MonitoredSensor(def_sens);
+ cur_index++;
+ mSensors.add(to_add);
+ if (DEBUG)
+ Log.d(TAG, String.format(
+ "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
+ cur_index, def_sens.getName(), def_sens.getType()));
+ }
+ }
}
/**
* Returns the list of sensors found on the device.
- * The list is computed once by {@link #onStart(EmulatorConnection, Context)}.
+ * The list is computed once by {@link #SensorChannel(ControllerService)}.
*
* @return A non-null possibly-empty list of sensors.
*/
@@ -100,188 +137,146 @@
return mGlobalAvgUpdateMs;
}
+ /*
+ * Channel abstract implementation.
+ */
+
+ /**
+ * This method is invoked when this channel is fully connected with its
+ * counterpart in the emulator.
+ */
@Override
- public void onStart(EmulatorConnection connection, Context context) {
- super.onStart(connection, context);
-
- // Iterate through the available sensors, adding them to the array.
- SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
- mSenMan = sm;
- List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ALL);
- int cur_index = 0;
- for (int n = 0; n < sensors.size(); n++) {
- Sensor avail_sensor = sensors.get(n);
-
- // There can be multiple sensors of the same type. We need only one.
- if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) {
- // The first sensor we've got for the given type is not
- // necessarily the right one. So, use the default sensor
- // for the given type.
- Sensor def_sens = sm.getDefaultSensor(avail_sensor.getType());
- MonitoredSensor to_add = new MonitoredSensor(def_sens);
- cur_index++;
- mSensors.add(to_add);
- if (DEBUG) Log.d(TAG, String.format(
- "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
- cur_index, def_sens.getName(), def_sens.getType()));
- }
- }
+ public void onEmulatorConnected() {
+ // Emulation is now possible. Note though that it will start only after
+ // emulator tells us so with SENSORS_START command.
+ enable();
}
+ /**
+ * This method is invoked when this channel loses connection with its
+ * counterpart in the emulator.
+ */
@Override
- public void onStop() {
+ public void onEmulatorDisconnected() {
+ // Stop sensor event callbacks.
stopSensors();
- super.onStop();
}
/**
- * Called when a query is received from the emulator. NOTE: This method is
- * called from the I/O loop.
+ * A query has been received from the emulator.
*
- * @param query Name of the query received from the emulator. The allowed
- * queries are: 'list' - Lists sensors that are monitored by this
- * application. The application replies to this command with a
- * string: 'List:<name1>\n<name2>\n...<nameN>\n\0" 'start' -
- * Starts monitoring sensors. There is no reply for this command.
- * 'stop' - Stops monitoring sensors. There is no reply for this
- * command. 'enable:<sensor|all> - Enables notifications for a
- * sensor / all sensors. 'disable:<sensor|all> - Disables
- * notifications for a sensor / all sensors.
- * @param param Query parameters.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
+ * @param query_id Identifies the query. This ID should be used when
+ * replying to the query.
+ * @param query_type Query type.
+ * @param query_data Query data.
*/
@Override
- public String onEmulatorQuery(String query, String param) {
- if (query.contentEquals("list")) {
- return onQueryList();
- } else if (query.contentEquals("start")) {
- return onQueryStart();
- } else if (query.contentEquals("stop")) {
- return onQueryStop();
- } else if (query.contentEquals("enable")) {
- return onQueryEnable(param);
- } else if (query.contentEquals("disable")) {
- return onQueryDisable(param);
- } else {
- Log.e(TAG, "Unknown query " + query + "(" + param + ")");
- return "ko:Query is unknown\0";
+ public void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data) {
+ switch (query_type) {
+ case ProtocolConstants.SENSORS_QUERY_LIST:
+ // Preallocate large response buffer.
+ ByteBuffer resp = ByteBuffer.allocate(1024);
+ resp.order(getEndian());
+ // Iterate through the list of monitored sensors, dumping them
+ // into the response buffer.
+ for (MonitoredSensor sensor : mSensors) {
+ // Entry for each sensor must contain:
+ // - an integer for its ID
+ // - a zero-terminated emulator-friendly name.
+ final byte[] name = sensor.getEmulatorFriendlyName().getBytes();
+ final int required_size = 4 + name.length + 1;
+ resp = ExpandIf(resp, required_size);
+ resp.putInt(sensor.getType());
+ resp.put(name);
+ resp.put((byte) 0);
+ }
+ // Terminating entry contains single -1 integer.
+ resp = ExpandIf(resp, 4);
+ resp.putInt(-1);
+ sendQueryResponse(query_id, resp);
+ return;
+
+ default:
+ Loge("Unknown query " + query_type);
+ return;
}
}
/**
- * Called when a BLOB query is received from the emulator. NOTE: This method
- * is called from the I/O loop, so all communication with the emulator will
- * be "on hold" until this method returns.
+ * A message has been received from the emulator.
*
- * @param array contains BLOB data for the query.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
+ * @param msg_type Message type.
+ * @param msg_data Packet received from the emulator.
*/
@Override
- public String onEmulatorBlobQuery(byte[] array) {
- return "ko:Unexpected\0";
- }
-
- /***************************************************************************
- * Query handlers
- **************************************************************************/
-
- /**
- * Handles 'list' query.
- *
- * @return List of emulator-friendly names for sensors that are available on
- * the device.
- */
- private String onQueryList() {
- // List monitored sensors.
- String list = "ok:";
- for (MonitoredSensor sensor : mSensors) {
- list += sensor.getEmulatorFriendlyName();
- list += "\n";
+ public void onEmulatorMessage(int msg_type, ByteBuffer msg_data) {
+ switch (msg_type) {
+ case ProtocolConstants.SENSORS_START:
+ Log.v(TAG, "Starting sensors emulation.");
+ startSensors();
+ break;
+ case ProtocolConstants.SENSORS_STOP:
+ Log.v(TAG, "Stopping sensors emulation.");
+ stopSensors();
+ break;
+ case ProtocolConstants.SENSORS_ENABLE:
+ String enable_name = new String(msg_data.array());
+ Log.v(TAG, "Enabling sensor: " + enable_name);
+ onEnableSensor(enable_name);
+ break;
+ case ProtocolConstants.SENSORS_DISABLE:
+ String disable_name = new String(msg_data.array());
+ Log.v(TAG, "Disabling sensor: " + disable_name);
+ onDisableSensor(disable_name);
+ break;
+ default:
+ Loge("Unknown message type " + msg_type);
+ break;
}
- list += '\0'; // Response must end with zero-terminator.
- return list;
}
/**
- * Handles 'start' query.
+ * Handles 'enable' message.
*
- * @return Empty string. This is a "command" query that doesn't assume any
- * response.
+ * @param name Emulator-friendly name of a sensor to enable, or "all" to
+ * enable all sensors.
*/
- private String onQueryStart() {
- startSensors();
- return "ok\0";
- }
-
- /**
- * Handles 'stop' query.
- *
- * @return Empty string. This is a "command" query that doesn't assume any
- * response.
- */
- private String onQueryStop() {
- stopSensors();
- return "ok\0";
- }
-
- /**
- * Handles 'enable' query.
- *
- * @param param Sensor selector: - all Enables all available sensors, or -
- * <name> Emulator-friendly name of a sensor to enable.
- * @return "ok" / "ko": success / failure.
- */
- private String onQueryEnable(String param) {
- if (param.contentEquals("all")) {
+ private void onEnableSensor(String name) {
+ if (name.contentEquals("all")) {
// Enable all sensors.
for (MonitoredSensor sensor : mSensors) {
sensor.enableSensor();
}
- return "ok\0";
- }
-
- // Lookup sensor by emulator-friendly name.
- MonitoredSensor sensor = getSensorByEFN(param);
- if (sensor != null) {
- sensor.enableSensor();
- return "ok\0";
} else {
- return "ko:Sensor not found\0";
+ // Lookup sensor by emulator-friendly name.
+ final MonitoredSensor sensor = getSensorByEFN(name);
+ if (sensor != null) {
+ sensor.enableSensor();
+ }
}
}
/**
- * Handles 'disable' query.
+ * Handles 'disable' message.
*
- * @param param Sensor selector: - all Disables all available sensors, or -
- * <name> Emulator-friendly name of a sensor to disable.
- * @return "ok" / "ko": success / failure.
+ * @param name Emulator-friendly name of a sensor to disable, or "all" to
+ * disable all sensors.
*/
- private String onQueryDisable(String param) {
- if (param.contentEquals("all")) {
+ private void onDisableSensor(String name) {
+ if (name.contentEquals("all")) {
// Disable all sensors.
for (MonitoredSensor sensor : mSensors) {
sensor.disableSensor();
}
- return "ok\0";
- }
-
- // Lookup sensor by emulator-friendly name.
- MonitoredSensor sensor = getSensorByEFN(param);
- if (sensor != null) {
- sensor.disableSensor();
- return "ok\0";
} else {
- return "ko:Sensor not found\0";
+ // Lookup sensor by emulator-friendly name.
+ MonitoredSensor sensor = getSensorByEFN(name);
+ if (sensor != null) {
+ sensor.disableSensor();
+ }
}
}
- /***************************************************************************
- * Internals
- **************************************************************************/
-
/**
* Start listening to all monitored sensors.
*/
@@ -300,6 +295,10 @@
}
}
+ /***************************************************************************
+ * Internals
+ **************************************************************************/
+
/**
* Checks if a sensor for the given type is already monitored.
*
@@ -351,8 +350,7 @@
private String mEmulatorFriendlyName;
/** Formats string to show in the TextView. */
private String mTextFmt;
- private int mExpectedLen;
- private int mNbValues = 0;
+ /** Sensor values. */
private float[] mValues = new float[3];
/**
* Enabled state. This state is controlled by the emulator, that
@@ -362,6 +360,7 @@
private boolean mEnabledByEmulator = false;
/** User-controlled enabled state. */
private boolean mEnabledByUser = true;
+ /** Sensor event listener for this sensor. */
private final OurSensorEventListener mListener = new OurSensorEventListener();
/**
@@ -377,124 +376,112 @@
// we can't really use sensor.getName() here, since the value it
// returns (although resembles the purpose) is a bit vaguer than it
// should be. Also choose an appropriate format for the strings that
- // display sensor's value, and strings that are sent to the
- // emulator.
+ // display sensor's value.
switch (sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
mUiName = "Accelerometer";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "acceleration";
- mExpectedLen = 3;
break;
case 9: // Sensor.TYPE_GRAVITY is missing in API 7
- // 3 floats.
mUiName = "Gravity";
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "gravity";
- mExpectedLen = 3;
break;
case Sensor.TYPE_GYROSCOPE:
mUiName = "Gyroscope";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "gyroscope";
- mExpectedLen = 3;
break;
case Sensor.TYPE_LIGHT:
mUiName = "Light";
- // 1 integer.
mTextFmt = "%.0f";
mEmulatorFriendlyName = "light";
- mExpectedLen = 1;
break;
case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7
mUiName = "Linear acceleration";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "linear-acceleration";
- mExpectedLen = 3;
break;
case Sensor.TYPE_MAGNETIC_FIELD:
mUiName = "Magnetic field";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "magnetic-field";
- mExpectedLen = 3;
break;
case Sensor.TYPE_ORIENTATION:
mUiName = "Orientation";
- // 3 integers.
mTextFmt = "%+03.0f %+03.0f %+03.0f";
mEmulatorFriendlyName = "orientation";
- mExpectedLen = 3;
break;
case Sensor.TYPE_PRESSURE:
mUiName = "Pressure";
- // 1 integer.
mTextFmt = "%.0f";
mEmulatorFriendlyName = "pressure";
- mExpectedLen = 1;
break;
case Sensor.TYPE_PROXIMITY:
mUiName = "Proximity";
- // 1 integer.
mTextFmt = "%.0f";
mEmulatorFriendlyName = "proximity";
- mExpectedLen = 1;
break;
case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7
mUiName = "Rotation";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "rotation";
- mExpectedLen = 3;
break;
case Sensor.TYPE_TEMPERATURE:
mUiName = "Temperature";
- // 1 integer.
mTextFmt = "%.0f";
mEmulatorFriendlyName = "temperature";
- mExpectedLen = 1;
break;
default:
mUiName = "<Unknown>";
mTextFmt = "N/A";
mEmulatorFriendlyName = "unknown";
- mExpectedLen = 0;
- if (DEBUG) Log.e(TAG, "Unknown sensor type " + mSensor.getType() +
+ if (DEBUG) Loge("Unknown sensor type " + mSensor.getType() +
" for sensor " + mSensor.getName());
break;
}
}
+ /**
+ * Get name for this sensor to display.
+ *
+ * @return Name for this sensor to display.
+ */
public String getUiName() {
return mUiName;
}
+ /**
+ * Gets current sensor value to display.
+ *
+ * @return Current sensor value to display.
+ */
public String getValue() {
- String val = mValue;
-
- if (val == null) {
- int len = mNbValues;
+ if (mValue == null) {
float[] values = mValues;
- if (len == 3) {
- val = String.format(mTextFmt, values[0], values[1],values[2]);
- } else if (len == 2) {
- val = String.format(mTextFmt, values[0], values[1]);
- } else if (len == 1) {
- val = String.format(mTextFmt, values[0]);
- }
- mValue = val;
+ mValue = String.format(mTextFmt, values[0], values[1], values[2]);
}
-
- return val == null ? "??" : val;
+ return mValue == null ? "??" : mValue;
}
+ /**
+ * Checks if monitoring of this this sensor has been enabled by
+ * emulator.
+ *
+ * @return true if monitoring of this this sensor has been enabled by
+ * emulator, or false if emulator didn't enable this sensor.
+ */
public boolean isEnabledByEmulator() {
return mEnabledByEmulator;
}
+ /**
+ * Checks if monitoring of this this sensor has been enabled by user.
+ *
+ * @return true if monitoring of this this sensor has been enabled by
+ * user, or false if user didn't enable this sensor.
+ */
public boolean isEnabledByUser() {
return mEnabledByUser;
}
@@ -513,8 +500,6 @@
}
}
- // ---------
-
/**
* Gets sensor type.
*
@@ -560,7 +545,6 @@
private void enableSensor() {
if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
mEnabledByEmulator = true;
- mNbValues = 0;
mValue = null;
Message msg = Message.obtain();
@@ -586,10 +570,11 @@
private class OurSensorEventListener implements SensorEventListener {
/** Last update's time-stamp in local thread millisecond time. */
- private long mLastUpdateTS;
+ private long mLastUpdateTS = 0;
/** Last display update time-stamp. */
- private long mLastDisplayTS;
- private final StringBuilder mTempStr = new StringBuilder();
+ private long mLastDisplayTS = 0;
+ /** Preallocated buffer for change notification message. */
+ private final ByteBuffer mChangeMsg = ByteBuffer.allocate(64);
/**
* Handles "sensor changed" event.
@@ -597,7 +582,7 @@
*/
@Override
public void onSensorChanged(SensorEvent event) {
- long now = SystemClock.currentThreadTimeMillis();
+ long now = SystemClock.elapsedRealtime();
long deltaMs = 0;
if (mLastUpdateTS != 0) {
@@ -608,31 +593,21 @@
}
}
- // Format message that will be sent to the emulator.
+ // Format and post message for the emulator.
float[] values = event.values;
final int len = values.length;
- // A 3printfs with 3 * %g takes around 9-15 ms on an ADP2, or 3-4 ms on a GN.
- // However doing 3 * StringBuilder.append(float) takes < ~1 ms on ADP2.
- StringBuilder sb = mTempStr;
- sb.setLength(0);
- sb.append(mEmulatorFriendlyName);
-
- if (len != mExpectedLen) {
- Log.e(TAG, "Unexpected number of values " + len
- + " in onSensorChanged for sensor " + mSensor.getName());
- return;
- } else {
- sb.append(':').append(values[0]);
- if (len > 1) {
- sb.append(':').append(values[1]);
- if (len > 2) {
- sb.append(':').append(values[2]);
- }
+ mChangeMsg.order(getEndian());
+ mChangeMsg.position(0);
+ mChangeMsg.putInt(getType());
+ mChangeMsg.putFloat(values[0]);
+ if (len > 1) {
+ mChangeMsg.putFloat(values[1]);
+ if (len > 2) {
+ mChangeMsg.putFloat(values[2]);
}
}
- sb.append('\0');
- sendEventToEmulator(sb.toString());
+ postMessage(ProtocolConstants.SENSORS_SENSOR_EVENT, mChangeMsg);
// Computes average update time for this sensor and average globally.
if (mLastUpdateTS != 0) {
@@ -648,14 +623,13 @@
if (hasUiHandler()) {
if (mLastDisplayTS != 0) {
long uiDeltaMs = now - mLastDisplayTS;
- if (uiDeltaMs < 1000/4 /*4fps in ms*/) {
+ if (uiDeltaMs < 1000 / 4 /* 4fps in ms */) {
// Skip this UI update
return;
}
}
mLastDisplayTS = now;
- mNbValues = len;
mValues[0] = values[0];
if (len > 1) {
mValues[1] = values[1];
@@ -672,7 +646,7 @@
}
if (DEBUG) {
- long now2 = SystemClock.currentThreadTimeMillis();
+ long now2 = SystemClock.elapsedRealtime();
long processingTimeMs = now2 - now;
Log.d(TAG, String.format("glob %d - local %d > target %d - processing %d -- %s",
mGlobalAvgUpdateMs, deltaMs, mUpdateTargetMs, processingTimeMs,
@@ -681,8 +655,8 @@
}
/**
- * Handles "sensor accuracy changed" event. This is an implementation of
- * the SensorEventListener interface.
+ * Handles "sensor accuracy changed" event.
+ * This is an implementation of the SensorEventListener interface.
*/
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
@@ -690,4 +664,12 @@
}
} // MonitoredSensor
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
new file mode 100644
index 0000000..639f4cf
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2012 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.tools.sdkcontroller.lib;
+
+import android.os.Message;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.service.ControllerService;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Encapsulates basics of a connection with the emulator.
+ * This class must be used as a base class for all the channelss that provide
+ * particular type of emulation (such as sensors, multi-touch, etc.)
+ * <p/>
+ * Essentially, Channel is an implementation of a particular emulated functionality,
+ * that defines logical format of the data transferred between the emulator and
+ * SDK controller. For instance, "sensors" is a channel that emulates sensors,
+ * and transfers sensor value changes from the device to the emulator. "Multi-touch"
+ * is a channel that supports multi-touch emulation, and transfers multi-touch
+ * events to the emulator, while receiving frame buffer updates from the emulator.
+ * <p/>
+ * Besides connection with the emulator, each channel may contain one or more UI
+ * components associated with it. This class provides some basics for UI support,
+ * including:
+ * <p/>
+ * - Providing a way to register / unregister a UI component with the channel.
+ * <p/>
+ * - Implementing posting of messages to emulator in opposite to direct message
+ * sent. This is due to requirement that UI threads are prohibited from doing
+ * network I/O.
+ */
+public abstract class Channel {
+
+ /**
+ * Encapsulates a message posted to be sent to the emulator from a worker
+ * thread. This class is used to describe a message that is posted in UI
+ * thread, and then picked up in the worker thread.
+ */
+ private class SdkControllerMessage {
+ /** Message type. */
+ private int mMessageType;
+ /** Message data (can be null). */
+ private byte[] mMessage;
+ /** Message data size */
+ private int mMessageSize;
+
+ /**
+ * Construct message from an array.
+ *
+ * @param type Message type.
+ * @param message Message data. Message data size is defined by size of
+ * the array.
+ */
+ public SdkControllerMessage(int type, byte[] message) {
+ mMessageType = type;
+ mMessage = message;
+ mMessageSize = (message != null) ? message.length : 0;
+ }
+
+ /**
+ * Construct message from a ByteBuffer.
+ *
+ * @param type Message type.
+ * @param message Message data. Message data size is defined by
+ * position() property of the ByteBuffer.
+ */
+ public SdkControllerMessage(int type, ByteBuffer message) {
+ mMessageType = type;
+ if (message != null) {
+ mMessage = message.array();
+ mMessageSize = message.position();
+ } else {
+ mMessage = null;
+ mMessageSize = 0;
+ }
+ }
+
+ /**
+ * Gets message type.
+
+ *
+ * @return Message type.
+ */
+ public int getMessageType() {
+ return mMessageType;
+ }
+
+ /**
+ * Gets message buffer.
+ *
+ * @return Message buffer.
+ */
+ public byte[] getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Gets message buffer size.
+ *
+ * @return Message buffer size.
+ */
+ public int getMessageSize() {
+ return mMessageSize;
+ }
+ } // SdkControllerMessage
+
+ /*
+ * Names for currently implemented SDK controller channels.
+ */
+
+ /** Name for a channel that handles sensors emulation */
+ public static final String SENSOR_CHANNEL = "sensors";
+ /** Name for a channel that handles multi-touch emulation */
+ public static final String MULTITOUCH_CHANNEL = "multi-touch";
+
+ /*
+ * Types of messages internally used by Channel class.
+ */
+
+ /** Service-side emulator is connected. */
+ private static final int MSG_CONNECTED = -1;
+ /** Service-side emulator is disconnected. */
+ private static final int MSG_DISCONNECTED = -2;
+ /** Service-side emulator is enabled. */
+ private static final int MSG_ENABLED = -3;
+ /** Service-side emulator is disabled. */
+ private static final int MSG_DISABLED = -4;
+
+ /** Tag for logging messages. */
+ private static final String TAG = "SdkControllerChannel";
+ /** Controls debug log. */
+ private static final boolean DEBUG = false;
+
+ /** Service that has created this object. */
+ protected ControllerService mService;
+
+ /*
+ * Socket stuff.
+ */
+
+ /** Socket to use to to communicate with the emulator. */
+ private Socket mSocket = null;
+ /** Channel name ("sensors", "multi-touch", etc.) */
+ private String mChannelName;
+ /** Endianness of data transferred in this channel. */
+ private ByteOrder mEndian;
+
+ /*
+ * Message posting support.
+ */
+
+ /** Total number of messages posted in this channel */
+ private final AtomicInteger mMsgCount = new AtomicInteger(0);
+ /** Flags whether or not message thread is running. */
+ private volatile boolean mRunMsgQueue = true;
+ /** Queue of messages pending transmission. */
+ private final BlockingQueue<SdkControllerMessage>
+ mMsgQueue = new LinkedBlockingQueue<SdkControllerMessage>();
+ /** Message thread */
+ private final Thread mMsgThread;
+
+ /*
+ * UI support.
+ */
+
+ /** Lists UI handlers attached to this channel. */
+ private final List<android.os.Handler> mUiHandlers = new ArrayList<android.os.Handler>();
+
+ /*
+ * Abstract methods.
+ */
+
+ /**
+ * This method is invoked when this channel is fully connected with its
+ * counterpart in the emulator.
+ */
+ public abstract void onEmulatorConnected();
+
+ /**
+ * This method is invoked when this channel loses connection with its
+ * counterpart in the emulator.
+ */
+ public abstract void onEmulatorDisconnected();
+
+ /**
+ * A message has been received from the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg_data Message data. Message data size is defined by the length
+ * of the array wrapped by the ByteBuffer.
+ */
+ public abstract void onEmulatorMessage(int msg_type, ByteBuffer msg_data);
+
+ /**
+ * A query has been received from the emulator.
+ *
+ * @param query_id Identifies the query. This ID must be used when replying
+ * to the query.
+ * @param query_type Query type.
+ * @param query_data Query data. Query data size is defined by the length of
+ * the array wrapped by the ByteBuffer.
+ */
+ public abstract void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data);
+
+ /*
+ * Channel implementation.
+ */
+
+ /**
+ * Constructs Channel instance.
+ *
+ * @param name Channel name.
+ */
+ public Channel(ControllerService service, String name) {
+ mService = service;
+ mChannelName = name;
+ // Start the worker thread for posted messages.
+ mMsgThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "MsgThread.started-" + mChannelName);
+ while (mRunMsgQueue) {
+ try {
+ SdkControllerMessage msg = mMsgQueue.take();
+ if (msg != null) {
+ sendMessage(
+ msg.getMessageType(), msg.getMessage(), msg.getMessageSize());
+ mMsgCount.incrementAndGet();
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "MsgThread-" + mChannelName, e);
+ }
+ }
+ if (DEBUG) Log.d(TAG, "MsgThread.terminate-" + mChannelName);
+ }
+ }, "MsgThread-" + name);
+ mMsgThread.start();
+ if (DEBUG) Log.d(TAG, "Channel is constructed for " + mChannelName);
+ }
+
+ /**
+ * Gets name for this channel.
+ *
+ * @return Emulator name.
+ */
+ public String getChannelName() {
+ return mChannelName;
+ }
+
+ /**
+ * Gets endianness for this channel.
+ *
+ * @return Channel endianness.
+ */
+ public ByteOrder getEndian() {
+ return mEndian;
+ }
+
+ /**
+ * Gets number of messages sent via postMessage method.
+ *
+ * @return Number of messages sent via postMessage method.
+ */
+ public int getMsgSentCount() {
+ return mMsgCount.get();
+ }
+
+ /**
+ * Checks if this channel is connected with the emulator.
+ *
+ * @return true if this channel is connected with the emulator, or false if it is
+ * not connected.
+ */
+ public boolean isConnected() {
+ // Use local copy of the socket, ensuring it's not going to NULL while
+ // we're working with it. If it gets closed, while we're in the middle
+ // of data transfer - it's OK, since it will produce an exception, and
+ // the caller will gracefully handle it.
+ //
+ // Same technique is used everywhere in this class where mSocket member
+ // is touched.
+ Socket socket = mSocket;
+ return socket != null && socket.isConnected();
+ }
+
+ /**
+ * Establishes connection with the emulator. This method is called by Connection
+ * object when emulator successfully connects to this channel, or this channel
+ * gets registered, and there is a pending socket connection for it.
+ *
+ * @param socket Channel connection socket.
+ */
+ public void connect(Socket socket) {
+ mSocket = socket;
+ mEndian = socket.getEndian();
+ Logv("Channel " + mChannelName + " is now connected with the emulator.");
+ // Notify the emulator that connection is established.
+ sendMessage(MSG_CONNECTED, (byte[]) null);
+
+ // Let the derived class know that emulator is connected, and start the
+ // I/O loop in which we will receive data from the emulator. Note that
+ // we start the loop after onEmulatorConnected call, since we don't want
+ // to start dispatching messages before the derived class could set
+ // itself up for receiving them.
+ onEmulatorConnected();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ runIOLooper();
+ }
+ }, "ChannelIoLoop").start();
+ mService.notifyStatusChanged();
+ }
+
+ /**
+ * Disconnects this channel from the emulator.
+ *
+ * @return true if this channel has been disconnected in this call, or false if
+ * channel has been already disconnected when this method has been called.
+ */
+ public boolean disconnect() {
+ // This is the only place in this class where we will null the
+ // socket object. Since this method can be called concurrently from
+ // different threads, lets do this under the lock.
+ Socket socket;
+ synchronized (this) {
+ socket = mSocket;
+ mSocket = null;
+ }
+ if (socket != null) {
+ // Notify the emulator about channel disconnection before we close
+ // the communication socket.
+ try {
+ sendMessage(socket, MSG_DISCONNECTED, null, 0);
+ } catch (IOException e) {
+ // Ignore I/O exception at this point. We don't care about
+ // it, since the socket is being closed anyways.
+ }
+ // This will eventually stop I/O looper thread.
+ socket.close();
+ mService.notifyStatusChanged();
+ }
+ return socket != null;
+ }
+
+ /**
+ * Enables the emulation. Typically, this method is called for channels that are
+ * dependent on UI to handle the emulation. For instance, multi-touch emulation is
+ * disabled until at least one UI component is attached to the channel. So, for
+ * multi-touch emulation this method is called when UI gets attached to the channel.
+ */
+ public void enable() {
+ postMessage(MSG_ENABLED, (byte[]) null);
+ mService.notifyStatusChanged();
+ }
+
+ /**
+ * Disables the emulation. Just the opposite to enable(). For multi-touch this
+ * method is called when UI detaches from the channel.
+ */
+ public void disable() {
+ postMessage(MSG_DISABLED, (byte[]) null);
+ mService.notifyStatusChanged();
+ }
+
+ /**
+ * Sends message to the emulator.
+ *
+ * @param socket Socket to send the message to.
+ * @param msg_type Message type.
+ * @param msg Message data to send.
+ * @param len Byte size of message data.
+ * @throws IOException
+ */
+ private void sendMessage(Socket socket, int msg_type, byte[] msg, int len)
+ throws IOException {
+ // In async environment we must have message header and message data in
+ // one block to prevent messages from other threads getting between the
+ // header and the data. So, we can't sent header, and then the data. We
+ // must combine them in one data block instead.
+ ByteBuffer bb = ByteBuffer.allocate(ProtocolConstants.MESSAGE_HEADER_SIZE + len);
+ bb.order(mEndian);
+
+ // Initialize message header.
+ bb.putInt(ProtocolConstants.PACKET_SIGNATURE);
+ bb.putInt(ProtocolConstants.MESSAGE_HEADER_SIZE + len);
+ bb.putInt(ProtocolConstants.PACKET_TYPE_MESSAGE);
+ bb.putInt(msg_type);
+
+ // Save message data (if there is any).
+ if (len != 0) {
+ bb.put(msg, 0, len);
+ }
+
+ socket.send(bb.array());
+ }
+
+ /**
+ * Sends message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to send. Message size is defined by the size of
+ * the array.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendMessage(int msg_type, byte[] msg, int msg_len) {
+ try {
+ Socket socket = mSocket;
+ if (socket != null) {
+ sendMessage(socket, msg_type, msg, msg_len);
+ return true;
+ } else {
+ Logw("sendMessage is called on disconnected Channel " + mChannelName);
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+ onIoFailure();
+ }
+ return false;
+ }
+
+ /**
+ * Sends message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to send. Message size is defined by the size of
+ * the array.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendMessage(int msg_type, byte[] msg) {
+ try {
+ Socket socket = mSocket;
+ if (socket != null) {
+ if (msg != null) {
+ sendMessage(socket, msg_type, msg, msg.length);
+ } else {
+ sendMessage(socket, msg_type, null, 0);
+ }
+ return true;
+ } else {
+ Logw("sendMessage is called on disconnected Channel " + mChannelName);
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+ onIoFailure();
+ }
+ return false;
+ }
+
+ /**
+ * Sends message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to send. Message size is defined by the
+ * position() property of the ByteBuffer.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendMessage(int msg_type, ByteBuffer msg) {
+ try {
+ Socket socket = mSocket;
+ if (socket != null) {
+ if (msg != null) {
+ sendMessage(socket, msg_type, msg.array(), msg.position());
+ } else {
+ sendMessage(socket, msg_type, null, 0);
+ }
+ return true;
+ } else {
+ Logw("sendMessage is called on disconnected Channel " + mChannelName);
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+ onIoFailure();
+ }
+ return false;
+ }
+
+ /**
+ * Posts message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to post. Message size is defined by the size of
+ * the array.
+ */
+ public void postMessage(int msg_type, byte[] msg) {
+ try {
+ mMsgQueue.put(new SdkControllerMessage(msg_type, msg));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "mMessageQueue.put", e);
+ }
+ }
+
+ /**
+ * Posts message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to post. Message size is defined by the
+ * position() property of the ByteBuffer.
+ */
+ public void postMessage(int msg_type, ByteBuffer msg) {
+ try {
+ mMsgQueue.put(new SdkControllerMessage(msg_type, msg));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "mMessageQueue.put", e);
+ }
+ }
+
+ /**
+ * Sends query response to the emulator.
+ *
+ * @param query_id Query identifier.
+ * @param qresp Response to the query.
+ * @param len Byte size of query response data.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendQueryResponse(int query_id, byte[] qresp, int len) {
+ // Just like with messages, we must combine header and data in a single
+ // transmitting block.
+ ByteBuffer bb = ByteBuffer.allocate(ProtocolConstants.QUERY_RESP_HEADER_SIZE + len);
+ bb.order(mEndian);
+
+ // Initialize response header.
+ bb.putInt(ProtocolConstants.PACKET_SIGNATURE);
+ bb.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + len);
+ bb.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
+ bb.putInt(query_id);
+
+ // Save response data (if there is any).
+ if (qresp != null && len != 0) {
+ bb.put(qresp, 0, len);
+ }
+
+ // Send the response.
+ try {
+ Socket socket = mSocket;
+ if (socket != null) {
+ socket.send(bb.array());
+ return true;
+ } else {
+ Logw("sendQueryResponse is called on disconnected Channel "
+ + mChannelName);
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in sendQueryResponse for Channel " + mChannelName);
+ onIoFailure();
+ }
+ return false;
+ }
+
+ /**
+ * Sends query response to the emulator.
+ *
+ * @param query_id Query identifier.
+ * @param qresp Response to the query. Query response size is defined by the
+ * size of the array.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendQueryResponse(int query_id, byte[] qresp) {
+ return (qresp != null) ? sendQueryResponse(query_id, qresp, qresp.length) :
+ sendQueryResponse(query_id, null, 0);
+ }
+
+ /**
+ * Sends query response to the emulator.
+ *
+ * @param query_id Query identifier.
+ * @param qresp Response to the query. Query response size is defined by the
+ * position() property of the ByteBuffer.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendQueryResponse(int query_id, ByteBuffer qresp) {
+ return (qresp != null) ? sendQueryResponse(query_id, qresp.array(), qresp.position()) :
+ sendQueryResponse(query_id, null, 0);
+ }
+
+ /**
+ * Handles an I/O failure occurred in the channel.
+ */
+ private void onIoFailure() {
+ // All I/O failures cause disconnection.
+ if (disconnect()) {
+ // Success of disconnect() indicates that I/O failure is not the
+ // result of a disconnection request, but is in deed an I/O
+ // failure. Report lost connection to the derived class.
+ Loge("Connection with the emulator has been lost in Channel " + mChannelName);
+ onEmulatorDisconnected();
+ }
+ }
+
+ /**
+ * Loops on the local socket, handling connection attempts.
+ */
+ private void runIOLooper() {
+ if (DEBUG) Log.d(TAG, "In I/O looper for Channel " + mChannelName);
+ // Initialize byte buffer large enough to receive packet header.
+ ByteBuffer header = ByteBuffer.allocate(ProtocolConstants.PACKET_HEADER_SIZE);
+ header.order(mEndian);
+ try {
+ // Since disconnection (which will null the mSocket) can be
+ // requested from outside of this thread, it's simpler just to make
+ // a copy of mSocket here, and work with that copy. Otherwise we
+ // will have to go through a complex synchronization algorithm that
+ // would decrease performance on normal runs. If socket gets closed
+ // while we're in the middle of transfer, an exception will occur,
+ // which we will catch and handle properly.
+ Socket socket = mSocket;
+ while (socket != null) {
+ // Reset header position.
+ header.position(0);
+ // This will receive total packet size + packet type.
+ socket.receive(header.array());
+ // First - signature.
+ final int signature = header.getInt();
+ assert signature == ProtocolConstants.PACKET_SIGNATURE;
+ // Next - packet size (including header).
+ int remains = header.getInt() - ProtocolConstants.PACKET_HEADER_SIZE;
+ // After the size comes packet type.
+ final int packet_type = header.getInt();
+
+ // Get the remainder of the data, and dispatch the packet to
+ // an appropriate handler.
+ switch (packet_type) {
+ case ProtocolConstants.PACKET_TYPE_MESSAGE:
+ // Read message header (one int: message type).
+ final int ext = ProtocolConstants.MESSAGE_HEADER_SIZE - ProtocolConstants.PACKET_HEADER_SIZE;
+ header.position(0);
+ socket.receive(header.array(), ext);
+ final int msg_type = header.getInt();
+
+ // Read message data.
+ remains -= ext;
+ final ByteBuffer msg_data = ByteBuffer.allocate(remains);
+ msg_data.order(mEndian);
+ socket.receive(msg_data.array());
+
+ // Dispatch message for handling.
+ onEmulatorMessage(msg_type, msg_data);
+ break;
+
+ case ProtocolConstants.PACKET_TYPE_QUERY:
+ // Read query ID and query type.
+ final int extq = ProtocolConstants.QUERY_HEADER_SIZE - ProtocolConstants.PACKET_HEADER_SIZE;
+ header.position(0);
+ socket.receive(header.array(), extq);
+ final int query_id = header.getInt();
+ final int query_type = header.getInt();
+
+ // Read query data.
+ remains -= extq;
+ final ByteBuffer query_data = ByteBuffer.allocate(remains);
+ query_data.order(mEndian);
+ socket.receive(query_data.array());
+
+ // Dispatch query for handling.
+ onEmulatorQuery(query_id, query_type, query_data);
+ break;
+
+ default:
+ // Unknown packet type. Just discard the remainder
+ // of the packet
+ Loge("Unknown packet type " + packet_type + " in Channel "
+ + mChannelName);
+ final byte[] discard_data = new byte[remains];
+ socket.receive(discard_data);
+ break;
+ }
+ socket = mSocket;
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in I/O looper for Channel " + mChannelName);
+ onIoFailure();
+ }
+ if (DEBUG) Log.d(TAG, "Exiting I/O looper for Channel " + mChannelName);
+ }
+
+ /**
+ * Indicates any UI handler is currently registered with the channel. If no UI
+ * is displaying the channel's state, maybe the channel can skip UI related tasks.
+ *
+ * @return True if there's at least one UI handler registered.
+ */
+ public boolean hasUiHandler() {
+ return !mUiHandlers.isEmpty();
+ }
+
+ /**
+ * Registers a new UI handler.
+ *
+ * @param uiHandler A non-null UI handler to register. Ignored if the UI
+ * handler is null or already registered.
+ */
+ public void addUiHandler(android.os.Handler uiHandler) {
+ assert uiHandler != null;
+ if (uiHandler != null) {
+ if (!mUiHandlers.contains(uiHandler)) {
+ mUiHandlers.add(uiHandler);
+ }
+ }
+ }
+
+ /**
+ * Unregisters an UI handler.
+ *
+ * @param uiHandler A non-null UI listener to unregister. Ignored if the
+ * listener is null or already registered.
+ */
+ public void removeUiHandler(android.os.Handler uiHandler) {
+ assert uiHandler != null;
+ mUiHandlers.remove(uiHandler);
+ }
+
+ /**
+ * Protected method to be used by handlers to send an event to all UI
+ * handlers.
+ *
+ * @param event An integer event code with no specific parameters. To be
+ * defined by the handler itself.
+ */
+ protected void notifyUiHandlers(int event) {
+ for (android.os.Handler uiHandler : mUiHandlers) {
+ uiHandler.sendEmptyMessage(event);
+ }
+ }
+
+ /**
+ * Protected method to be used by handlers to send an event to all UI
+ * handlers.
+ *
+ * @param msg An event with parameters. To be defined by the handler itself.
+ */
+ protected void notifyUiHandlers(Message msg) {
+ for (android.os.Handler uiHandler : mUiHandlers) {
+ uiHandler.sendMessage(msg);
+ }
+ }
+
+ /**
+ * A helper routine that expands ByteBuffer to contain given number of extra
+ * bytes.
+ *
+ * @param buff Buffer to expand.
+ * @param extra Number of bytes that are required to be available in the
+ * buffer after current position()
+ * @return ByteBuffer, containing required number of available bytes.
+ */
+ public ByteBuffer ExpandIf(ByteBuffer buff, int extra) {
+ if (extra <= buff.remaining()) {
+ return buff;
+ }
+ ByteBuffer ret = ByteBuffer.allocate(buff.position() + extra);
+ ret.order(buff.order());
+ ret.put(buff.array(), 0, buff.position());
+ return ret;
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
+
+ private void Logw(String log) {
+ Log.w(TAG, log);
+ }
+
+ private void Logv(String log) {
+ Log.v(TAG, log);
+ }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
new file mode 100644
index 0000000..cb50869
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2012 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.tools.sdkcontroller.lib;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.util.Log;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.service.ControllerService;
+
+/**
+ * Encapsulates a connection between SdkController service and the emulator. On
+ * the device side, the connection is bound to the UNIX-domain socket named
+ * 'android.sdk.controller'. On the emulator side the connection is established
+ * via TCP port that is used to forward I/O traffic on the host machine to
+ * 'android.sdk.controller' socket on the device. Typically, the port forwarding
+ * can be enabled using adb command:
+ * <p/>
+ * 'adb forward tcp:<TCP port number> localabstract:android.sdk.controller'
+ * <p/>
+ * The way communication between the emulator and SDK controller service works
+ * is as follows:
+ * <p/>
+ * 1. Both sides, emulator and the service have components that implement a particular
+ * type of emulation. For instance, AndroidSensorsPort in the emulator, and
+ * SensorChannel in the application implement sensors emulation.
+ * Emulation channels are identified by unique names. For instance, sensor emulation
+ * is done via "sensors" channel, multi-touch emulation is done via "multi-touch"
+ * channel, etc.
+ * <p/>
+ * 2. Channels are connected to emulator via separate socket instance (though all
+ * of the connections share the same socket address).
+ * <p/>
+ * 3. Connection is initiated by the emulator side, while the service provides
+ * its side (a channel) that implement functionality and exchange protocol required
+ * by the requested type of emulation.
+ * <p/>
+ * Given that, the main responsibilities of this class are:
+ * <p/>
+ * 1. Bind to "android.sdk.controller" socket, listening to emulator connections.
+ * <p/>
+ * 2. Maintain a list of service-side channels registered by the application.
+ * <p/>
+ * 3. Bind emulator connection with service-side channel via port name, provided by
+ * the emulator.
+ * <p/>
+ * 4. Monitor connection state with the emulator, and automatically restore the
+ * connection once it is lost.
+ */
+public class Connection {
+ /** UNIX-domain name reserved for SDK controller. */
+ public static final String SDK_CONTROLLER_PORT = "android.sdk.controller";
+ /** Tag for logging messages. */
+ private static final String TAG = "SdkControllerConnection";
+ /** Controls debug logging */
+ private static final boolean DEBUG = false;
+
+ /** Server socket used to listen to emulator connections. */
+ private LocalServerSocket mServerSocket = null;
+ /** Service that has created this object. */
+ private ControllerService mService;
+ /**
+ * List of connected emulator sockets, pending for a channel to be registered.
+ * <p/>
+ * Emulator may connect to SDK controller before the app registers a channel
+ * for that connection. In this case (when app-side channel is not registered
+ * with this class) we will keep emulator connection in this list, pending
+ * for the app-side channel to register.
+ */
+ private List<Socket> mPendingSockets = new ArrayList<Socket>();
+ /**
+ * List of registered app-side channels.
+ * <p/>
+ * Channels that are kept in this list may be disconnected from (or pending
+ * connection with) the emulator, or they may be connected with the
+ * emulator.
+ */
+ private List<Channel> mChannels = new ArrayList<Channel>();
+
+ /**
+ * Constructs Connection instance.
+ */
+ public Connection(ControllerService service) {
+ mService = service;
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is constructed.");
+ }
+
+ /**
+ * Binds to the socket, and starts the listening thread.
+ */
+ public void connect() {
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is connecting...");
+ // Start connection listener.
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ runIOLooper();
+ }
+ }, "SdkControllerConnectionIoLoop").start();
+ }
+
+ /**
+ * Stops the listener, and closes the socket.
+ *
+ * @return true if connection has been stopped in this call, or false if it
+ * has been already stopped when this method has been called.
+ */
+ public boolean disconnect() {
+ // This is the only place in this class where we will null the
+ // socket object. Since this method can be called concurrently from
+ // different threads, lets do this under the lock.
+ LocalServerSocket socket;
+ synchronized (this) {
+ socket = mServerSocket;
+ mServerSocket = null;
+ }
+ if (socket != null) {
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is stopping I/O looper...");
+ // Stop accepting new connections.
+ wakeIOLooper(socket);
+ try {
+ socket.close();
+ } catch (Exception e) {
+ }
+
+ // Close all the pending sockets, and clear pending socket list.
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is closing pending sockets...");
+ for (Socket pending_socket : mPendingSockets) {
+ pending_socket.close();
+ }
+ mPendingSockets.clear();
+
+ // Disconnect all the emualtors.
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnecting channels...");
+ for (Channel channel : mChannels) {
+ if (channel.disconnect()) {
+ channel.onEmulatorDisconnected();
+ }
+ }
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnected.");
+ }
+ return socket != null;
+ }
+
+ /**
+ * Registers SDK controller channel.
+ *
+ * @param channel SDK controller emulator to register.
+ * @return true if channel has been registered successfully, or false if channel
+ * with the same name is already registered.
+ */
+ public boolean registerChannel(Channel channel) {
+ for (Channel check_channel : mChannels) {
+ if (check_channel.getChannelName().equals(channel.getChannelName())) {
+ Loge("Registering a duplicate Channel " + channel.getChannelName());
+ return false;
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Registering Channel " + channel.getChannelName());
+ mChannels.add(channel);
+
+ // Lets see if there is a pending socket for this channel.
+ for (Socket pending_socket : mPendingSockets) {
+ if (pending_socket.getChannelName().equals(channel.getChannelName())) {
+ // Remove the socket from the pending list, and connect the registered channel with it.
+ if (DEBUG) Log.d(TAG, "Found pending Socket for registering Channel "
+ + channel.getChannelName());
+ mPendingSockets.remove(pending_socket);
+ channel.connect(pending_socket);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if at least one socket connection exists with channel.
+ *
+ * @return true if at least one socket connection exists with channel.
+ */
+ public boolean isEmulatorConnected() {
+ for (Channel channel : mChannels) {
+ if (channel.isConnected()) {
+ return true;
+ }
+ }
+ return !mPendingSockets.isEmpty();
+ }
+
+ /**
+ * Gets Channel instance for the given channel name.
+ *
+ * @param name Channel name to get Channel instance for.
+ * @return Channel instance for the given channel name, or NULL if no
+ * channel has been registered for that name.
+ */
+ public Channel getChannel(String name) {
+ for (Channel channel : mChannels) {
+ if (channel.getChannelName().equals(name)) {
+ return channel;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets connected emulator socket that is pending for service-side channel
+ * registration.
+ *
+ * @param name Channel name to lookup Socket for.
+ * @return Connected emulator socket that is pending for service-side channel
+ * registration, or null if no socket is pending for service-size
+ * channel registration.
+ */
+ private Socket getPendingSocket(String name) {
+ for (Socket socket : mPendingSockets) {
+ if (socket.getChannelName().equals(name)) {
+ return socket;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Wakes I/O looper waiting on connection with the emulator.
+ *
+ * @param socket Server socket waiting on connection.
+ */
+ private void wakeIOLooper(LocalServerSocket socket) {
+ // We wake the looper by connecting to the socket.
+ LocalSocket waker = new LocalSocket();
+ try {
+ waker.connect(socket.getLocalSocketAddress());
+ } catch (IOException e) {
+ Loge("Exception " + e + " in SdkControllerConnection while waking up the I/O looper.");
+ }
+ }
+
+ /**
+ * Loops on the local socket, handling emulator connection attempts.
+ */
+ private void runIOLooper() {
+ if (DEBUG) Log.d(TAG, "In SdkControllerConnection I/O looper.");
+ do {
+ try {
+ // Create non-blocking server socket that would listen for connections,
+ // and bind it to the given port on the local host.
+ mServerSocket = new LocalServerSocket(SDK_CONTROLLER_PORT);
+ LocalServerSocket socket = mServerSocket;
+ while (socket != null) {
+ final LocalSocket sk = socket.accept();
+ if (mServerSocket != null) {
+ onAccept(sk);
+ } else {
+ break;
+ }
+ socket = mServerSocket;
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + "SdkControllerConnection I/O looper.");
+ }
+ if (DEBUG) Log.d(TAG, "Exiting SdkControllerConnection I/O looper.");
+
+ // If we're exiting the internal loop for reasons other than an explicit
+ // disconnect request, we should reconnect again.
+ } while (disconnect());
+ }
+
+ /**
+ * Accepts new connection from the emulator.
+ *
+ * @param sock Connecting socket.
+ * @throws IOException
+ */
+ private void onAccept(LocalSocket sock) throws IOException {
+ final ByteBuffer handshake = ByteBuffer.allocate(ProtocolConstants.QUERY_HEADER_SIZE);
+
+ // By protocol, first byte received from newly connected emulator socket
+ // indicates host endianness.
+ Socket.receive(sock, handshake.array(), 1);
+ final ByteOrder endian = (handshake.getChar() == 0) ? ByteOrder.LITTLE_ENDIAN :
+ ByteOrder.BIG_ENDIAN;
+ handshake.order(endian);
+
+ // Right after that follows the handshake query header.
+ handshake.position(0);
+ Socket.receive(sock, handshake.array(), handshake.array().length);
+
+ // First int - signature
+ final int signature = handshake.getInt();
+ assert signature == ProtocolConstants.PACKET_SIGNATURE;
+ // Second int - total query size (including fixed query header)
+ final int remains = handshake.getInt() - ProtocolConstants.QUERY_HEADER_SIZE;
+ // After that - header type (which must be SDKCTL_PACKET_TYPE_QUERY)
+ final int msg_type = handshake.getInt();
+ assert msg_type == ProtocolConstants.PACKET_TYPE_QUERY;
+ // After that - query ID.
+ final int query_id = handshake.getInt();
+ // And finally, query type (which must be ProtocolConstants.QUERY_HANDSHAKE for
+ // handshake query)
+ final int query_type = handshake.getInt();
+ assert query_type == ProtocolConstants.QUERY_HANDSHAKE;
+ // Verify that received is a query.
+ if (msg_type != ProtocolConstants.PACKET_TYPE_QUERY) {
+ // Message type is not a query. Lets read and discard the remainder
+ // of the message.
+ if (remains > 0) {
+ Loge("Unexpected handshake message type: " + msg_type);
+ byte[] discard = new byte[remains];
+ Socket.receive(sock, discard, discard.length);
+ }
+ return;
+ }
+
+ // Receive query data.
+ final byte[] name_array = new byte[remains];
+ Socket.receive(sock, name_array, name_array.length);
+
+ // Prepare response header.
+ handshake.position(0);
+ handshake.putInt(ProtocolConstants.PACKET_SIGNATURE);
+ // Handshake reply is just one int.
+ handshake.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + 4);
+ handshake.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
+ handshake.putInt(query_id);
+
+ // Verify that received query is in deed a handshake query.
+ if (query_type != ProtocolConstants.QUERY_HANDSHAKE) {
+ // Query is not a handshake. Reply with failure.
+ Loge("Unexpected handshake query type: " + query_type);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_QUERY_UNKNOWN);
+ sock.getOutputStream().write(handshake.array());
+ return;
+ }
+
+ // Handshake query data consist of SDK controller channel name.
+ final String channel_name = new String(name_array);
+ if (DEBUG) Log.d(TAG, "Handshake received for channel " + channel_name);
+
+ // Respond to query depending on service-side channel availability
+ final Channel channel = getChannel(channel_name);
+ Socket sk = null;
+
+ if (channel != null) {
+ if (channel.isConnected()) {
+ // This is a duplicate connection.
+ Loge("Duplicate connection to a connected Channel " + channel_name);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
+ } else {
+ // Connecting to a registered channel.
+ if (DEBUG) Log.d(TAG, "Emulator is connected to a registered Channel " + channel_name);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_CONNECTED);
+ }
+ } else {
+ // Make sure that there are no other channel connections for this
+ // channel name.
+ if (getPendingSocket(channel_name) != null) {
+ // This is a duplicate.
+ Loge("Duplicate connection to a pending Socket " + channel_name);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
+ } else {
+ // Connecting to a channel that has not been registered yet.
+ if (DEBUG) Log.d(TAG, "Emulator is connected to a pending Socket " + channel_name);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_NOPORT);
+ sk = new Socket(sock, channel_name, endian);
+ mPendingSockets.add(sk);
+ }
+ }
+
+ // Send handshake reply.
+ sock.getOutputStream().write(handshake.array());
+
+ // If a disconnected channel for emulator connection has been found,
+ // connect it.
+ if (channel != null && !channel.isConnected()) {
+ if (DEBUG) Log.d(TAG, "Connecting Channel " + channel_name + " with emulator.");
+ sk = new Socket(sock, channel_name, endian);
+ channel.connect(sk);
+ }
+
+ mService.notifyStatusChanged();
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
deleted file mode 100755
index f7682f8..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
+++ /dev/null
@@ -1,968 +0,0 @@
-/*
- * Copyright (C) 2011 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.tools.sdkcontroller.lib;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.ClosedSelectorException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.nio.channels.spi.SelectorProvider;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.Vector;
-
-import android.util.Log;
-
-/**
- * Encapsulates a connection with the emulator. The connection is established
- * over a TCP port forwarding enabled with 'adb forward' command.
- * <p/>
- * Communication with the emulator is performed via two socket channels
- * connected to the forwarded TCP port. One channel is a query channel that is
- * intended solely for receiving queries from the emulator. Another channel is
- * an event channel that is intended for sending notification messages (events)
- * to the emulator.
- * <p/>
- * EmulatorConnection is considered to be "connected" when both channels are connected.
- * EmulatorConnection is considered to be "disconnected" when connection with any of the
- * channels is lost.
- * <p/>
- * Instance of this class is operational only for a single connection with the
- * emulator. Once connection is established and then lost, a new instance of
- * this class must be created to establish new connection.
- * <p/>
- * Note that connection with the device over TCP port forwarding is extremely
- * fragile at the moment. For whatever reason the connection is even more
- * fragile if device uses asynchronous sockets (based on java.nio API). So, to
- * address this issue EmulatorConnection class implements two types of connections. One is
- * using synchronous sockets, and another is using asynchronous sockets. The
- * type of connection is selected when EmulatorConnection instance is created (see
- * comments to EmulatorConnection's constructor).
- * <p/>
- * According to the exchange protocol with the emulator, queries, responses to
- * the queries, and notification messages are all zero-terminated strings.
- */
-public class EmulatorConnection {
- /** Defines connection types supported by the EmulatorConnection class. */
- public enum EmulatorConnectionType {
- /** Use asynchronous connection (based on java.nio API). */
- ASYNC_CONNECTION,
- /** Use synchronous connection (based on synchronous Socket objects). */
- SYNC_CONNECTION,
- }
-
- /** TCP port reserved for the sensors emulation. */
- public static final int SENSORS_PORT = 1968;
- /** TCP port reserved for the multitouch emulation. */
- public static final int MULTITOUCH_PORT = 1969;
- /** Tag for logging messages. */
- private static final String TAG = "EmulatorConnection";
- /** EmulatorConnection events listener. */
- private EmulatorListener mListener;
- /** I/O selector (looper). */
- private Selector mSelector;
- /** Server socket channel. */
- private ServerSocketChannel mServerSocket;
- /** Query channel. */
- private EmulatorChannel mQueryChannel;
- /** Event channel. */
- private EmulatorChannel mEventChannel;
- /** Selector for the connection type. */
- private EmulatorConnectionType mConnectionType;
- /** Connection status */
- private boolean mIsConnected = false;
- /** Disconnection status */
- private boolean mIsDisconnected = false;
- /** Exit I/O loop flag. */
- private boolean mExitIoLoop = false;
- /** Disconnect flag. */
- private boolean mDisconnect = false;
-
- /***************************************************************************
- * EmulatorChannel - Base class for sync / async channels.
- **************************************************************************/
-
- /**
- * Encapsulates a base class for synchronous and asynchronous communication
- * channels.
- */
- private abstract class EmulatorChannel {
- /** Identifier for a query channel type. */
- private static final String QUERY_CHANNEL = "query";
- /** Identifier for an event channel type. */
- private static final String EVENT_CHANNEL = "event";
- /** BLOB query string. */
- private static final String BLOBL_QUERY = "$BLOB";
-
- /***********************************************************************
- * Abstract API
- **********************************************************************/
-
- /**
- * Sends a message via this channel.
- *
- * @param msg Zero-terminated message string to send.
- */
- public abstract void sendMessage(String msg) throws IOException;
-
- /**
- * Closes this channel.
- */
- abstract public void closeChannel() throws IOException;
-
- /***********************************************************************
- * Public API
- **********************************************************************/
-
- /**
- * Constructs EmulatorChannel instance.
- */
- public EmulatorChannel() {
- }
-
- /**
- * Handles a query received in this channel.
- *
- * @param socket A socket through which the query has been received.
- * @param query_str Query received from this channel. All queries are
- * formatted as such: <query>:<query parameters> where -
- * <query> Is a query name that identifies the query, and -
- * <query parameters> represent parameters for the query.
- * Query name and query parameters are separated with a ':'
- * character.
- */
- public void onQueryReceived(Socket socket, String query_str) throws IOException {
- String query, query_param, response;
-
- // Lets see if query has parameters.
- int sep = query_str.indexOf(':');
- if (sep == -1) {
- // Query has no parameters.
- query = query_str;
- query_param = "";
- } else {
- // Separate query name from its parameters.
- query = query_str.substring(0, sep);
- // Make sure that substring after the ':' does contain
- // something, otherwise the query is paramless.
- query_param = (sep < (query_str.length() - 1)) ? query_str.substring(sep + 1) : "";
- }
-
- // Handle the query, obtain response string, and reply it back to
- // the emulator. Note that there is one special query: $BLOB, that
- // requires reading of a byte array of data first. The size of the
- // array is defined by the query parameter.
- if (query.compareTo(BLOBL_QUERY) == 0) {
- // This is the BLOB query. It must have a parameter which
- // contains byte size of the blob.
- final int array_size = Integer.parseInt(query_param);
- if (array_size > 0) {
- // Read data from the query's socket.
- byte[] array = new byte[array_size];
- final int transferred = readSocketArray(socket, array);
- if (transferred == array_size) {
- // Handle blob query.
- response = onBlobQuery(array);
- } else {
- response = "ko:Transfer failure\0";
- }
- } else {
- response = "ko:Invalid parameter\0";
- }
- } else {
- response = onQuery(query, query_param);
- if (response.length() == 0 || response.charAt(0) == '\0') {
- Logw("No response to the query " + query_str);
- }
- }
-
- if (response.length() != 0) {
- if (response.charAt(response.length() - 1) != '\0') {
- Logw("Response '" + response + "' to query '" + query
- + "' does not contain zero-terminator.");
- }
- sendMessage(response);
- }
- }
- } // EmulatorChannel
-
- /***************************************************************************
- * EmulatorSyncChannel - Implements a synchronous channel.
- **************************************************************************/
-
- /**
- * Encapsulates a synchronous communication channel with the emulator.
- */
- private class EmulatorSyncChannel extends EmulatorChannel {
- /** Communication socket. */
- private Socket mSocket;
-
- /**
- * Constructs EmulatorSyncChannel instance.
- *
- * @param socket Connected ('accept'ed) communication socket.
- */
- public EmulatorSyncChannel(Socket socket) {
- mSocket = socket;
- // Start the reader thread.
- new Thread(new Runnable() {
- @Override
- public void run() {
- theReader();
- }
- }, "EmuSyncChannel").start();
- }
-
- /***********************************************************************
- * Abstract API implementation
- **********************************************************************/
-
- /**
- * Sends a message via this channel.
- *
- * @param msg Zero-terminated message string to send.
- */
- @Override
- public void sendMessage(String msg) throws IOException {
- if (msg.charAt(msg.length() - 1) != '\0') {
- Logw("Missing zero-terminator in message '" + msg + "'");
- }
- mSocket.getOutputStream().write(msg.getBytes());
- }
-
- /**
- * Closes this channel.
- */
- @Override
- public void closeChannel() throws IOException {
- mSocket.close();
- }
-
- /***********************************************************************
- * EmulatorSyncChannel implementation
- **********************************************************************/
-
- /**
- * The reader thread: loops reading and dispatching queries.
- */
- private void theReader() {
- try {
- for (;;) {
- String query = readSocketString(mSocket);
- onQueryReceived(mSocket, query);
- }
- } catch (IOException e) {
- onLostConnection();
- }
- }
- } // EmulatorSyncChannel
-
- /***************************************************************************
- * EmulatorAsyncChannel - Implements an asynchronous channel.
- **************************************************************************/
-
- /**
- * Encapsulates an asynchronous communication channel with the emulator.
- */
- private class EmulatorAsyncChannel extends EmulatorChannel {
- /** Communication socket channel. */
- private SocketChannel mChannel;
- /** I/O selection key for this channel. */
- private SelectionKey mSelectionKey;
- /** Accumulator for the query string received in this channel. */
- private String mQuery = "";
- /**
- * Preallocated character reader that is used when data is read from
- * this channel. See 'onRead' method for more details.
- */
- private ByteBuffer mIn = ByteBuffer.allocate(1);
- /**
- * Currently sent notification message(s). See 'sendMessage', and
- * 'onWrite' methods for more details.
- */
- private ByteBuffer mOut;
- /**
- * Array of pending notification messages. See 'sendMessage', and
- * 'onWrite' methods for more details.
- */
- private Vector<String> mNotifications = new Vector<String>();
-
- /**
- * Constructs EmulatorAsyncChannel instance.
- *
- * @param channel Accepted socket channel to use for communication.
- * @throws IOException
- */
- private EmulatorAsyncChannel(SocketChannel channel) throws IOException {
- // Mark character reader at the beginning, so we can reset it after
- // next read character has been pulled out from the buffer.
- mIn.mark();
-
- // Configure communication channel as non-blocking, and register
- // it with the I/O selector for reading.
- mChannel = channel;
- mChannel.configureBlocking(false);
- mSelectionKey = mChannel.register(mSelector, SelectionKey.OP_READ, this);
- // Start receiving read I/O.
- mSelectionKey.selector().wakeup();
- }
-
- /***********************************************************************
- * Abstract API implementation
- **********************************************************************/
-
- /**
- * Sends a message via this channel.
- *
- * @param msg Zero-terminated message string to send.
- */
- @Override
- public void sendMessage(String msg) throws IOException {
- if (msg.charAt(msg.length() - 1) != '\0') {
- Logw("Missing zero-terminator in message '" + msg + "'");
- }
- synchronized (this) {
- if (mOut != null) {
- // Channel is busy with writing another message.
- // Queue this one. It will be picked up later when current
- // write operation is completed.
- mNotifications.add(msg);
- return;
- }
-
- // No other messages are in progress. Send this one outside of
- // the lock.
- mOut = ByteBuffer.wrap(msg.getBytes());
- }
- mChannel.write(mOut);
-
- // Lets see if we were able to send the entire message.
- if (mOut.hasRemaining()) {
- // Write didn't complete. Schedule write I/O callback to
- // pick up from where this write has left.
- enableWrite();
- return;
- }
-
- // Entire message has been sent. Lets see if other messages were
- // queued while we were busy sending this one.
- for (;;) {
- synchronized (this) {
- // Dequeue message that was yielding to this write.
- if (!dequeueMessage()) {
- // Writing is over...
- disableWrite();
- mOut = null;
- return;
- }
- }
-
- // Send queued message.
- mChannel.write(mOut);
-
- // Lets see if we were able to send the entire message.
- if (mOut.hasRemaining()) {
- // Write didn't complete. Schedule write I/O callback to
- // pick up from where this write has left.
- enableWrite();
- return;
- }
- }
- }
-
- /**
- * Closes this channel.
- */
- @Override
- public void closeChannel() throws IOException {
- mSelectionKey.cancel();
- synchronized (this) {
- mNotifications.clear();
- }
- mChannel.close();
- }
-
- /***********************************************************************
- * EmulatorAsyncChannel implementation
- **********************************************************************/
-
- /**
- * Reads data from the channel. This method is invoked from the I/O loop
- * when data is available for reading on this channel. When reading from
- * a channel we read character-by-character, building the query string
- * until zero-terminator is read. When zero-terminator is read, we
- * handle the query, and start building the new query string.
- *
- * @throws IOException
- */
- private void onRead() throws IOException, ClosedChannelException {
- int count = mChannel.read(mIn);
- Logv("onRead: " + count);
- while (count == 1) {
- final char c = (char) mIn.array()[0];
- mIn.reset();
- if (c == '\0') {
- // Zero-terminator is read. Process the query, and reset
- // the query string.
- onQueryReceived(mChannel.socket(), mQuery);
- mQuery = "";
- } else {
- // Continue building the query string.
- mQuery += c;
- }
- count = mChannel.read(mIn);
- }
-
- if (count == -1) {
- // Channel got disconnected.
- throw new ClosedChannelException();
- } else {
- // "Don't block" in effect. Will get back to reading as soon as
- // read I/O is available.
- assert (count == 0);
- }
- }
-
- /**
- * Writes data to the channel. This method is ivnoked from the I/O loop
- * when data is available for writing on this channel.
- *
- * @throws IOException
- */
- private void onWrite() throws IOException {
- if (mOut != null && mOut.hasRemaining()) {
- // Continue writing to the channel.
- mChannel.write(mOut);
- if (mOut.hasRemaining()) {
- // Write is still incomplete. Come back to it when write I/O
- // becomes available.
- return;
- }
- }
-
- // We're done with the current message. Lets see if we've
- // accumulated some more while this write was in progress.
- synchronized (this) {
- // Dequeue next message into mOut.
- if (!dequeueMessage()) {
- // Nothing left to write.
- disableWrite();
- mOut = null;
- return;
- }
- // We don't really want to run a big loop here, flushing the
- // message queue. The reason is that we're inside the I/O loop,
- // so we don't want to block others for long. So, we will
- // continue with queue flushing next time we're picked up by
- // write I/O event.
- }
- }
-
- /**
- * Dequeues messages that were yielding to the write in progress.
- * Messages will be dequeued directly to the mOut, so it's ready to be
- * sent when this method returns. NOTE: This method must be called from
- * within synchronized(this).
- *
- * @return true if messages were dequeued, or false if message queue was
- * empty.
- */
- private boolean dequeueMessage() {
- // It's tempting to dequeue all messages here, but in practice it's
- // less performant than dequeuing just one.
- if (!mNotifications.isEmpty()) {
- mOut = ByteBuffer.wrap(mNotifications.remove(0).getBytes());
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Enables write I/O callbacks.
- */
- private void enableWrite() {
- mSelectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
- // Looks like we must wake up the selector. Otherwise it's not going
- // to immediately pick up on the change that we just made.
- mSelectionKey.selector().wakeup();
- }
-
- /**
- * Disables write I/O callbacks.
- */
- private void disableWrite() {
- mSelectionKey.interestOps(SelectionKey.OP_READ);
- }
- } // EmulatorChannel
-
- /***************************************************************************
- * EmulatorConnection public API
- **************************************************************************/
-
- /**
- * Constructs EmulatorConnection instance.
- * Caller must call {@link #connect(int, EmulatorConnectionType)} afterwards.
- *
- * @param listener EmulatorConnection event listener. Must not be null.
- */
- public EmulatorConnection(EmulatorListener listener) {
- mListener = listener;
- }
-
- /**
- * Connects the EmulatorConnection instance.
- * <p/>
- * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
- * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
- *
- * @param port TCP port where emulator connects.
- * @param ctype Defines connection type to use (sync / async). See comments
- * to EmulatorConnection class for more info.
- * @return This object for chaining calls.
- */
- public EmulatorConnection connect(int port, EmulatorConnectionType ctype) {
- constructEmulator(port, ctype);
- return this;
- }
-
-
- /**
- * Disconnects the emulator.
- */
- public void disconnect() {
- mDisconnect = true;
- mSelector.wakeup();
- }
-
- /**
- * Constructs EmulatorConnection instance.
- * <p/>
- * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
- * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
- * <p/>
- * On error or success, this calls
- * {@link EmulatorListener#onEmulatorBindResult(boolean, Exception)} to indicate whether
- * the socket was properly bound.
- * The IO loop will start after the method reported a successful bind.
- *
- * @param port TCP port where emulator connects.
- * @param ctype Defines connection type to use (sync / async). See comments
- * to EmulatorConnection class for more info.
- */
- private void constructEmulator(final int port, EmulatorConnectionType ctype) {
-
- try {
- mConnectionType = ctype;
- // Create I/O looper.
- mSelector = SelectorProvider.provider().openSelector();
-
- // Create non-blocking server socket that would listen for connections,
- // and bind it to the given port on the local host.
- mServerSocket = ServerSocketChannel.open();
- mServerSocket.configureBlocking(false);
- InetAddress local = InetAddress.getLocalHost();
- final InetSocketAddress address = new InetSocketAddress(local, port);
- mServerSocket.socket().bind(address);
-
- // Register 'accept' I/O on the server socket.
- mServerSocket.register(mSelector, SelectionKey.OP_ACCEPT);
- } catch (IOException e) {
- mListener.onEmulatorBindResult(false, e);
- return;
- }
-
- mListener.onEmulatorBindResult(true, null);
- Logv("EmulatorConnection listener is created for port " + port);
-
- // Start I/O looper and dispatcher.
- new Thread(new Runnable() {
- @Override
- public void run() {
- runIOLooper();
- }
- }, "EmuCnxIoLoop").start();
- }
-
- /**
- * Sends a notification message to the emulator via 'event' channel.
- * <p/>
- * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
- * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
- *
- * @param msg
- */
- public void sendNotification(String msg) {
- if (mIsConnected) {
- try {
- mEventChannel.sendMessage(msg);
- } catch (IOException e) {
- onLostConnection();
- }
- } else {
- Logw("Attempt to send '" + msg + "' to a disconnected EmulatorConnection");
- }
- }
-
- /**
- * Sets or removes a listener to the events generated by this emulator
- * instance.
- *
- * @param listener Listener to set. Passing null with this parameter will
- * remove the current listener (if there was one).
- */
- public void setEmulatorListener(EmulatorListener listener) {
- synchronized (this) {
- mListener = listener;
- }
- // Make sure that new listener knows the connection status.
- if (mListener != null) {
- if (mIsConnected) {
- mListener.onEmulatorConnected();
- } else if (mIsDisconnected) {
- mListener.onEmulatorDisconnected();
- }
- }
- }
-
- /***************************************************************************
- * EmulatorConnection events
- **************************************************************************/
-
- /**
- * Called when emulator is connected. NOTE: This method is called from the
- * I/O loop, so all communication with the emulator will be "on hold" until
- * this method returns.
- */
- private void onConnected() {
- EmulatorListener listener;
- synchronized (this) {
- listener = mListener;
- }
- if (listener != null) {
- listener.onEmulatorConnected();
- }
- }
-
- /**
- * Called when emulator is disconnected. NOTE: This method could be called
- * from the I/O loop, in which case all communication with the emulator will
- * be "on hold" until this method returns.
- */
- private void onDisconnected() {
- EmulatorListener listener;
- synchronized (this) {
- listener = mListener;
- }
- if (listener != null) {
- listener.onEmulatorDisconnected();
- }
- }
-
- /**
- * Called when a query is received from the emulator. NOTE: This method
- * could be called from the I/O loop, in which case all communication with
- * the emulator will be "on hold" until this method returns.
- *
- * @param query Name of the query received from the emulator.
- * @param param Query parameters.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
- */
- private String onQuery(String query, String param) {
- EmulatorListener listener;
- synchronized (this) {
- listener = mListener;
- }
- if (listener != null) {
- return listener.onEmulatorQuery(query, param);
- } else {
- return "ko:Service is detached.\0";
- }
- }
-
- /**
- * Called when a BLOB query is received from the emulator. NOTE: This method
- * could be called from the I/O loop, in which case all communication with
- * the emulator will be "on hold" until this method returns.
- *
- * @param array Array containing blob data.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
- */
- private String onBlobQuery(byte[] array) {
- EmulatorListener listener;
- synchronized (this) {
- listener = mListener;
- }
- if (listener != null) {
- return listener.onEmulatorBlobQuery(array);
- } else {
- return "ko:Service is detached.\0";
- }
- }
-
- /***************************************************************************
- * EmulatorConnection implementation
- **************************************************************************/
-
- /**
- * Loops on the selector, handling and dispatching I/O events.
- */
- private void runIOLooper() {
- try {
- Logv("Waiting on EmulatorConnection to connect...");
- // Check mExitIoLoop before calling 'select', and after in order to
- // detect condition when mSelector has been waken up to exit the
- // I/O loop.
- while (!mExitIoLoop && !mDisconnect &&
- mSelector.select() >= 0 &&
- !mExitIoLoop && !mDisconnect) {
- Set<SelectionKey> readyKeys = mSelector.selectedKeys();
- Iterator<SelectionKey> i = readyKeys.iterator();
- while (i.hasNext()) {
- SelectionKey sk = i.next();
- i.remove();
- if (sk.isAcceptable()) {
- final int ready = sk.readyOps();
- if ((ready & SelectionKey.OP_ACCEPT) != 0) {
- // Accept new connection.
- onAccept(((ServerSocketChannel) sk.channel()).accept());
- }
- } else {
- // Read / write events are expected only on a 'query',
- // or 'event' asynchronous channels.
- EmulatorAsyncChannel esc = (EmulatorAsyncChannel) sk.attachment();
- if (esc != null) {
- final int ready = sk.readyOps();
- if ((ready & SelectionKey.OP_READ) != 0) {
- // Read data.
- esc.onRead();
- }
- if ((ready & SelectionKey.OP_WRITE) != 0) {
- // Write data.
- esc.onWrite();
- }
- } else {
- Loge("No emulator channel found in selection key.");
- }
- }
- }
- }
- } catch (ClosedSelectorException e) {
- } catch (IOException e) {
- }
-
- // Destroy connection on any I/O failure.
- if (!mExitIoLoop) {
- onLostConnection();
- }
- }
-
- /**
- * Accepts new connection from the emulator.
- *
- * @param channel Connecting socket channel.
- * @throws IOException
- */
- private void onAccept(SocketChannel channel) throws IOException {
- // Make sure we're not connected yet.
- if (mEventChannel != null && mQueryChannel != null) {
- // We don't accept any more connections after both channels were
- // connected.
- Loge("EmulatorConnection is connecting to the already connected instance.");
- channel.close();
- return;
- }
-
- // According to the protocol, each channel identifies itself as a query
- // or event channel, sending a "cmd", or "event" message right after
- // the connection.
- Socket socket = channel.socket();
- String socket_type = readSocketString(socket);
- if (socket_type.contentEquals(EmulatorChannel.QUERY_CHANNEL)) {
- if (mQueryChannel == null) {
- // TODO: Find better way to do that!
- socket.getOutputStream().write("ok\0".getBytes());
- if (mConnectionType == EmulatorConnectionType.ASYNC_CONNECTION) {
- mQueryChannel = new EmulatorAsyncChannel(channel);
- Logv("Asynchronous query channel is registered.");
- } else {
- mQueryChannel = new EmulatorSyncChannel(channel.socket());
- Logv("Synchronous query channel is registered.");
- }
- } else {
- // TODO: Find better way to do that!
- Loge("Duplicate query channel.");
- socket.getOutputStream().write("ko:Duplicate\0".getBytes());
- channel.close();
- return;
- }
- } else if (socket_type.contentEquals(EmulatorChannel.EVENT_CHANNEL)) {
- if (mEventChannel == null) {
- // TODO: Find better way to do that!
- socket.getOutputStream().write("ok\0".getBytes());
- if (mConnectionType == EmulatorConnectionType.ASYNC_CONNECTION) {
- mEventChannel = new EmulatorAsyncChannel(channel);
- Logv("Asynchronous event channel is registered.");
- } else {
- mEventChannel = new EmulatorSyncChannel(channel.socket());
- Logv("Synchronous event channel is registered.");
- }
- } else {
- Loge("Duplicate event channel.");
- socket.getOutputStream().write("ko:Duplicate\0".getBytes());
- channel.close();
- return;
- }
- } else {
- Loge("Unknown channel is connecting: " + socket_type);
- socket.getOutputStream().write("ko:Unknown channel type\0".getBytes());
- channel.close();
- return;
- }
-
- // Lets see if connection is complete...
- if (mEventChannel != null && mQueryChannel != null) {
- // When both, query and event channels are connected, the emulator
- // is considered to be connected.
- Logv("... EmulatorConnection is connected.");
- mIsConnected = true;
- onConnected();
- }
- }
-
- /**
- * Called when connection to any of the channels has been lost.
- */
- private void onLostConnection() {
- // Since we're multithreaded, there can be multiple "bangs" from those
- // threads. We should only handle the first one.
- boolean first_time = false;
- synchronized (this) {
- first_time = mIsConnected;
- mIsConnected = false;
- mIsDisconnected = true;
- }
- if (first_time) {
- Logw("Connection with the emulator is lost!");
- // Close all channels, exit the I/O loop, and close the selector.
- try {
- if (mEventChannel != null) {
- mEventChannel.closeChannel();
- }
- if (mQueryChannel != null) {
- mQueryChannel.closeChannel();
- }
- if (mServerSocket != null) {
- mServerSocket.close();
- }
- if (mSelector != null) {
- mExitIoLoop = true;
- mSelector.wakeup();
- mSelector.close();
- }
- } catch (IOException e) {
- Loge("onLostConnection exception: " + e.getMessage());
- }
-
- // Notify the app about lost connection.
- onDisconnected();
- }
- }
-
- /**
- * Reads zero-terminated string from a synchronous socket.
- *
- * @param socket Socket to read string from. Must be a synchronous socket.
- * @return String read from the socket.
- * @throws IOException
- */
- private static String readSocketString(Socket socket) throws IOException {
- String str = "";
-
- // Current characted received from the input stream.
- int current_byte = 0;
-
- // With port forwarding there is no reliable way how to detect
- // socket disconnection, other than checking on the input stream
- // to die ("end of stream" condition). That condition is reported
- // when input stream's read() method returns -1.
- while (socket.isConnected() && current_byte != -1) {
- // Character by character read the input stream, and accumulate
- // read characters in the command string. The end of the command
- // is indicated with zero character.
- current_byte = socket.getInputStream().read();
- if (current_byte != -1) {
- if (current_byte == 0) {
- // String is completed.
- return str;
- } else {
- // Append read character to the string.
- str += (char) current_byte;
- }
- }
- }
-
- // Got disconnected!
- throw new ClosedChannelException();
- }
-
- /**
- * Reads a block of data from a socket.
- *
- * @param socket Socket to read data from. Must be a synchronous socket.
- * @param array Array where to read data.
- * @return Number of bytes read from the socket, or -1 on an error.
- * @throws IOException
- */
- private static int readSocketArray(Socket socket, byte[] array) throws IOException {
- int in = 0;
- while (in < array.length) {
- final int ret = socket.getInputStream().read(array, in, array.length - in);
- if (ret == -1) {
- // Got disconnected!
- throw new ClosedChannelException();
- }
- in += ret;
- }
- return in;
- }
-
- /***************************************************************************
- * Logging wrappers
- **************************************************************************/
-
- private void Loge(String log) {
- Log.e(TAG, log);
- }
-
- private void Logw(String log) {
- Log.w(TAG, log);
- }
-
- private void Logv(String log) {
- Log.v(TAG, log);
- }
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java
deleted file mode 100644
index 4d2a19f..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2011 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.tools.sdkcontroller.lib;
-
-
-/**
- * Encapsulates a listener to emulator events. An object implementing this
- * interface must be registered with the EmulatorConnection instance via
- * setEmulatorListener method of the EmulatorConnection class.
- */
-public interface EmulatorListener {
-
- /**
- * Called as a side effect of constructing a new {@link EmulatorConnection} to
- * indicate whether the when emulator is bound with its communication socket.
- *
- * @param success True if the socket bind was successful.
- * False when the socket bind failed.
- * @param e Any exception thrown whilst trying to bind to the communication socket.
- * Null if there's no exception (typically when {@code success==true}).
- */
- public void onEmulatorBindResult(boolean success, Exception e);
-
- /**
- * Called when emulator is connected. NOTE: This method is called from the
- * I/O loop, so all communication with the emulator will be "on hold" until
- * this method returns.
- */
- public void onEmulatorConnected();
-
- /**
- * Called when emulator is disconnected. NOTE: This method could be called
- * from the I/O loop, in which case all communication with the emulator will
- * be "on hold" until this method returns.
- */
- public void onEmulatorDisconnected();
-
- /**
- * Called when a query is received from the emulator. NOTE: This method is
- * called from the I/O loop, so all communication with the emulator will be
- * "on hold" until this method returns.
- *
- * @param query Name of the query received from the emulator.
- * @param param Query parameters.
- * @return Zero-terminated reply string. If not an empty string is returned,
- * it must be formatted as such: "ok|ko[:reply data]"
- */
- public String onEmulatorQuery(String query, String param);
-
- /**
- * Called when a BLOB query is received from the emulator. NOTE: This method
- * is called from the I/O loop, so all communication with the emulator will
- * be "on hold" until this method returns.
- *
- * @param array contains BLOB data for the query.
- * @return Zero-terminated reply string. If not an empty string is returned,
- * it must be formatted as such: "ok|ko[:reply data]"
- */
- public String onEmulatorBlobQuery(byte[] array);
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
new file mode 100644
index 0000000..b4c6593
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
@@ -0,0 +1,142 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.tools.sdkcontroller.lib;
+
+/**
+ * Contains declarations of constants that are tied to emulator implementation.
+ * These constants can be changed only simultaneously in both places.
+ */
+public final class ProtocolConstants {
+ /*
+ * Constants related to data transfer.
+ */
+
+ /** Signature of a packet sent via SDK controller socket ('SDKC') */
+ public static final int PACKET_SIGNATURE = 0x53444B43;
+
+ /*
+ * Header sizes for packets sent / received by SDK controller emulator.
+ */
+
+ /**
+ * 12 bytes (3 ints) for the packet header:
+ * <p/>
+ * - Signature.
+ * <p/>
+ * - Total packet size.
+ * <p/>
+ * - Packet type.
+ */
+ public static final int PACKET_HEADER_SIZE = 12;
+ /**
+ * 16 bytes (4 ints) for the message header:
+ * <p/>
+ * - Common packet header.
+ * <p/>
+ * - Message type.
+ */
+ public static final int MESSAGE_HEADER_SIZE = 16;
+ /**
+ * 20 bytes (5 ints) for the query header:
+ * <p/>
+ * - Common packet header.
+ * <p/>
+ * - Query ID.
+ * <p/>
+ * - Query type.
+ */
+ public static final int QUERY_HEADER_SIZE = 20;
+ /**
+ * 16 bytes (4 ints) for the query response:
+ * <p/>
+ * - Common packet header.
+ * <p/>
+ * - Query ID.
+ */
+ public static final int QUERY_RESP_HEADER_SIZE = 16;
+
+ /*
+ * Types of packets transferred via SDK Controller channel.
+ */
+
+ /** Packet is a message. */
+ public static final int PACKET_TYPE_MESSAGE = 1;
+ /** Packet is a query. */
+ public static final int PACKET_TYPE_QUERY = 2;
+ /** Packet is a response to a query. */
+ public static final int PACKET_TYPE_QUERY_RESPONSE = 3;
+
+ /*
+ * Constants related to handshake protocol between the emulator and a channel.
+ */
+
+ /**
+ * Query type for a special "handshake" query.
+ * <p/>
+ * When emulator connects to SDK controller, the first thing that goes
+ * through the socket is a special "handshake" query that delivers channel name
+ * to the service.
+ */
+ public static final int QUERY_HANDSHAKE = -1;
+ /**
+ * Handshake query response on condition that service-side channel is available
+ * (registered).
+ */
+ public static final int HANDSHAKE_RESP_CONNECTED = 0;
+ /**
+ * Handshake query response on condition that service-side channel is not
+ * available (not registered).
+ */
+ public static final int HANDSHAKE_RESP_NOPORT = 1;
+ /**
+ * Handshake query response on condition that there is already an existing
+ * emulator connection for this channel. Emulator should stop connection
+ * attempts in this case.
+ */
+ public static final int HANDSHAKE_RESP_DUP = -1;
+ /** Response to an unknown handshake query type. */
+ public static final int HANDSHAKE_RESP_QUERY_UNKNOWN = -2;
+
+ /*
+ * Constants related to multi-touch emulation.
+ */
+
+ /** Received frame is JPEG image. */
+ public static final int MT_FRAME_JPEG = 1;
+ /** Received frame is RGB565 bitmap. */
+ public static final int MT_FRAME_RGB565 = 2;
+ /** Received frame is RGB888 bitmap. */
+ public static final int MT_FRAME_RGB888 = 3;
+
+ /** Pointer(s) moved. */
+ public static final int MT_MOVE = 1;
+ /** First pointer down message. */
+ public static final int MT_FISRT_DOWN = 2;
+ /** Last pointer up message. */
+ public static final int MT_LAST_UP = 3;
+ /** Pointer down message. */
+ public static final int MT_POINTER_DOWN = 4;
+ /** Pointer up message. */
+ public static final int MT_POINTER_UP = 5;
+ /** Sends framebuffer update. */
+ public static final int MT_FB_UPDATE = 6;
+ /** Size of an event entry in the touch event message to the emulator. */
+ public static final int MT_EVENT_ENTRY_SIZE = 16;
+
+ /*
+ * Constants related to sensor emulation.
+ */
+
+ /** Query type for a query that should return the list of available sensors. */
+ public static final int SENSORS_QUERY_LIST = 1;
+ /** Message that starts sensor emulation. */
+ public static final int SENSORS_START = 1;
+ /** Message that stops sensor emulation. */
+ public static final int SENSORS_STOP = 2;
+ /** Message that enables emulation of a particular sensor. */
+ public static final int SENSORS_ENABLE = 3;
+ /** Message that disables emulation of a particular sensor. */
+ public static final int SENSORS_DISABLE = 4;
+ /** Message that delivers sensor events to emulator. */
+ public static final int SENSORS_SENSOR_EVENT = 5;
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
new file mode 100644
index 0000000..08e6b28
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2012 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.tools.sdkcontroller.lib;
+
+import android.net.LocalSocket;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+
+/**
+ * Encapsulates a connection with the emulator over a UNIX-domain socket.
+ */
+public class Socket {
+ /** UNIX-domain socket connected with the emulator. */
+ private LocalSocket mSocket = null;
+ /** Channel name for the connection established via this socket. */
+ private String mChannelName;
+ /** Endianness of data transferred in this connection. */
+ private ByteOrder mEndian;
+
+ /** Tag for message logging. */
+ private static final String TAG = "SdkControllerSocket";
+ /** Controls debug log. */
+ private static boolean DEBUG = false;
+
+ /**
+ * Constructs Socket instance.
+ *
+ * @param socket Socket connection with the emulator.
+ * @param name Channel port name for this connection.
+ * @param endian Endianness of data transferred in this connection.
+ */
+ public Socket(LocalSocket socket, String name, ByteOrder endian) {
+ mSocket = socket;
+ mChannelName = name;
+ mEndian = endian;
+ if (DEBUG) Log.d(TAG, "Socket is constructed for " + mChannelName);
+ }
+
+ /**
+ * Gets connection status of this socket.
+ *
+ * @return true if socket is connected, or false if socket is not connected.
+ */
+ public boolean isConnected() {
+ return mSocket != null;
+ }
+
+ /**
+ * Gets channel name for this socket.
+ *
+ * @return Channel name for this socket.
+ */
+ public String getChannelName() {
+ return mChannelName;
+ }
+
+ /**
+ * Gets endianness of data transferred via this socket.
+ *
+ * @return Endianness of data transferred via this socket.
+ */
+ public ByteOrder getEndian() {
+ return mEndian;
+ }
+
+ /**
+ * Sends data to the socket.
+ *
+ * @param data Data to send. Data size is defined by the length of the
+ * array.
+ * @throws IOException
+ */
+ public void send(byte[] data) throws IOException {
+ // Use local copy of the socket, ensuring it's not going to NULL while
+ // we're working with it. If it gets closed, while we're in the middle
+ // of data transfer - it's OK, since it will produce an exception, and
+ // the caller will gracefully handle it.
+ //
+ // Same technique is used everywhere in this class where mSocket member
+ // is touched.
+ LocalSocket socket = mSocket;
+ if (socket == null) {
+ Logw("'send' request on closed Socket " + mChannelName);
+ throw new ClosedChannelException();
+ }
+ socket.getOutputStream().write(data);
+ }
+
+ /**
+ * Sends data to the socket.
+ *
+ * @param data Data to send.
+ * @param offset The start position in data from where to get bytes.
+ * @param len The number of bytes from data to write to this socket.
+ * @throws IOException
+ */
+ public void send(byte[] data, int offset, int len) throws IOException {
+ LocalSocket socket = mSocket;
+ if (socket == null) {
+ Logw("'send' request on closed Socket " + mChannelName);
+ throw new ClosedChannelException();
+ }
+ socket.getOutputStream().write(data, offset, len);
+ }
+
+ /**
+ * Receives data from the socket.
+ *
+ * @param socket Socket from where to receive data.
+ * @param data Array where to save received data.
+ * @param len Number of bytes to receive.
+ * @throws IOException
+ */
+ public static void receive(LocalSocket socket, byte[] data, int len) throws IOException {
+ final InputStream is = socket.getInputStream();
+ int received = 0;
+ while (received != len) {
+ final int chunk = is.read(data, received, len - received);
+ if (chunk < 0) {
+ throw new IOException(
+ "I/O failure while receiving SDK controller data from socket.");
+ }
+ received += chunk;
+ }
+ }
+
+ /**
+ * Receives data from the socket.
+ *
+ * @param data Array where to save received data.
+ * @param len Number of bytes to receive.
+ * @throws IOException
+ */
+ public void receive(byte[] data, int len) throws IOException {
+ LocalSocket socket = mSocket;
+ if (socket == null) {
+ Logw("'receive' request on closed Socket " + mChannelName);
+ throw new ClosedChannelException();
+ }
+ receive(socket, data, len);
+ }
+
+ /**
+ * Receives data from the socket.
+ *
+ * @param data Array where to save received data. Data size is defined by
+ * the size of the array.
+ * @throws IOException
+ */
+ public void receive(byte[] data) throws IOException {
+ receive(data, data.length);
+ }
+
+ /**
+ * Closes the socket.
+ *
+ * @return true if socket has been closed in this call, or false if it had
+ * been already closed when this method has been called.
+ */
+ public boolean close() {
+ // This is the only place in this class where we will null the socket
+ // object. Since this method can be called concurrently from different
+ // threads, lets do this under the lock.
+ LocalSocket socket;
+ synchronized (this) {
+ socket = mSocket;
+ mSocket = null;
+ }
+ if (socket != null) {
+ try {
+ // Force all I/O to stop before closing the socket.
+ socket.shutdownInput();
+ socket.shutdownOutput();
+ socket.close();
+ if (DEBUG) Log.d(TAG, "Socket is closed for " + mChannelName);
+ return true;
+ } catch (IOException e) {
+ Loge("Exception " + e + " while closing Socket for " + mChannelName);
+ }
+ }
+ return false;
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ Log.e(TAG, log);
+ }
+
+ private void Logw(String log) {
+ Log.w(TAG, log);
+ }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
index cd35833..705bd07 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
@@ -17,9 +17,7 @@
package com.android.tools.sdkcontroller.service;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import android.app.Activity;
import android.app.Notification;
@@ -33,33 +31,32 @@
import com.android.tools.sdkcontroller.R;
import com.android.tools.sdkcontroller.activities.MainActivity;
-import com.android.tools.sdkcontroller.handlers.BaseHandler;
-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
-import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;
-import com.android.tools.sdkcontroller.handlers.SensorsHandler;
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
-import com.android.tools.sdkcontroller.lib.EmulatorConnection.EmulatorConnectionType;
-import com.android.tools.sdkcontroller.lib.EmulatorListener;
+import com.android.tools.sdkcontroller.handlers.MultiTouchChannel;
+import com.android.tools.sdkcontroller.handlers.SensorChannel;
+import com.android.tools.sdkcontroller.lib.Connection;
+import com.android.tools.sdkcontroller.lib.Channel;
/**
* The background service of the SdkController.
* There can be only one instance of this.
* <p/>
- * The service manages a number of action "handlers" which can be seen as individual tasks
- * that the user might want to accomplish, for example "sending sensor data to the emulator"
- * or "sending multi-touch data and displaying an emulator screen".
+ * The service manages a number of SDK controller channels which can be seen as
+ * individual tasks that the user might want to accomplish, for example "sending
+ * sensor data to the emulator" or "sending multi-touch data and displaying an
+ * emulator screen".
* <p/>
- * Each handler currently has its own emulator connection associated to it (cf class
- * {@code EmuCnxHandler} below. However our goal is to later move to a single connection channel
- * with all data multiplexed on top of it.
+ * Each channel connects to the emulator via UNIX-domain socket that is bound to
+ * "android.sdk.controller" port. Connection class provides a socket server that
+ * listens to emulator connections on this port, and binds new connection with a
+ * channel, based on channel name.
* <p/>
- * All the handlers are created when the service starts, and whether the emulator connection
- * is successful or not, and whether there's any UI to control it. It's up to the handlers
- * to deal with these specific details. <br/>
- * For example the {@link SensorsHandler} initializes its sensor list as soon as created
- * and then tries to send data as soon as there's an emulator connection.
- * On the other hand the {@link MultiTouchHandler} lays dormant till there's an UI interacting
- * with it.
+ * All the channels are created when the service starts, and whether the emulator
+ * connection is successful or not, and whether there's any UI to control it.
+ * It's up to the channels to deal with these specific details. <br/>
+ * For example the {@link SensorChannel} initializes its sensor list as soon as
+ * created and then tries to send data as soon as there's an emulator
+ * connection. On the other hand the {@link MultiTouchChannel} lays dormant till
+ * there's an UI interacting with it.
*/
public class ControllerService extends Service {
@@ -68,12 +65,17 @@
* http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
*/
+ /** Tag for logging messages. */
public static String TAG = ControllerService.class.getSimpleName();
+ /** Controls debug log. */
private static boolean DEBUG = true;
-
/** Identifier for the notification. */
private static int NOTIF_ID = 'S' << 24 + 'd' << 16 + 'k' << 8 + 'C' << 0;
+ /** Connection to the emulator. */
+ public Connection mConnection;
+
+
private final IBinder mBinder = new ControllerBinder();
private List<ControllerListener> mListeners = new ArrayList<ControllerListener>();
@@ -86,8 +88,6 @@
/** Internal error reported by the service. */
private String mServiceError = "";
- private final Set<EmuCnxHandler> mHandlers = new HashSet<ControllerService.EmuCnxHandler>();
-
/**
* Interface that the service uses to notify binded activities.
* <p/>
@@ -119,7 +119,7 @@
public void addControllerListener(ControllerListener listener) {
assert listener != null;
if (listener != null) {
- synchronized(mListeners) {
+ synchronized (mListeners) {
if (!mListeners.contains(listener)) {
mListeners.add(listener);
}
@@ -134,7 +134,7 @@
*/
public void removeControllerListener(ControllerListener listener) {
assert listener != null;
- synchronized(mListeners) {
+ synchronized (mListeners) {
mListeners.remove(listener);
}
}
@@ -149,34 +149,24 @@
}
/**
- * Indicates when <em>all</all> the communication channels for all handlers
- * are properly connected.
+ * Indicates when <em>any</all> of the SDK controller channels is connected
+ * with the emulator.
*
- * @return True if all the handler's communication channels are connected.
+ * @return True if any of the SDK controller channels is connected with the
+ * emulator.
*/
public boolean isEmuConnected() {
- for (EmuCnxHandler handler : mHandlers) {
- if (!handler.isConnected()) {
- return false;
- }
- }
- return true;
+ return mConnection.isEmulatorConnected();
}
/**
- * Returns the handler for the given type.
+ * Returns the channel instance for the given type.
*
- * @param type One of the {@link HandlerType}s. Must not be null.
+ * @param name One of the channel names. Must not be null.
* @return Null if the type is not found, otherwise the handler's unique instance.
*/
- public BaseHandler getHandler(HandlerType type) {
- for (EmuCnxHandler handler : mHandlers) {
- BaseHandler h = handler.getHandler();
- if (h.getType() == type) {
- return h;
- }
- }
- return null;
+ public Channel getChannel(String name) {
+ return mConnection.getChannel(name);
}
}
@@ -220,101 +210,10 @@
super.onDestroy();
}
- // ------
-
- /**
- * Wrapper that associates one {@link EmulatorConnection} with
- * one {@link BaseHandler}. Ideally we would not need this if all
- * the action handlers were using the same port, so this wrapper
- * is just temporary.
- */
- private class EmuCnxHandler implements EmulatorListener {
-
- private EmulatorConnection mCnx;
- private boolean mConnected;
- private final BaseHandler mHandler;
-
- public EmuCnxHandler(BaseHandler handler) {
- mHandler = handler;
- }
-
- @Override
- public void onEmulatorConnected() {
- mConnected = true;
- notifyStatusChanged();
- }
-
- @Override
- public void onEmulatorDisconnected() {
- mConnected = false;
- notifyStatusChanged();
- }
-
- @Override
- public String onEmulatorQuery(String query, String param) {
- if (DEBUG) Log.d(TAG, mHandler.getType().toString() + " Query " + query);
- return mHandler.onEmulatorQuery(query, param);
- }
-
- @Override
- public String onEmulatorBlobQuery(byte[] array) {
- if (DEBUG) Log.d(TAG, mHandler.getType().toString() + " BlobQuery " + array.length);
- return mHandler.onEmulatorBlobQuery(array);
- }
-
- EmuCnxHandler connect() {
- assert mCnx == null;
-
- mCnx = new EmulatorConnection(this);
-
- // Apps targeting Honeycomb SDK can't do network IO on their main UI
- // thread. So just start the connection from a thread.
- Thread t = new Thread(new Runnable() {
- @Override
- public void run() {
- // This will call onEmulatorBindResult with the result.
- mCnx.connect(mHandler.getPort(), EmulatorConnectionType.SYNC_CONNECTION);
- }
- }, "EmuCnxH.connect-" + mHandler.getType().toString());
- t.start();
-
- return this;
- }
-
- @Override
- public void onEmulatorBindResult(boolean success, Exception e) {
- if (success) {
- mHandler.onStart(mCnx, ControllerService.this /*context*/);
- } else {
- Log.e(TAG, "EmuCnx failed for " + mHandler.getType(), e);
- String msg = mHandler.getType().toString() + " failed: " +
- (e == null ? "n/a" : e.toString());
- addError(msg);
- }
- }
-
- void disconnect() {
- if (mCnx != null) {
- mHandler.onStop();
- mCnx.disconnect();
- mCnx = null;
- }
- }
-
- boolean isConnected() {
- return mConnected;
- }
-
- public BaseHandler getHandler() {
- return mHandler;
- }
- }
-
private void disconnectAll() {
- for(EmuCnxHandler handler : mHandlers) {
- handler.disconnect();
+ if (mConnection != null) {
+ mConnection.disconnect();
}
- mHandlers.clear();
}
/**
@@ -324,9 +223,15 @@
try {
disconnectAll();
- assert mHandlers.isEmpty();
- mHandlers.add(new EmuCnxHandler(new MultiTouchHandler()).connect());
- mHandlers.add(new EmuCnxHandler(new SensorsHandler()).connect());
+ // Bind to SDK controller port, and start accepting emulator
+ // connections.
+ mConnection = new Connection(ControllerService.this);
+ mConnection.connect();
+
+ // Create and register sensors channel.
+ mConnection.registerChannel(new SensorChannel(ControllerService.this));
+ // Create and register multi-touch channel.
+ mConnection.registerChannel(new MultiTouchChannel(ControllerService.this));
} catch (Exception e) {
addError("Connection failed: " + e.toString());
}
@@ -340,15 +245,15 @@
}
private void notifyErrorChanged() {
- synchronized(mListeners) {
+ synchronized (mListeners) {
for (ControllerListener listener : mListeners) {
listener.onErrorChanged();
}
}
}
- private void notifyStatusChanged() {
- synchronized(mListeners) {
+ public void notifyStatusChanged() {
+ synchronized (mListeners) {
for (ControllerListener listener : mListeners) {
listener.onStatusChanged();
}
@@ -368,7 +273,7 @@
* An internal utility method to add a line to the error string and notify listeners.
* @param error A non-null non-empty error line. \n will be added automatically.
*/
- private void addError(String error) {
+ public void addError(String error) {
Log.e(TAG, error);
if (mServiceError.length() > 0) {
mServiceError += "\n";
@@ -397,10 +302,10 @@
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(
- this, //context
- 0, //requestCode
- intent, //intent
- 0 // pending intent flags
+ this, //context
+ 0, //requestCode
+ intent, //intent
+ 0 //pending intent flags
);
n.setLatestEventInfo(this, text, text, pi);
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java b/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
index d612769..30bccdc 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
@@ -17,6 +17,7 @@
package com.android.tools.sdkcontroller.views;
import java.io.InputStream;
+import java.nio.ByteBuffer;
import android.content.Context;
import android.graphics.Bitmap;
@@ -186,19 +187,19 @@
/**
* Constructs touch event message to be send to emulator.
*
- * @param sb String builder where to construct the message.
+ * @param bb ByteBuffer where to construct the message.
* @param event Event for which to construct the message.
* @param ptr_index Index of the motion pointer for which to construct the
* message.
*/
- public void constructEventMessage(StringBuilder sb, MotionEvent event, int ptr_index) {
- sb.append(" pid=").append(event.getPointerId(ptr_index));
+ public void constructEventMessage(ByteBuffer bb, MotionEvent event, int ptr_index) {
+ bb.putInt(event.getPointerId(ptr_index));
if (mRotateDisplay == false) {
- sb.append(" x=").append((int) (event.getX(ptr_index) / mDx));
- sb.append(" y=").append((int) (event.getY(ptr_index) / mDy));
+ bb.putInt((int) (event.getX(ptr_index) / mDx));
+ bb.putInt((int) (event.getY(ptr_index) / mDy));
} else {
- sb.append(" x=").append((int) (event.getY(ptr_index) / mDy));
- sb.append(" y=").append((int) (getWidth() - event.getX(ptr_index) / mDx));
+ bb.putInt((int) (event.getY(ptr_index) / mDy));
+ bb.putInt((int) (getWidth() - event.getX(ptr_index) / mDx));
}
// At the system level the input reader takes integers in the range
// 0 - 100 for the pressure.
@@ -207,7 +208,7 @@
if (pressure > 100) {
pressure = 100;
}
- sb.append(" pressure=").append(pressure);
+ bb.putInt(pressure);
}
/***************************************************************************
diff --git a/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java b/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java
index fa81392..0124000 100644
--- a/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java
+++ b/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java
@@ -59,6 +59,8 @@
/** Shapes that can be used for icon backgrounds */
public static enum Shape {
+ /** No background */
+ NONE("none"),
/** Circular background */
CIRCLE("circle"),
/** Square background */
diff --git a/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java b/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java
index 4e0534d..b3e327b 100644
--- a/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java
+++ b/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java
@@ -42,12 +42,17 @@
density = launcherOptions.density.getResourceValue();
}
String shape = launcherOptions.shape.id;
- BufferedImage mBackImage = context.loadImageResource("/images/launcher_stencil/"
+ BufferedImage mBackImage = null;
+ BufferedImage mForeImage = null;
+ BufferedImage mMaskImage = null;
+ if (launcherOptions.shape != Shape.NONE) {
+ mBackImage = context.loadImageResource("/images/launcher_stencil/"
+ shape + "/" + density + "/back.png");
- BufferedImage mForeImage = context.loadImageResource("/images/launcher_stencil/"
+ mForeImage = context.loadImageResource("/images/launcher_stencil/"
+ shape + "/" + density + "/" + launcherOptions.style.id + ".png");
- BufferedImage mMaskImage = context.loadImageResource("/images/launcher_stencil/"
+ mMaskImage = context.loadImageResource("/images/launcher_stencil/"
+ shape + "/" + density + "/mask.png");
+ }
float scaleFactor = GraphicGenerator.getMdpiScaleFactor(launcherOptions.density);
if (launcherOptions.isWebGraphic) {
@@ -59,14 +64,18 @@
BufferedImage outImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height);
Graphics2D g = (Graphics2D) outImage.getGraphics();
- g.drawImage(mBackImage, 0, 0, null);
+ if (mBackImage != null) {
+ g.drawImage(mBackImage, 0, 0, null);
+ }
BufferedImage tempImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height);
Graphics2D g2 = (Graphics2D) tempImage.getGraphics();
- g2.drawImage(mMaskImage, 0, 0, null);
- g2.setComposite(AlphaComposite.SrcAtop);
- g2.setPaint(new Color(launcherOptions.backgroundColor));
- g2.fillRect(0, 0, imageRect.width, imageRect.height);
+ if (mMaskImage != null) {
+ g2.drawImage(mMaskImage, 0, 0, null);
+ g2.setComposite(AlphaComposite.SrcAtop);
+ g2.setPaint(new Color(launcherOptions.backgroundColor));
+ g2.fillRect(0, 0, imageRect.width, imageRect.height);
+ }
if (launcherOptions.crop) {
Util.drawCenterCrop(g2, launcherOptions.sourceImage, targetRect);
@@ -75,7 +84,9 @@
}
g.drawImage(tempImage, 0, 0, null);
- g.drawImage(mForeImage, 0, 0, null);
+ if (mForeImage != null) {
+ g.drawImage(mForeImage, 0, 0, null);
+ }
g.dispose();
g2.dispose();
diff --git a/bash_completion/adb.bash b/bash_completion/adb.bash
index 12b36ef..46ed208 100644
--- a/bash_completion/adb.bash
+++ b/bash_completion/adb.bash
@@ -66,7 +66,7 @@
COMPREPLY=( $(compgen -W "$OPTIONS $COMMAND" -- "$cur") )
;;
OPT_SERIAL_ARG)
- local devices=$(command adb devices '2>' /dev/null | grep -v "List of devices" | awk '{ print $1 }')
+ local devices=$(command adb devices 2> /dev/null | grep -v "List of devices" | awk '{ print $1 }')
COMPREPLY=( $(compgen -W "${devices}" -- ${cur}) )
;;
COMMAND)
diff --git a/build/product_sdk.mk b/build/product_sdk.mk
index 7802211..f96bcf5 100644
--- a/build/product_sdk.mk
+++ b/build/product_sdk.mk
@@ -25,50 +25,35 @@
# packaged in the SDK.
#
-# Host tools that are parts of the SDK.
+# Host tools and java libraries that are parts of the SDK.
PRODUCT_PACKAGES += \
android \
- emulator \
- ddms \
- draw9patch \
- hierarchyviewer \
- lint \
- mksdcard \
- monkeyrunner \
- monitor \
- traceview
-
-# Native host Java libraries that are parts of the SDK.
-PRODUCT_PACKAGES += \
androidprefs \
annotations \
anttasks \
archquery \
- commons-compress-1.0 \
+ assetstudio \
common-tests \
- ddms \
ddmlib \
ddmlib-tests \
+ ddms \
ddmuilib \
draw9patch \
+ emulator \
hierarchyviewer \
+ ide_common \
lint \
- traceview \
- jcommon-1.0.12 \
- jfreechart-1.0.9 \
- jfreechart-1.0.9-swt \
+ manifmerger \
+ mksdcard \
+ monitor \
monkeyrunner \
- org.eclipse.core.commands_3.4.0.I20080509-2000 \
- org.eclipse.equinox.common_3.4.0.v20080421-2006 \
- org.eclipse.jface_3.4.2.M20090107-0800 \
- osgi \
ninepatch-tests \
- sdkmanager \
+ rule_api \
sdklib \
sdklib-tests \
+ sdkmanager \
sdkstats \
sdkuilib \
sdkuilib-tests \
- swing-worker-1.1 \
- swtmenubar
-
+ swtmenubar \
+ traceview
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java
similarity index 60%
rename from ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java
rename to ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java
index 2caf50d..1a547c7 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java
@@ -19,11 +19,13 @@
import java.util.List;
/**
- * Listeners interested in log cat messages should implement this interface.
+ * Listeners interested in changes in the logcat buffer should implement this interface.
*/
-public interface ILogCatMessageEventListener {
- /** Called on reception of logcat messages.
- * @param receivedMessages list of messages received
+public interface ILogCatBufferChangeListener {
+ /**
+ * Called when the logcat buffer changes.
+ * @param addedMessages list of messages that were added to the logcat buffer
+ * @param deletedMessages list of messages that were removed from the logcat buffer
*/
- void messageReceived(List<LogCatMessage> receivedMessages);
+ void bufferChanged(List<LogCatMessage> addedMessages, List<LogCatMessage> deletedMessages);
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilter.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilter.java
index 509449d..7bdd98a 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilter.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilter.java
@@ -15,7 +15,6 @@
*/
package com.android.ddmuilib.logcat;
-import com.android.ddmlib.Log;
import com.android.ddmlib.Log.LogLevel;
import java.util.ArrayList;
@@ -96,8 +95,6 @@
mAppNamePattern = Pattern.compile(mAppName, getPatternCompileFlags(mAppName));
mCheckAppName = true;
} catch (PatternSyntaxException e) {
- Log.e("LogCatFilter", "Ignoring invalid app name regex.");
- Log.e("LogCatFilter", e.getMessage());
mCheckAppName = false;
}
}
@@ -107,8 +104,6 @@
mTagPattern = Pattern.compile(mTag, getPatternCompileFlags(mTag));
mCheckTag = true;
} catch (PatternSyntaxException e) {
- Log.e("LogCatFilter", "Ignoring invalid tag regex.");
- Log.e("LogCatFilter", e.getMessage());
mCheckTag = false;
}
}
@@ -118,8 +113,6 @@
mTextPattern = Pattern.compile(mText, getPatternCompileFlags(mText));
mCheckText = true;
} catch (PatternSyntaxException e) {
- Log.e("LogCatFilter", "Ignoring invalid text regex.");
- Log.e("LogCatFilter", e.getMessage());
mCheckText = false;
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
index 164f484..68c08d4 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
@@ -39,10 +39,6 @@
*/
@Override
public Object[] getElements(Object model) {
- if (model instanceof List<?>) {
- return ((List<?>) model).toArray();
- }
- return null;
+ return ((List<?>) model).toArray();
}
-
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
index f68ee05..39b3fa9 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
@@ -165,7 +165,7 @@
* on the dialog is valid or not. If it is not valid, the message
* field stores the reason why it isn't.
*/
- private final class DialogStatus {
+ private static final class DialogStatus {
final boolean valid;
final String message;
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java
deleted file mode 100644
index bd7b520..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2011 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.logcat;
-
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/**
- * A JFace content provider for the LogCat log messages, used in the {@link LogCatPanel}.
- */
-public final class LogCatMessageContentProvider implements IStructuredContentProvider {
- @Override
- public void dispose() {
- }
-
- @Override
- public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- }
-
- @Override
- public Object[] getElements(Object model) {
- if (model instanceof LogCatMessageList) {
- Object[] e = ((LogCatMessageList) model).toArray();
- return e;
- }
-
- return null;
- }
-}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java
deleted file mode 100644
index 1d83a9c..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2011 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.logcat;
-
-import com.android.ddmlib.Log.LogLevel;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.ViewerCell;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.Point;
-
-/**
- * A JFace Column label provider for the LogCat log messages. It expects elements of type
- * {@link LogCatMessage}.
- */
-public final class LogCatMessageLabelProvider extends ColumnLabelProvider {
- private static final int INDEX_LOGLEVEL = 0;
- private static final int INDEX_LOGTIME = 1;
- private static final int INDEX_PID = 2;
- private static final int INDEX_APPNAME = 3;
- private static final int INDEX_TAG = 4;
- private static final int INDEX_TEXT = 5;
-
- /* Default Colors for different log levels. */
- private static final Color INFO_MSG_COLOR = new Color(null, 0, 127, 0);
- private static final Color DEBUG_MSG_COLOR = new Color(null, 0, 0, 127);
- private static final Color ERROR_MSG_COLOR = new Color(null, 255, 0, 0);
- private static final Color WARN_MSG_COLOR = new Color(null, 255, 127, 0);
- private static final Color VERBOSE_MSG_COLOR = new Color(null, 0, 0, 0);
-
- /** Amount of pixels to shift the tooltip by. */
- private static final Point LOGCAT_TOOLTIP_SHIFT = new Point(10, 10);
-
- private Font mLogFont;
- private int mWrapWidth = 100;
-
- /**
- * Construct a column label provider for the logcat table.
- * @param font default font to use
- */
- public LogCatMessageLabelProvider(Font font) {
- mLogFont = font;
- }
-
- private String getCellText(LogCatMessage m, int columnIndex) {
- switch (columnIndex) {
- case INDEX_LOGLEVEL:
- return Character.toString(m.getLogLevel().getPriorityLetter());
- case INDEX_LOGTIME:
- return m.getTime();
- case INDEX_PID:
- return m.getPid();
- case INDEX_APPNAME:
- return m.getAppName();
- case INDEX_TAG:
- return m.getTag();
- case INDEX_TEXT:
- return m.getMessage();
- default:
- return "";
- }
- }
-
- @Override
- public void update(ViewerCell cell) {
- Object element = cell.getElement();
- if (!(element instanceof LogCatMessage)) {
- return;
- }
- LogCatMessage m = (LogCatMessage) element;
-
- String text = getCellText(m, cell.getColumnIndex());
- cell.setText(text);
- cell.setFont(mLogFont);
- cell.setForeground(getForegroundColor(m));
- }
-
- private Color getForegroundColor(LogCatMessage m) {
- LogLevel l = m.getLogLevel();
-
- if (l.equals(LogLevel.VERBOSE)) {
- return VERBOSE_MSG_COLOR;
- } else if (l.equals(LogLevel.INFO)) {
- return INFO_MSG_COLOR;
- } else if (l.equals(LogLevel.DEBUG)) {
- return DEBUG_MSG_COLOR;
- } else if (l.equals(LogLevel.ERROR)) {
- return ERROR_MSG_COLOR;
- } else if (l.equals(LogLevel.WARN)) {
- return WARN_MSG_COLOR;
- }
-
- return null;
- }
-
- public void setFont(Font preferredFont) {
- if (mLogFont != null) {
- mLogFont.dispose();
- }
-
- mLogFont = preferredFont;
- }
-
- public void setMinimumLengthForToolTips(int widthInChars) {
- mWrapWidth = widthInChars;
- }
-
- /**
- * Obtain the tool tip to show for a particular logcat message.
- * We display a tool tip only for messages longer than the width set by
- * {@link #setMinimumLengthForToolTips(int)}.
- */
- @Override
- public String getToolTipText(Object element) {
- String text = element.toString();
- if (text.length() > mWrapWidth) {
- return text;
- } else {
- return null;
- }
- }
-
- @Override
- public Point getToolTipShift(Object object) {
- // The only reason we override this method is that the default shift amounts
- // don't seem to work on OS X Lion.
- return LOGCAT_TOOLTIP_SHIFT;
- }
-}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
index 0d0e3c2..080dbc1 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
@@ -16,6 +16,8 @@
package com.android.ddmuilib.logcat;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -33,7 +35,6 @@
private int mFifoSize;
private BlockingQueue<LogCatMessage> mQ;
- private LogCatMessage[] mQArray;
/**
* Construct an empty message list.
@@ -43,7 +44,6 @@
mFifoSize = maxMessages;
mQ = new ArrayBlockingQueue<LogCatMessage>(mFifoSize);
- mQArray = new LogCatMessage[mFifoSize];
}
/**
@@ -64,8 +64,6 @@
mQ.offer(curMessages[i]);
}
}
-
- mQArray = new LogCatMessage[mFifoSize];
}
/**
@@ -73,17 +71,30 @@
* message will be popped off of it.
* @param m log to be inserted
*/
- public synchronized void appendMessage(final LogCatMessage m) {
- if (mQ.remainingCapacity() == 0) {
- /* make space by removing the first entry */
- mQ.poll();
+ public synchronized void appendMessages(final List<LogCatMessage> messages) {
+ ensureSpace(messages.size());
+ for (LogCatMessage m: messages) {
+ mQ.offer(m);
}
- mQ.offer(m);
}
/**
- * Returns the number of additional elements that this queue can
- * ideally (in the absence of memory or resource constraints)
+ * Ensure that there is sufficient space for given number of messages.
+ * @return list of messages that were deleted to create additional space.
+ */
+ public synchronized List<LogCatMessage> ensureSpace(int messageCount) {
+ List<LogCatMessage> l = new ArrayList<LogCatMessage>(messageCount);
+
+ while (mQ.remainingCapacity() < messageCount) {
+ l.add(mQ.poll());
+ }
+
+ return l;
+ }
+
+ /**
+ * Returns the number of additional elements that this queue can
+ * ideally (in the absence of memory or resource constraints)
* accept without blocking.
* @return the remaining capacity
*/
@@ -91,25 +102,13 @@
return mQ.remainingCapacity();
}
- /**
- * Clear all messages in the list.
- */
+ /** Clear all messages in the list. */
public synchronized void clear() {
mQ.clear();
}
- /**
- * Obtain all the messages currently present in the list.
- * @return array containing all the log messages
- */
- public Object[] toArray() {
- if (mQ.size() == mFifoSize) {
- /*
- * Once the queue is full, it stays full until the user explicitly clears
- * all the logs. Optimize for this case by not reallocating the array.
- */
- return mQ.toArray(mQArray);
- }
- return mQ.toArray();
+ /** Obtain a copy of the message list. */
+ public synchronized List<LogCatMessage> getAllMessages() {
+ return new ArrayList<LogCatMessage>(mQ);
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
index 7aa0328..c2942d0 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
@@ -25,17 +25,14 @@
import com.android.ddmuilib.SelectionDependentPanel;
import com.android.ddmuilib.TableHelper;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.ViewerCell;
-import org.eclipse.jface.window.ToolTip;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
@@ -50,9 +47,12 @@
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
@@ -63,7 +63,7 @@
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
@@ -78,12 +78,14 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
/**
* LogCatPanel displays a table listing the logcat messages.
*/
public final class LogCatPanel extends SelectionDependentPanel
- implements ILogCatMessageEventListener {
+ implements ILogCatBufferChangeListener {
/** Preference key to use for storing list of logcat filters. */
public static final String LOGCAT_FILTERS_LIST = "logcat.view.filters.list";
@@ -121,20 +123,26 @@
private static final String IMAGE_SAVE_LOG_TO_FILE = "save.png"; //$NON-NLS-1$
private static final String IMAGE_CLEAR_LOG = "clear.png"; //$NON-NLS-1$
private static final String IMAGE_DISPLAY_FILTERS = "displayfilters.png"; //$NON-NLS-1$
- private static final String IMAGE_PAUSE_LOGCAT = "pause_logcat.png"; //$NON-NLS-1$
+ private static final String IMAGE_SCROLL_LOCK = "pause_logcat.png"; //$NON-NLS-1$
private static final int[] WEIGHTS_SHOW_FILTERS = new int[] {15, 85};
private static final int[] WEIGHTS_LOGCAT_ONLY = new int[] {0, 100};
+ /** Index of the default filter in the saved filters column. */
+ private static final int DEFAULT_FILTER_INDEX = 0;
+
+ /* Text colors for the filter box */
+ private static final Color VALID_FILTER_REGEX_COLOR =
+ Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
+ private static final Color INVALID_FILTER_REGEX_COLOR =
+ Display.getDefault().getSystemColor(SWT.COLOR_RED);
+
private LogCatReceiver mReceiver;
private IPreferenceStore mPrefStore;
private List<LogCatFilter> mLogCatFilters;
private int mCurrentSelectedFilterIndex;
- private int mRemovedEntriesCount = 0;
- private int mPreviousRemainingCapacity = 0;
-
private ToolItem mNewFilterToolItem;
private ToolItem mDeleteFilterToolItem;
private ToolItem mEditFilterToolItem;
@@ -143,28 +151,40 @@
private Combo mLiveFilterLevelCombo;
private Text mLiveFilterText;
- private TableViewer mViewer;
+ private List<LogCatFilter> mCurrentFilters = Collections.emptyList();
+
+ private Table mTable;
private boolean mShouldScrollToLatestLog = true;
- private ToolItem mPauseLogcatCheckBox;
- private boolean mLastItemPainted = false;
+ private ToolItem mScrollLockCheckBox;
private String mLogFileExportFolder;
- private LogCatMessageLabelProvider mLogCatMessageLabelProvider;
+
+ private Font mFont;
+ private int mWrapWidthInChars;
private SashForm mSash;
+ // messages added since last refresh, synchronized on mLogBuffer
+ private List<LogCatMessage> mLogBuffer;
+
+ // # of messages deleted since last refresh, synchronized on mLogBuffer
+ private int mDeletedLogCount;
+
/**
* Construct a logcat panel.
* @param prefStore preference store where UI preferences will be saved
*/
public LogCatPanel(IPreferenceStore prefStore) {
mPrefStore = prefStore;
+ mLogBuffer = new ArrayList<LogCatMessage>(LogCatMessageList.MAX_MESSAGES_DEFAULT);
initializeFilters();
setupDefaultPreferences();
initializePreferenceUpdateListeners();
+
+ mFont = getFontFromPrefStore();
}
private void initializeFilters() {
@@ -196,15 +216,24 @@
@Override
public void propertyChange(PropertyChangeEvent event) {
String changedProperty = event.getProperty();
-
if (changedProperty.equals(LogCatPanel.LOGCAT_VIEW_FONT_PREFKEY)) {
- mLogCatMessageLabelProvider.setFont(getFontFromPrefStore());
- refreshLogCatTable();
- } else if (changedProperty.equals(
- LogCatMessageList.MAX_MESSAGES_PREFKEY)) {
+ if (mFont != null) {
+ mFont.dispose();
+ }
+ mFont = getFontFromPrefStore();
+ recomputeWrapWidth();
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ for (TableItem it: mTable.getItems()) {
+ it.setFont(mFont);
+ }
+ }
+ });
+ } else if (changedProperty.equals(LogCatMessageList.MAX_MESSAGES_PREFKEY)) {
mReceiver.resizeFifo(mPrefStore.getInt(
LogCatMessageList.MAX_MESSAGES_PREFKEY));
- refreshLogCatTable();
+ reloadLogBuffer();
}
}
});
@@ -246,7 +275,7 @@
mReceiver = LogCatReceiverFactory.INSTANCE.newReceiver(device, mPrefStore);
mReceiver.addMessageReceivedEventListener(this);
- mViewer.setInput(mReceiver.getMessages());
+ reloadLogBuffer();
// Always scroll to last line whenever the selected device changes.
// Run this in a separate async thread to give the table some time to update after the
@@ -353,9 +382,11 @@
});
}
- private void addNewFilter() {
+ private void addNewFilter(String defaultTag, String defaultText, String defaultPid,
+ String defaultAppName, LogLevel defaultLevel) {
LogCatFilterSettingsDialog d = new LogCatFilterSettingsDialog(
Display.getCurrent().getActiveShell());
+ d.setDefaults("", defaultTag, defaultText, defaultPid, defaultAppName, defaultLevel);
if (d.open() != Window.OK) {
return;
}
@@ -378,6 +409,11 @@
saveFilterPreferences();
}
+ private void addNewFilter() {
+ addNewFilter("", "", "",
+ "", LogLevel.VERBOSE);
+ }
+
private void deleteSelectedFilter() {
int selectedIndex = mFiltersTableViewer.getTable().getSelectionIndex();
if (selectedIndex <= 0) {
@@ -430,7 +466,7 @@
* @param appName application name to filter by
*/
public void selectTransientAppFilter(String appName) {
- assert mViewer.getTable().getDisplay().getThread() == Thread.currentThread();
+ assert mTable.getDisplay().getThread() == Thread.currentThread();
LogCatFilter f = findTransientAppFilter(appName);
if (f == null) {
@@ -463,8 +499,11 @@
private void selectFilterAt(final int index) {
mFiltersTableViewer.refresh();
- mFiltersTableViewer.getTable().setSelection(index);
- filterSelectionChanged();
+
+ if (index != mFiltersTableViewer.getTable().getSelectionIndex()) {
+ mFiltersTableViewer.getTable().setSelection(index);
+ filterSelectionChanged();
+ }
}
private void createFiltersTable(Composite parent) {
@@ -501,11 +540,7 @@
createLogcatViewTable(c);
}
- /**
- * Create the search bar at the top of the logcat messages table.
- * FIXME: Currently, this feature is incomplete: The UI elements are created, but they
- * are all set to disabled state.
- */
+ /** Create the search bar at the top of the logcat messages table. */
private void createLiveFilters(Composite parent) {
Composite c = new Composite(parent, SWT.NONE);
c.setLayout(new GridLayout(3, false));
@@ -518,6 +553,7 @@
mLiveFilterText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent arg0) {
+ updateFilterTextColor();
updateAppliedFilters();
}
});
@@ -557,8 +593,6 @@
mReceiver.clearMessages();
refreshLogCatTable();
- mRemovedEntriesCount = 0;
-
// the filters view is not cleared unless the filters are re-applied.
updateAppliedFilters();
}
@@ -580,21 +614,34 @@
}
});
- mPauseLogcatCheckBox = new ToolItem(toolBar, SWT.CHECK);
- mPauseLogcatCheckBox.setImage(
- ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_PAUSE_LOGCAT,
+ mScrollLockCheckBox = new ToolItem(toolBar, SWT.CHECK);
+ mScrollLockCheckBox.setImage(
+ ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_SCROLL_LOCK,
toolBar.getDisplay()));
- mPauseLogcatCheckBox.setSelection(false);
- mPauseLogcatCheckBox.setToolTipText("Pause receiving new logcat messages.");
- mPauseLogcatCheckBox.addSelectionListener(new SelectionAdapter() {
+ mScrollLockCheckBox.setSelection(false);
+ mScrollLockCheckBox.setToolTipText("Scroll Lock");
+ mScrollLockCheckBox.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
- boolean pauseLogcat = mPauseLogcatCheckBox.getSelection();
- setScrollToLatestLog(!pauseLogcat, false);
+ boolean scrollLock = mScrollLockCheckBox.getSelection();
+ setScrollToLatestLog(!scrollLock);
}
});
}
+ /** Sets the foreground color of filter text based on whether the regex is valid. */
+ private void updateFilterTextColor() {
+ String text = mLiveFilterText.getText();
+ Color c;
+ try {
+ Pattern.compile(text.trim());
+ c = VALID_FILTER_REGEX_COLOR;
+ } catch (PatternSyntaxException e) {
+ c = INVALID_FILTER_REGEX_COLOR;
+ }
+ mLiveFilterText.setForeground(c);
+ }
+
private void updateFiltersColumn(boolean showFilters) {
if (showFilters) {
mSash.setWeights(WEIGHTS_SHOW_FILTERS);
@@ -620,13 +667,13 @@
Thread t = new Thread(new Runnable() {
@Override
public void run() {
+ BufferedWriter w = null;
try {
- BufferedWriter w = new BufferedWriter(new FileWriter(fName));
+ w = new BufferedWriter(new FileWriter(fName));
for (LogCatMessage m : selectedMessages) {
w.append(m.toString());
w.newLine();
}
- w.close();
} catch (final IOException e) {
Display.getDefault().asyncExec(new Runnable() {
@Override
@@ -637,6 +684,14 @@
+ e.getMessage());
}
});
+ } finally {
+ if (w != null) {
+ try {
+ w.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
}
}
});
@@ -675,44 +730,25 @@
}
private List<LogCatMessage> getSelectedLogCatMessages() {
- Table table = mViewer.getTable();
- int[] indices = table.getSelectionIndices();
+ int[] indices = mTable.getSelectionIndices();
Arrays.sort(indices); /* Table.getSelectionIndices() does not specify an order */
- // Get items from the table's input as opposed to getting each table item's data.
- // Retrieving table item's data can return NULL in case of a virtual table if the item
- // has not been displayed yet.
- Object input = mViewer.getInput();
- if (!(input instanceof LogCatMessageList)) {
- return Collections.emptyList();
- }
-
- List<LogCatMessage> filteredItems = applyCurrentFilters((LogCatMessageList) input);
List<LogCatMessage> selectedMessages = new ArrayList<LogCatMessage>(indices.length);
for (int i : indices) {
- // consider removed logcat message entries
- i -= mRemovedEntriesCount;
- if (i >= 0 && i < filteredItems.size()) {
- LogCatMessage m = filteredItems.get(i);
- selectedMessages.add(m);
+ Object data = mTable.getItem(i).getData();
+ if (data instanceof LogCatMessage) {
+ selectedMessages.add((LogCatMessage) data);
}
}
return selectedMessages;
}
- private List<LogCatMessage> applyCurrentFilters(LogCatMessageList msgList) {
- Object[] items = msgList.toArray();
- List<LogCatMessage> filteredItems = new ArrayList<LogCatMessage>(items.length);
- List<LogCatViewerFilter> filters = getFiltersToApply();
+ private List<LogCatMessage> applyCurrentFilters(List<LogCatMessage> msgList) {
+ List<LogCatMessage> filteredItems = new ArrayList<LogCatMessage>(msgList.size());
- for (Object item : items) {
- if (!(item instanceof LogCatMessage)) {
- continue;
- }
-
- LogCatMessage msg = (LogCatMessage) item;
- if (!isMessageFiltered(msg, filters)) {
+ for (LogCatMessage msg: msgList) {
+ if (isMessageAccepted(msg, mCurrentFilters)) {
filteredItems.add(msg);
}
}
@@ -720,33 +756,30 @@
return filteredItems;
}
- private boolean isMessageFiltered(LogCatMessage msg, List<LogCatViewerFilter> filters) {
- for (LogCatViewerFilter f : filters) {
- if (!f.select(null, null, msg)) {
- // message does not make it through this filter
- return true;
+ private boolean isMessageAccepted(LogCatMessage msg, List<LogCatFilter> filters) {
+ for (LogCatFilter f : filters) {
+ if (!f.matches(msg)) {
+ // not accepted by this filter
+ return false;
}
}
- return false;
+ // accepted by all filters
+ return true;
}
private void createLogcatViewTable(Composite parent) {
- // The SWT.VIRTUAL bit causes the table to be rendered faster. However it makes all rows
- // to be of the same height, thereby clipping any rows with multiple lines of text.
- // In such a case, users can view the full text by hovering over the item and looking at
- // the tooltip.
- final Table table = new Table(parent, SWT.FULL_SELECTION | SWT.MULTI | SWT.VIRTUAL);
- mViewer = new TableViewer(table);
+ mTable = new Table(parent, SWT.FULL_SELECTION | SWT.MULTI);
- table.setLayoutData(new GridData(GridData.FILL_BOTH));
- table.getHorizontalBar().setVisible(true);
+ mTable.setLayoutData(new GridData(GridData.FILL_BOTH));
+ mTable.getHorizontalBar().setVisible(true);
/** Columns to show in the table. */
String[] properties = {
"Level",
"Time",
"PID",
+ "TID",
"Application",
"Tag",
"Text",
@@ -758,32 +791,31 @@
" ",
" 00-00 00:00:00.0000 ",
" 0000",
+ " 0000",
" com.android.launcher",
" SampleTagText",
" Log Message field should be pretty long by default. As long as possible for correct display on Mac.",
};
- mLogCatMessageLabelProvider = new LogCatMessageLabelProvider(getFontFromPrefStore());
for (int i = 0; i < properties.length; i++) {
- TableColumn tc = TableHelper.createTableColumn(mViewer.getTable(),
+ TableHelper.createTableColumn(mTable,
properties[i], /* Column title */
SWT.LEFT, /* Column Style */
sampleText[i], /* String to compute default col width */
getColPreferenceKey(properties[i]), /* Preference Store key for this column */
mPrefStore);
- TableViewerColumn tvc = new TableViewerColumn(mViewer, tc);
- tvc.setLabelProvider(mLogCatMessageLabelProvider);
}
- mViewer.getTable().setLinesVisible(true); /* zebra stripe the table */
- mViewer.getTable().setHeaderVisible(true);
- mViewer.setContentProvider(new LogCatMessageContentProvider());
- WrappingToolTipSupport.enableFor(mViewer, ToolTip.NO_RECREATE);
+ // don't zebra stripe the table: When the buffer is full, and scroll lock is on, having
+ // zebra striping means that the background could keep changing depending on the number
+ // of new messages added to the bottom of the log.
+ mTable.setLinesVisible(false);
+ mTable.setHeaderVisible(true);
// Set the row height to be sufficient enough to display the current font.
// This is not strictly necessary, except that on WinXP, the rows showed up clipped. So
// we explicitly set it to be sure.
- mViewer.getTable().addListener(SWT.MeasureItem, new Listener() {
+ mTable.addListener(SWT.MeasureItem, new Listener() {
@Override
public void handleEvent(Event event) {
event.height = event.gc.getFontMetrics().getHeight();
@@ -791,146 +823,87 @@
});
// Update the label provider whenever the text column's width changes
- TableColumn textColumn = mViewer.getTable().getColumn(properties.length - 1);
+ TableColumn textColumn = mTable.getColumn(properties.length - 1);
textColumn.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent event) {
- TableColumn tc = (TableColumn) event.getSource();
- int width = tc.getWidth();
- GC gc = new GC(tc.getParent());
- int avgCharWidth = gc.getFontMetrics().getAverageCharWidth();
- gc.dispose();
-
- if (mLogCatMessageLabelProvider != null) {
- mLogCatMessageLabelProvider.setMinimumLengthForToolTips(width/avgCharWidth);
- }
+ recomputeWrapWidth();
}
});
- setupAutoScrollLockBehavior();
+ addRightClickMenu(mTable);
initDoubleClickListener();
+ recomputeWrapWidth();
}
- /**
- * Setup to automatically enable or disable scroll lock. From a user's perspective,
- * the logcat window will: <ul>
- * <li> Automatically scroll and reveal new entries if the scrollbar is at the bottom. </li>
- * <li> Not scroll even when new messages are received if the scrollbar is not at the bottom.
- * </li>
- * </ul>
- * This requires that we are able to detect where the scrollbar is and what direction
- * it is moving. Unfortunately, that proves to be very platform dependent. Here's the behavior
- * of the scroll events on different platforms: <ul>
- * <li> On Windows, scroll bar events specify which direction the scrollbar is moving, but
- * it is not possible to determine if the scrollbar is right at the end. </li>
- * <li> On Mac/Cocoa, scroll bar events do not specify the direction of movement (it is always
- * set to SWT.DRAG), and it is not possible to identify where the scrollbar is since
- * small movements of the scrollbar are not reflected in sb.getSelection(). </li>
- * <li> On Linux/gtk, we don't get the direction, but we can accurately locate the
- * scrollbar location using getSelection(), getThumb() and getMaximum().
- * </ul>
- */
- private void setupAutoScrollLockBehavior() {
- if (DdmConstants.CURRENT_PLATFORM == DdmConstants.PLATFORM_WINDOWS) {
- // On Windows, it is not possible to detect whether the scrollbar is at the
- // bottom using the values of ScrollBar.getThumb, getSelection and getMaximum.
- // Instead we resort to the following workaround: attach to the paint listener
- // and see if the last item has been painted since the previous scroll event.
- // If the last item has been painted, then we assume that we are at the bottom.
- mViewer.getTable().addListener(SWT.PaintItem, new Listener() {
- @Override
- public void handleEvent(Event event) {
- TableItem item = (TableItem) event.item;
- TableItem[] items = mViewer.getTable().getItems();
- if (items.length > 0 && items[items.length - 1] == item) {
- mLastItemPainted = true;
- }
+ /** Setup menu to be displayed when right clicking a log message. */
+ private void addRightClickMenu(final Table table) {
+ // This action will pop up a create filter dialog pre-populated with current selection
+ final Action filterAction = new Action("Filter similar messages..") {
+ @Override
+ public void run() {
+ List<LogCatMessage> selectedMessages = getSelectedLogCatMessages();
+ if (selectedMessages.size() == 0) {
+ addNewFilter();
+ } else {
+ LogCatMessage m = selectedMessages.get(0);
+ addNewFilter(m.getTag(), m.getMessage(), m.getPid(), m.getAppName(),
+ m.getLogLevel());
}
- });
- mViewer.getTable().getVerticalBar().addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent event) {
- boolean scrollToLast;
- if (event.detail == SWT.ARROW_UP || event.detail == SWT.PAGE_UP
- || event.detail == SWT.HOME) {
- // if we know that we are moving up, then do not scroll down
- scrollToLast = false;
- } else {
- // otherwise, enable scrollToLast only if the last item was displayed
- scrollToLast = mLastItemPainted;
- }
+ }
+ };
- setScrollToLatestLog(scrollToLast, true);
- mLastItemPainted = false;
- }
- });
- } else if (DdmConstants.CURRENT_PLATFORM == DdmConstants.PLATFORM_LINUX) {
- // On Linux/gtk, we do not get any details regarding the scroll event (up/down/etc).
- // So we completely rely on whether the scrollbar is at the bottom or not.
- mViewer.getTable().getVerticalBar().addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent event) {
- ScrollBar sb = (ScrollBar) event.getSource();
- boolean scrollToLast = sb.getSelection() + sb.getThumb() == sb.getMaximum();
- setScrollToLatestLog(scrollToLast, true);
- }
- });
- } else {
- // On Mac, we do not get any details regarding the (trackball) scroll event,
- // nor can we rely on getSelection() changing for small movements. As a result, we
- // do not setup any auto scroll lock behavior. Mac users have to manually pause and
- // unpause if they are looking at a particular item in a high volume stream of events.
+ final MenuManager mgr = new MenuManager();
+ mgr.add(filterAction);
+ final Menu menu = mgr.createContextMenu(table);
+
+ table.addListener(SWT.MenuDetect, new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ Point pt = table.getDisplay().map(null, table, new Point(event.x, event.y));
+ Rectangle clientArea = table.getClientArea();
+
+ // The click location is in the header if it is between
+ // clientArea.y and clientArea.y + header height
+ boolean header = pt.y > clientArea.y
+ && pt.y < (clientArea.y + table.getHeaderHeight());
+
+ // Show the menu only if it is not inside the header
+ table.setMenu(header ? null : menu);
+ }
+ });
+ }
+
+ public void recomputeWrapWidth() {
+ if (mTable == null || mTable.isDisposed()) {
+ return;
}
+
+ // get width of the last column (log message)
+ TableColumn tc = mTable.getColumn(mTable.getColumnCount() - 1);
+ int colWidth = tc.getWidth();
+
+ // get font width
+ GC gc = new GC(tc.getParent());
+ gc.setFont(mFont);
+ int avgCharWidth = gc.getFontMetrics().getAverageCharWidth();
+ gc.dispose();
+
+ int MIN_CHARS_PER_LINE = 50; // show atleast these many chars per line
+ mWrapWidthInChars = Math.max(colWidth/avgCharWidth, MIN_CHARS_PER_LINE);
+
+ int OFFSET_AT_END_OF_LINE = 10; // leave some space at the end of the line
+ mWrapWidthInChars -= OFFSET_AT_END_OF_LINE;
}
- private void setScrollToLatestLog(boolean scroll, boolean updateCheckbox) {
+ private void setScrollToLatestLog(boolean scroll) {
mShouldScrollToLatestLog = scroll;
- if (updateCheckbox) {
- mPauseLogcatCheckBox.setSelection(!scroll);
- }
-
if (scroll) {
- mViewer.refresh();
scrollToLatestLog();
}
}
- private static class WrappingToolTipSupport extends ColumnViewerToolTipSupport {
- protected WrappingToolTipSupport(ColumnViewer viewer, int style,
- boolean manualActivation) {
- super(viewer, style, manualActivation);
- }
-
- @Override
- protected Composite createViewerToolTipContentArea(Event event, ViewerCell cell,
- Composite parent) {
- Composite comp = new Composite(parent, SWT.NONE);
- GridLayout l = new GridLayout(1, false);
- l.horizontalSpacing = 0;
- l.marginWidth = 0;
- l.marginHeight = 0;
- l.verticalSpacing = 0;
- comp.setLayout(l);
-
- Text text = new Text(comp, SWT.BORDER | SWT.V_SCROLL | SWT.WRAP);
- text.setEditable(false);
- text.setText(cell.getElement().toString());
- text.setLayoutData(new GridData(500, 150));
-
- return comp;
- }
-
- @Override
- public boolean isHideOnMouseDown() {
- return false;
- }
-
- public static final void enableFor(ColumnViewer viewer, int style) {
- new WrappingToolTipSupport(viewer, style, false);
- }
- }
-
private String getColPreferenceKey(String field) {
return LOGCAT_VIEW_COLSIZE_PREFKEY_PREFIX + field;
}
@@ -952,7 +925,7 @@
* Perform all necessary updates whenever a filter is selected (by user or programmatically).
*/
private void filterSelectionChanged() {
- int idx = getSelectedSavedFilterIndex();
+ int idx = mFiltersTableViewer.getTable().getSelectionIndex();
if (idx == -1) {
/* One of the filters should always be selected.
* On Linux, there is no way to deselect an item.
@@ -971,84 +944,80 @@
}
private void resetUnreadCountForSelectedFilter() {
- int index = getSelectedSavedFilterIndex();
- mLogCatFilters.get(index).resetUnreadCount();
-
+ mLogCatFilters.get(mCurrentSelectedFilterIndex).resetUnreadCount();
refreshFiltersTable();
}
- private int getSelectedSavedFilterIndex() {
- return mFiltersTableViewer.getTable().getSelectionIndex();
- }
-
private void updateFiltersToolBar() {
/* The default filter at index 0 can neither be edited, nor removed. */
- boolean en = getSelectedSavedFilterIndex() != 0;
+ boolean en = mCurrentSelectedFilterIndex != DEFAULT_FILTER_INDEX;
mEditFilterToolItem.setEnabled(en);
mDeleteFilterToolItem.setEnabled(en);
}
private void updateAppliedFilters() {
- List<LogCatViewerFilter> filters = getFiltersToApply();
- mViewer.setFilters(filters.toArray(new LogCatViewerFilter[filters.size()]));
-
- /* whenever filters are changed, the number of displayed logs changes
- * drastically. Display the latest log in such a situation. */
- scrollToLatestLog();
+ mCurrentFilters = getFiltersToApply();
+ reloadLogBuffer();
}
- private List<LogCatViewerFilter> getFiltersToApply() {
+ private List<LogCatFilter> getFiltersToApply() {
/* list of filters to apply = saved filter + live filters */
- List<LogCatViewerFilter> filters = new ArrayList<LogCatViewerFilter>();
- filters.add(getSelectedSavedFilter());
+ List<LogCatFilter> filters = new ArrayList<LogCatFilter>();
+
+ if (mCurrentSelectedFilterIndex != DEFAULT_FILTER_INDEX) {
+ filters.add(getSelectedSavedFilter());
+ }
+
filters.addAll(getCurrentLiveFilters());
return filters;
}
- private List<LogCatViewerFilter> getCurrentLiveFilters() {
- List<LogCatViewerFilter> liveFilters = new ArrayList<LogCatViewerFilter>();
-
- List<LogCatFilter> liveFilterSettings = LogCatFilter.fromString(
+ private List<LogCatFilter> getCurrentLiveFilters() {
+ return LogCatFilter.fromString(
mLiveFilterText.getText(), /* current query */
LogLevel.getByString(mLiveFilterLevelCombo.getText())); /* current log level */
- for (LogCatFilter s : liveFilterSettings) {
- liveFilters.add(new LogCatViewerFilter(s));
- }
-
- return liveFilters;
}
- private LogCatViewerFilter getSelectedSavedFilter() {
- int index = getSelectedSavedFilterIndex();
- return new LogCatViewerFilter(mLogCatFilters.get(index));
+ private LogCatFilter getSelectedSavedFilter() {
+ return mLogCatFilters.get(mCurrentSelectedFilterIndex);
}
-
@Override
public void setFocus() {
}
- /**
- * Update view whenever a message is received.
- * @param receivedMessages list of messages from logcat
- * Implements {@link ILogCatMessageEventListener#messageReceived()}.
- */
@Override
- public void messageReceived(List<LogCatMessage> receivedMessages) {
- refreshLogCatTable();
+ public void bufferChanged(List<LogCatMessage> addedMessages,
+ List<LogCatMessage> deletedMessages) {
- if (mShouldScrollToLatestLog) {
- updateUnreadCount(receivedMessages);
- refreshFiltersTable();
- } else {
- LogCatMessageList messageList = mReceiver.getMessages();
- int remainingCapacity = messageList.remainingCapacity();
- if (remainingCapacity == 0) {
- mRemovedEntriesCount +=
- receivedMessages.size() - mPreviousRemainingCapacity;
- }
- mPreviousRemainingCapacity = remainingCapacity;
+ synchronized (mLogBuffer) {
+ addedMessages = applyCurrentFilters(addedMessages);
+ deletedMessages = applyCurrentFilters(deletedMessages);
+
+ mLogBuffer.addAll(addedMessages);
+ mDeletedLogCount += deletedMessages.size();
}
+
+ refreshLogCatTable();
+ updateUnreadCount(addedMessages);
+ refreshFiltersTable();
+ }
+
+ private void reloadLogBuffer() {
+ mTable.removeAll();
+
+ synchronized (mLogBuffer) {
+ mLogBuffer.clear();
+ mDeletedLogCount = 0;
+ }
+
+ if (mReceiver == null || mReceiver.getMessages() == null) {
+ return;
+ }
+
+ List<LogCatMessage> addedMessages = mReceiver.getMessages().getAllMessages();
+ List<LogCatMessage> deletedMessages = Collections.emptyList();
+ bufferChanged(addedMessages, deletedMessages);
}
/**
@@ -1089,34 +1058,210 @@
*/
private void refreshLogCatTable() {
synchronized (this) {
- if (mCurrentRefresher == null && mShouldScrollToLatestLog) {
+ if (mCurrentRefresher == null) {
mCurrentRefresher = new LogCatTableRefresherTask();
Display.getDefault().asyncExec(mCurrentRefresher);
}
}
}
+ /**
+ * The {@link LogCatTableRefresherTask} takes care of refreshing the table with the
+ * new log messages that have been received. Since the log behaves like a circular buffer,
+ * the first step is to remove items from the top of the table (if necessary). This step
+ * is complicated by the fact that a single log message may span multiple rows if the message
+ * was wrapped. Once the deleted items are removed, the new messages are added to the bottom
+ * of the table. If scroll lock is enabled, the item that was original visible is made visible
+ * again, if not, the last item is made visible.
+ */
private class LogCatTableRefresherTask implements Runnable {
@Override
public void run() {
- if (mViewer.getTable().isDisposed()) {
+ if (mTable.isDisposed()) {
return;
}
synchronized (LogCatPanel.this) {
mCurrentRefresher = null;
}
- if (mShouldScrollToLatestLog) {
- mViewer.refresh();
- scrollToLatestLog();
+ // Current topIndex so that it can be restored if scroll locked.
+ int topIndex = mTable.getTopIndex();
+
+ mTable.setRedraw(false);
+
+ // Obtain the list of new messages, and the number of deleted messages.
+ List<LogCatMessage> newMessages;
+ int deletedMessageCount;
+ synchronized (mLogBuffer) {
+ newMessages = new ArrayList<LogCatMessage>(mLogBuffer);
+ mLogBuffer.clear();
+
+ deletedMessageCount = mDeletedLogCount;
+ mDeletedLogCount = 0;
}
+
+ int originalItemCount = mTable.getItemCount();
+
+ // Remove entries from the start of the table if they were removed in the log buffer
+ // This is complicated by the fact that a single message may span multiple TableItems
+ // if it was word-wrapped.
+ deletedMessageCount -= removeFromTable(mTable, deletedMessageCount);
+
+ // Compute number of table items that were deleted from the table.
+ int deletedItemCount = originalItemCount - mTable.getItemCount();
+
+ // If there are more messages to delete (after deleting messages from the table),
+ // then delete them from the start of the newly added messages list
+ if (deletedMessageCount > 0) {
+ assert deletedMessageCount < newMessages.size();
+ for (int i = 0; i < deletedMessageCount; i++) {
+ newMessages.remove(0);
+ }
+ }
+
+ // Add the remaining messages to the table.
+ for (LogCatMessage m: newMessages) {
+ List<String> wrappedMessageList = wrapMessage(m.getMessage(), mWrapWidthInChars);
+ Color c = getForegroundColor(m);
+ for (int i = 0; i < wrappedMessageList.size(); i++) {
+ TableItem item = new TableItem(mTable, SWT.NONE);
+
+ if (i == 0) {
+ // Only set the message data in the first item. This allows code that
+ // examines the table item data (such as copy selection) to distinguish
+ // between real messages versus lines that are really just wrapped
+ // content from the previous message.
+ item.setData(m);
+
+ item.setText(new String[] {
+ Character.toString(m.getLogLevel().getPriorityLetter()),
+ m.getTime(),
+ m.getPid(),
+ m.getTid(),
+ m.getAppName(),
+ m.getTag(),
+ wrappedMessageList.get(i)
+ });
+ } else {
+ item.setText(new String[] {
+ "", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ "", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ wrappedMessageList.get(i)
+ });
+ }
+ item.setForeground(c);
+ item.setFont(mFont);
+ }
+ }
+
+ if (mShouldScrollToLatestLog) {
+ scrollToLatestLog();
+ } else {
+ // If scroll locked, show the same item that was original visible in the table.
+ int index = Math.max(topIndex - deletedItemCount, 0);
+ mTable.setTopIndex(index);
+ }
+
+ mTable.setRedraw(true);
+ }
+
+ /**
+ * Removes given number of messages from the table, starting at the top of the table.
+ * Note that the number of messages deleted is not equal to the number of rows
+ * deleted since a single message could span multiple rows. This method first calculates
+ * the number of rows that correspond to the number of messages to delete, and then
+ * removes all those rows.
+ * @param table table from which messages should be removed
+ * @param msgCount number of messages to be removed
+ * @return number of messages that were actually removed
+ */
+ private int removeFromTable(Table table, int msgCount) {
+ int deletedMessageCount = 0; // # of messages that have been deleted
+ int lastItemToDelete = 0; // index of the last item that should be deleted
+
+ while (deletedMessageCount < msgCount && lastItemToDelete < table.getItemCount()) {
+ // only rows that begin a message have their item data set
+ TableItem item = table.getItem(lastItemToDelete);
+ if (item.getData() != null) {
+ deletedMessageCount++;
+ }
+
+ lastItemToDelete++;
+ }
+
+ // If there are any table items left over at the end that are wrapped over from the
+ // previous message, mark them for deletion as well.
+ if (lastItemToDelete < table.getItemCount()
+ && table.getItem(lastItemToDelete).getData() == null) {
+ lastItemToDelete++;
+ }
+
+ table.remove(0, lastItemToDelete - 1);
+
+ return deletedMessageCount;
}
}
/** Scroll to the last line. */
private void scrollToLatestLog() {
- mRemovedEntriesCount = 0;
- mViewer.getTable().setTopIndex(mViewer.getTable().getItemCount() - 1);
+ mTable.setTopIndex(mTable.getItemCount() - 1);
+ }
+
+ /**
+ * Splits the message into multiple lines if the message length exceeds given width.
+ * If the message was split, then a wrap character \u23ce is appended to the end of all
+ * lines but the last one.
+ */
+ private List<String> wrapMessage(String msg, int wrapWidth) {
+ if (msg.length() < wrapWidth) {
+ return Collections.singletonList(msg);
+ }
+
+ List<String> wrappedMessages = new ArrayList<String>();
+
+ int offset = 0;
+ int len = msg.length();
+
+ while (len > 0) {
+ int copylen = Math.min(wrapWidth, len);
+ String s = msg.substring(offset, offset + copylen);
+
+ offset += copylen;
+ len -= copylen;
+
+ if (len > 0) { // if there are more lines following, then append a wrap marker
+ s += " \u23ce"; //$NON-NLS-1$
+ }
+
+ wrappedMessages.add(s);
+ }
+
+ return wrappedMessages;
+ }
+
+ /* Default Colors for different log levels. */
+ private static final Color INFO_MSG_COLOR = new Color(null, 0, 127, 0);
+ private static final Color DEBUG_MSG_COLOR = new Color(null, 0, 0, 127);
+ private static final Color ERROR_MSG_COLOR = new Color(null, 255, 0, 0);
+ private static final Color WARN_MSG_COLOR = new Color(null, 255, 127, 0);
+ private static final Color VERBOSE_MSG_COLOR = new Color(null, 0, 0, 0);
+
+ private static Color getForegroundColor(LogCatMessage m) {
+ LogLevel l = m.getLogLevel();
+
+ if (l.equals(LogLevel.VERBOSE)) {
+ return VERBOSE_MSG_COLOR;
+ } else if (l.equals(LogLevel.INFO)) {
+ return INFO_MSG_COLOR;
+ } else if (l.equals(LogLevel.DEBUG)) {
+ return DEBUG_MSG_COLOR;
+ } else if (l.equals(LogLevel.ERROR)) {
+ return ERROR_MSG_COLOR;
+ } else if (l.equals(LogLevel.WARN)) {
+ return WARN_MSG_COLOR;
+ }
+
+ return null;
}
private List<ILogCatMessageSelectionListener> mMessageSelectionListeners;
@@ -1124,7 +1269,7 @@
private void initDoubleClickListener() {
mMessageSelectionListeners = new ArrayList<ILogCatMessageSelectionListener>(1);
- mViewer.getTable().addSelectionListener(new SelectionAdapter() {
+ mTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent arg0) {
List<LogCatMessage> selectedMessages = getSelectedLogCatMessages();
@@ -1153,7 +1298,6 @@
public void setTableFocusListener(ITableFocusListener listener) {
mTableFocusListener = listener;
- final Table table = mViewer.getTable();
final IFocusedTableActivator activator = new IFocusedTableActivator() {
@Override
public void copy(Clipboard clipboard) {
@@ -1162,11 +1306,11 @@
@Override
public void selectAll() {
- table.selectAll();
+ mTable.selectAll();
}
};
- table.addFocusListener(new FocusListener() {
+ mTable.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
mTableFocusListener.focusGained(activator);
@@ -1198,6 +1342,6 @@
/** Select all items in the logcat table. */
public void selectAll() {
- mViewer.getTable().selectAll();
+ mTable.selectAll();
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
index c9606f6..2674e92 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
@@ -20,9 +20,11 @@
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.Log;
import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.Log.LogLevel;
import org.eclipse.jface.preference.IPreferenceStore;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -34,11 +36,14 @@
public final class LogCatReceiver {
private static final String LOGCAT_COMMAND = "logcat -v long";
private static final int DEVICE_POLL_INTERVAL_MSEC = 1000;
+ private static LogCatMessage DEVICE_DISCONNECTED_MESSAGE =
+ new LogCatMessage(LogLevel.ERROR, "", "", "",
+ "", "", "Device disconnected");
private LogCatMessageList mLogMessages;
private IDevice mCurrentDevice;
private LogCatOutputReceiver mCurrentLogCatOutputReceiver;
- private Set<ILogCatMessageEventListener> mLogCatMessageListeners;
+ private Set<ILogCatBufferChangeListener> mLogCatMessageListeners;
private LogCatMessageParser mLogCatMessageParser;
private LogCatPidToNameMapper mPidToNameMapper;
private IPreferenceStore mPrefStore;
@@ -55,7 +60,7 @@
mCurrentDevice = device;
mPrefStore = prefStore;
- mLogCatMessageListeners = new HashSet<ILogCatMessageEventListener>();
+ mLogCatMessageListeners = new HashSet<ILogCatBufferChangeListener>();
mLogCatMessageParser = new LogCatMessageParser();
mPidToNameMapper = new LogCatPidToNameMapper(mCurrentDevice);
@@ -72,9 +77,11 @@
/* stop the current logcat command */
mCurrentLogCatOutputReceiver.mIsCancelled = true;
mCurrentLogCatOutputReceiver = null;
+
+ // add a message to the log indicating that the device has been disconnected.
+ processLogMessages(Collections.singletonList(DEVICE_DISCONNECTED_MESSAGE));
}
- mLogMessages = null;
mCurrentDevice = null;
}
@@ -147,14 +154,19 @@
}
private void processLogLines(String[] lines) {
- List<LogCatMessage> messages = mLogCatMessageParser.processLogLines(lines,
+ List<LogCatMessage> newMessages = mLogCatMessageParser.processLogLines(lines,
mPidToNameMapper);
+ processLogMessages(newMessages);
+ }
- if (messages.size() > 0) {
- for (LogCatMessage m : messages) {
- mLogMessages.appendMessage(m);
+ private void processLogMessages(List<LogCatMessage> newMessages) {
+ if (newMessages.size() > 0) {
+ List<LogCatMessage> deletedMessages;
+ synchronized (mLogMessages) {
+ deletedMessages = mLogMessages.ensureSpace(newMessages.size());
+ mLogMessages.appendMessages(newMessages);
}
- sendMessageReceivedEvent(messages);
+ sendLogChangedEvent(newMessages, deletedMessages);
}
}
@@ -177,17 +189,18 @@
* Add to list of message event listeners.
* @param l listener to notified when messages are received from the device
*/
- public void addMessageReceivedEventListener(ILogCatMessageEventListener l) {
+ public void addMessageReceivedEventListener(ILogCatBufferChangeListener l) {
mLogCatMessageListeners.add(l);
}
- public void removeMessageReceivedEventListener(ILogCatMessageEventListener l) {
+ public void removeMessageReceivedEventListener(ILogCatBufferChangeListener l) {
mLogCatMessageListeners.remove(l);
}
- private void sendMessageReceivedEvent(List<LogCatMessage> messages) {
- for (ILogCatMessageEventListener l : mLogCatMessageListeners) {
- l.messageReceived(messages);
+ private void sendLogChangedEvent(List<LogCatMessage> addedMessages,
+ List<LogCatMessage> deletedMessages) {
+ for (ILogCatBufferChangeListener l : mLogCatMessageListeners) {
+ l.bufferChanged(addedMessages, deletedMessages);
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java
deleted file mode 100644
index f7b8dce..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 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.logcat;
-
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerFilter;
-
-/**
- * A JFace {@link ViewerFilter} for the {@link LogCatPanel} displaying logcat messages.
- * This is a simple wrapper around {@link LogCatFilter}.
- */
-public final class LogCatViewerFilter extends ViewerFilter {
- private LogCatFilter mFilter;
-
- /**
- * Construct a {@link ViewerFilter} filtering logcat messages based on
- * user provided filter settings for PID, Tag and log level.
- * @param filter filter to use
- */
- public LogCatViewerFilter(LogCatFilter filter) {
- mFilter = filter;
- }
-
- @Override
- public boolean select(Viewer viewer, Object parent, Object element) {
- if (!(element instanceof LogCatMessage)) {
- return false;
- }
-
- LogCatMessage m = (LogCatMessage) element;
- return mFilter.matches(m);
- }
-}
diff --git a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
index 7c719b4..9b086f4 100644
--- a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
+++ b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
@@ -88,7 +88,7 @@
private JLabel xLabel;
private JLabel yLabel;
- private TexturePaint texture;
+ private TexturePaint texture;
private List<Rectangle> patches;
private List<Rectangle> horizontalPatches;
@@ -98,7 +98,7 @@
private boolean horizontalStartWithPatch;
private Pair<Integer> horizontalPadding;
- private Pair<Integer> verticalPadding;
+ private Pair<Integer> verticalPadding;
ImageEditorPanel(MainFrame mainFrame, BufferedImage image, String name) {
this.image = image;
@@ -409,7 +409,7 @@
both = new StretchView();
setScale(DEFAULT_SCALE);
-
+
add(vertical, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
add(horizontal, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index f0432c6..fe8cc4b 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -58,6 +58,7 @@
congrats
coords
credentials
+cyclical
dalvik
ddms
deactivated
@@ -153,6 +154,7 @@
locales
logo
lombok
+lopsided
lowercase
luminance
mac
@@ -166,6 +168,7 @@
micro
min
mipmap
+misaligned
monospace
monte
ms
@@ -271,6 +274,7 @@
subclassed
subclassing
submenu
+submenus
subregion
superclasses
supertype
@@ -317,6 +321,7 @@
verbosity
viewport
vs
+wallpaper
webtools
whilst
workflow
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/activity.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/activity.png
new file mode 100644
index 0000000..3612361
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/activity.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/display.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/display.png
new file mode 100644
index 0000000..8831f89
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/display.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_landscape.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_landscape.png
new file mode 100644
index 0000000..4940cea
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_landscape.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_portrait.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_portrait.png
new file mode 100644
index 0000000..c28a980
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_portrait.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/globe.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/globe.png
new file mode 100644
index 0000000..45e5e05
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/globe.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/landscape.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/landscape.png
new file mode 100644
index 0000000..4ed5c2e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/landscape.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/newConfig.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/newConfig.png
new file mode 100644
index 0000000..d6317df
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/newConfig.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/portrait.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/portrait.png
new file mode 100644
index 0000000..3786153
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/portrait.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/square.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/square.png
new file mode 100644
index 0000000..7cc8a4c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/square.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/themes.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/themes.png
new file mode 100644
index 0000000..c96394c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/themes.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java
index 3129f4d..768f93f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java
@@ -172,6 +172,7 @@
* The top level android package as a prefix, "android.".
*/
public static final String ANDROID_PKG_PREFIX = ANDROID_PKG + '.';
+ public static final String ANDROID_SUPPORT_PKG_PREFIX = ANDROID_PKG_PREFIX + "support."; //$NON-NLS-1$
/** The android.view. package prefix */
public static final String ANDROID_VIEW_PKG = ANDROID_PKG_PREFIX + "view."; //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
index ec3d8a4..7b3e8a3 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
@@ -75,6 +75,15 @@
mDeprecatedDoc = info.mDeprecatedDoc;
}
+ /**
+ * Sets the XML Name of the attribute
+ *
+ * @param name the new name to assign
+ */
+ public void setName(String name) {
+ mName = name;
+ }
+
/** Returns the XML Name of the attribute */
@Override
public String getName() {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
index 52b0e15..a0a5ad8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
@@ -21,6 +21,7 @@
import com.android.ide.common.api.IAttributeInfo.Format;
import com.android.ide.common.log.ILogger;
import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
+import com.android.ide.eclipse.adt.AdtUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@@ -197,7 +198,7 @@
String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$
viewLayoutClass.getShortClassName(),
info.getShortClassName());
- xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$
+ xmlName = AdtUtils.stripSuffix(xmlName, "Params"); //$NON-NLS-1$
DeclareStyleableInfo style = mStyleMap.get(xmlName);
if (style != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index 7e2a44d..114e45a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -16,8 +16,13 @@
package com.android.ide.eclipse.adt;
+import static com.android.tools.lint.detector.api.LintConstants.TOOLS_PREFIX;
+import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
@@ -36,6 +41,8 @@
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
@@ -44,6 +51,10 @@
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import java.io.File;
import java.net.URISyntaxException;
@@ -219,6 +230,22 @@
}
/**
+ * Strips the given suffix from the given string, provided that the string ends with
+ * the suffix.
+ *
+ * @param string the full string to strip from
+ * @param suffix the suffix to strip out
+ * @return the string without the suffix at the end
+ */
+ public static String stripSuffix(@NonNull String string, @NonNull String suffix) {
+ if (string.endsWith(suffix)) {
+ return string.substring(0, string.length() - suffix.length());
+ }
+
+ return string;
+ }
+
+ /**
* Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z].
* Returns the string unmodified if the first character is not [a-z].
*
@@ -236,6 +263,29 @@
return sb.toString();
}
+ /** For use by {@link #getLineSeparator()} */
+ private static String sLineSeparator;
+
+ /**
+ * Returns the default line separator to use.
+ * <p>
+ * NOTE: If you have an associated {@link IDocument}, it is better to call
+ * {@link TextUtilities#getDefaultLineDelimiter(IDocument)} since that will
+ * allow (for example) editing a \r\n-delimited document on a \n-delimited
+ * platform and keep a consistent usage of delimiters in the file.
+ *
+ * @return the delimiter string to use
+ */
+ @NonNull
+ public static String getLineSeparator() {
+ if (sLineSeparator == null) {
+ // This is guaranteed to exist:
+ sLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
+ }
+
+ return sLineSeparator;
+ }
+
/**
* Returns the current editor (the currently visible and active editor), or null if
* not found
@@ -550,4 +600,90 @@
return "";
}
+
+ /**
+ * Sets the given tools: attribute in the given XML editor document, adding
+ * the tools name space declaration if necessary, formatting the affected
+ * document region, and optionally comma-appending to an existing value and
+ * optionally opening and revealing the attribute.
+ *
+ * @param editor the associated editor
+ * @param element the associated element
+ * @param description the description of the attribute (shown in the undo
+ * event)
+ * @param name the name of the attribute
+ * @param value the attribute value
+ * @param reveal if true, open the editor and select the given attribute
+ * node
+ * @param appendValue if true, add this value as a comma separated value to
+ * the existing attribute value, if any
+ */
+ @SuppressWarnings("restriction") // DOM model
+ public static void setToolsAttribute(
+ @NonNull final AndroidXmlEditor editor,
+ @NonNull final Element element,
+ @NonNull final String description,
+ @NonNull final String name,
+ @NonNull final String value,
+ final boolean reveal,
+ final boolean appendValue) {
+ editor.wrapUndoEditXmlModel(description, new Runnable() {
+ @Override
+ public void run() {
+ String prefix = UiElementNode.lookupNamespacePrefix(element,
+ TOOLS_URI, null);
+ if (prefix == null) {
+ // Add in new prefix...
+ prefix = UiElementNode.lookupNamespacePrefix(element,
+ TOOLS_URI, TOOLS_PREFIX);
+ // ...and ensure that the header is formatted such that
+ // the XML namespace declaration is placed in the right
+ // position and wrapping is applied etc.
+ editor.scheduleNodeReformat(editor.getUiRootNode(),
+ true /*attributesOnly*/);
+ }
+
+ String v = value;
+ if (appendValue) {
+ String prev = element.getAttributeNS(TOOLS_URI, name);
+ if (prev.length() > 0) {
+ v = prev + ',' + value;
+ }
+ }
+
+ // Use the non-namespace form of set attribute since we can't
+ // reference the namespace until the model has been reloaded
+ element.setAttribute(prefix + ':' + name, v);
+
+ UiElementNode rootUiNode = editor.getUiRootNode();
+ if (rootUiNode != null) {
+ final UiElementNode uiNode = rootUiNode.findXmlNode(element);
+ if (uiNode != null) {
+ editor.scheduleNodeReformat(uiNode, true /*attributesOnly*/);
+
+ if (reveal) {
+ // Update editor selection after format
+ Display display = AdtPlugin.getDisplay();
+ if (display != null) {
+ display.asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ Node xmlNode = uiNode.getXmlNode();
+ Attr attribute = ((Element) xmlNode).getAttributeNodeNS(
+ TOOLS_URI, name);
+ if (attribute instanceof IndexedRegion) {
+ IndexedRegion region = (IndexedRegion) attribute;
+ editor.getStructuredTextEditor().selectAndReveal(
+ region.getStartOffset(), region.getLength());
+ }
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
index b468c5e..594912b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
@@ -21,6 +21,7 @@
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler.Solution;
import com.android.ide.eclipse.adt.Messages;
import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.repository.packages.FullRevision;
import com.android.sdklib.repository.PkgProps;
import org.osgi.framework.Constants;
@@ -48,7 +49,7 @@
/**
* The minimum version of the SDK Tools that this version of ADT requires.
*/
- private final static int MIN_TOOLS_REV = 17;
+ private final static FullRevision MIN_TOOLS_REV = new FullRevision(17);
/**
* Pattern to get the minimum plugin version supported by the SDK. This is read from
@@ -57,7 +58,7 @@
private final static Pattern sPluginVersionPattern = Pattern.compile(
"^plugin.version=(\\d+)\\.(\\d+)\\.(\\d+).*$"); //$NON-NLS-1$
private final static Pattern sSourcePropPattern = Pattern.compile(
- "^" + PkgProps.PKG_REVISION + "=(\\d+).*$"); //$NON-NLS-1$
+ "^" + PkgProps.PKG_REVISION + "=(.*)$"); //$NON-NLS-1$
/**
* Checks the plugin and the SDK have compatible versions.
@@ -136,7 +137,7 @@
// now check whether the tools are new enough.
String osTools = osSdkPath + SdkConstants.OS_SDK_TOOLS_FOLDER;
- int toolsRevision = Integer.MAX_VALUE;
+ FullRevision toolsRevision = new FullRevision(Integer.MAX_VALUE);
try {
reader = new FileReader(osTools + SdkConstants.FN_SOURCE_PROP);
BufferedReader bReader = new BufferedReader(reader);
@@ -144,7 +145,9 @@
while ((line = bReader.readLine()) != null) {
Matcher m = sSourcePropPattern.matcher(line);
if (m.matches()) {
- toolsRevision = Integer.parseInt(m.group(1));
+ try {
+ toolsRevision = FullRevision.parseRevision(m.group(1));
+ } catch (NumberFormatException ignore) {}
break;
}
}
@@ -163,7 +166,7 @@
}
}
- if (toolsRevision < MIN_TOOLS_REV) {
+ if (toolsRevision.compareTo(MIN_TOOLS_REV) < 0) {
// this is a warning only as we need to parse the SDK to allow updating
// of the tools!
return errorHandler.handleWarning(
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java
index 8d38c7c..7da3cc0 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java
@@ -16,6 +16,7 @@
package com.android.ide.eclipse.adt.internal.actions;
+import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
@@ -35,7 +36,9 @@
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
@@ -245,12 +248,16 @@
final IProject newProject;
try {
IProgressMonitor monitor = new NullProgressMonitor();
- IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IWorkspaceRoot root = workspace.getRoot();
String name = AdtUtils.getUniqueProjectName(
"gridlayout_v7", "_"); //$NON-NLS-1$ //$NON-NLS-2$
newProject = root.getProject(name);
- newProject.create(monitor);
+ IProjectDescription description = workspace.newProjectDescription(name);
+ String[] natures = new String[] { AdtConstants.NATURE_DEFAULT, JavaCore.NATURE_ID };
+ description.setNatureIds(natures);
+ newProject.create(description, monitor);
// Copy in the files recursively
IFileSystem fileSystem = EFS.getLocalFileSystem();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
index f8a080a..7345a04 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.actions;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.SdkConstants;
@@ -164,15 +165,7 @@
final BufferedWriter writer = new BufferedWriter(new FileWriter(dstFile));
- String sep = System.getProperty("line.separator"); //$NON-NLS-1$
- if (sep == null || sep.length() < 1) {
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- sep = "\r\n"; //$NON-NLS-1$
- } else {
- sep = "\n"; //$NON-NLS-1$
- }
- }
- final String lineSep = sep;
+ final String lineSep = AdtUtils.getLineSeparator();
int err = GrabProcessOutput.grabProcessOutput(
process,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
index 29cb910..58dd332 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
@@ -19,7 +19,7 @@
/**
* The type of asset to create: launcher icon, menu icon, etc.
*/
-enum AssetType {
+public enum AssetType {
/** Launcher icon to be shown in the application list */
LAUNCHER("Launcher Icons", "ic_launcher"), //$NON-NLS-2$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ChooseAssetTypePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ChooseAssetTypePage.java
index 15b1f05..a26a22e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ChooseAssetTypePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ChooseAssetTypePage.java
@@ -21,7 +21,6 @@
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.resources.ResourceFolderType;
-import org.eclipse.core.resources.IProject;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
@@ -41,25 +40,19 @@
/** Page for choosing the type of asset to create, as well as the target project */
public class ChooseAssetTypePage extends WizardPage implements SelectionListener, ModifyListener {
+ private final CreateAssetSetWizardState mValues;
private ProjectCombo mProjectButton;
private Button mClipboardButton;
- private IProject mProject;
private Text mNameText;
- /**
- * The type of asset being created. This field is static such that when you
- * bring up the wizard repeatedly (for example to create multiple
- * notification icons) you don't have to keep selecting the same type over
- * and over.
- */
- private static AssetType sType = AssetType.LAUNCHER;
private boolean mNameModified;
private Label mResourceName;
/**
* Create the wizard.
*/
- public ChooseAssetTypePage() {
+ public ChooseAssetTypePage(CreateAssetSetWizardState values) {
super("chooseAssetTypePage");
+ mValues = values;
setTitle("Choose Icon Set Type");
setDescription("Select the type of icon set to create:");
}
@@ -80,7 +73,7 @@
Button button = new Button(container, SWT.RADIO);
button.setData(type);
button.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
- button.setSelection(type == sType);
+ button.setSelection(type == mValues.type);
button.setText(type.getDisplayName());
button.addSelectionListener(this);
}
@@ -96,7 +89,7 @@
ProjectChooserHelper helper =
new ProjectChooserHelper(getShell(), null /* filter */);
- mProjectButton = new ProjectCombo(helper, container, mProject);
+ mProjectButton = new ProjectCombo(helper, container, mValues.project);
mProjectButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
mProjectButton.addSelectionListener(this);
@@ -135,8 +128,10 @@
if (!mNameModified) {
// Default name suggestion, possibly as a suffix, e.g. "ic_menu_<name>"
String replace = "name";
- String suggestedName = String.format(getAssetType().getDefaultNameFormat(), replace);
+ String suggestedName = String.format(mValues.type.getDefaultNameFormat(), replace);
mNameText.setText(suggestedName);
+ mValues.outputName = suggestedName;
+
updateResourceLabel();
mNameModified = false;
int start = suggestedName.indexOf(replace);
@@ -154,24 +149,16 @@
mResourceName.setText("@drawable/" + getOutputName()); //$NON-NLS-1$
}
- void setProject(IProject project) {
- mProject = project;
- }
-
- IProject getProject() {
- return mProject;
- }
-
@Override
public boolean canFlipToNextPage() {
- return mProject != null;
+ return mValues.project != null;
}
@Override
public void widgetSelected(SelectionEvent e) {
Object source = e.getSource();
if (source == mProjectButton) {
- mProject = mProjectButton.getSelectedProject();
+ mValues.project = mProjectButton.getSelectedProject();
validatePage();
} else if (source == mClipboardButton) {
Clipboard clipboard = new Clipboard(getShell().getDisplay());
@@ -184,7 +171,8 @@
// User selected a different asset type to be created
Object data = ((Button) source).getData();
if (data instanceof AssetType) {
- sType = (AssetType) data;
+ mValues.type = (AssetType) data;
+ CreateAssetSetWizardState.sLastType = mValues.type;
updateAssetType();
}
}
@@ -199,24 +187,21 @@
Object source = e.getSource();
if (source == mNameText) {
mNameModified = true;
+ mValues.outputName = mNameText.getText().trim();
updateResourceLabel();
}
validatePage();
}
- String getOutputName() {
+ private String getOutputName() {
return mNameText.getText().trim();
}
- AssetType getAssetType() {
- return sType;
- }
-
private void validatePage() {
String error = null;
- if (getProject() == null) {
+ if (mValues.project == null) {
error = "Please select an Android project.";
} else {
String outputName = getOutputName();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
index 105892a..1f45570 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
@@ -92,6 +92,7 @@
*/
public class ConfigureAssetSetPage extends WizardPage implements SelectionListener,
GraphicGeneratorContext, ModifyListener {
+ private final CreateAssetSetWizardState mValues;
private static final int PREVIEW_AREA_WIDTH = 120;
@@ -102,6 +103,8 @@
*/
private static boolean SUPPORT_LAUNCHER_ICON_TYPES = false;
+ private boolean mShown;
+
private Composite mConfigurationArea;
private Button mImageRadio;
private Button mClipartRadio;
@@ -112,6 +115,7 @@
private Label mPercentLabel;
private Button mCropRadio;
private Button mCenterRadio;
+ private Button mNoShapeRadio;
private Button mSquareRadio;
private Button mCircleButton;
private Button mBgButton;
@@ -132,7 +136,6 @@
private RGB mBgColor;
private RGB mFgColor;
private Text mText;
- private String mSelectedClipart;
/** Most recently set image path: preserved across wizard sessions */
private static String sImagePath;
@@ -151,11 +154,17 @@
private Label mEffectsLabel;
private Composite mEffectsComposite;
+ private boolean mIgnore;
+
/**
* Create the wizard.
+ *
+ * @param values the wizard state
*/
- public ConfigureAssetSetPage() {
+ public ConfigureAssetSetPage(CreateAssetSetWizardState values) {
super("configureAssetPage");
+ mValues = values;
+
setTitle("Configure Icon Set");
setDescription("Configure the attributes of the icon set");
}
@@ -261,7 +270,6 @@
textLabel.setText("Text:");
mText = new Text(mTextForm, SWT.BORDER);
- mText.setText("Aa");
mText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
mText.addModifyListener(this);
@@ -292,12 +300,11 @@
// This doesn't work right -- not sure why. For now just use a plain slider
// and subtract 10 from it to get the real range.
//mPaddingSlider.setValues(0, -10, 50, 0, 1, 10);
- mPaddingSlider.setSelection(10 + 15);
+ //mPaddingSlider.setSelection(10 + 15);
mPaddingSlider.addSelectionListener(this);
mPercentLabel = new Label(mConfigurationArea, SWT.NONE);
mPercentLabel.setText(" 15%"); // Enough available space for -10%
-
mScalingLabel = new Label(mConfigurationArea, SWT.NONE);
mScalingLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
mScalingLabel.setText("Foreground Scaling:");
@@ -324,10 +331,14 @@
mShapeComposite = new Composite(mConfigurationArea, SWT.NONE);
mShapeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 2, 1));
- GridLayout gl_mShapeComposite = new GridLayout(5, false);
+ GridLayout gl_mShapeComposite = new GridLayout(6, false);
gl_mShapeComposite.horizontalSpacing = 0;
mShapeComposite.setLayout(gl_mShapeComposite);
+ mNoShapeRadio = new Button(mShapeComposite, SWT.FLAT | SWT.TOGGLE);
+ mNoShapeRadio.setText("None");
+ mNoShapeRadio.addSelectionListener(this);
+
mSquareRadio = new Button(mShapeComposite, SWT.FLAT | SWT.TOGGLE);
mSquareRadio.setSelection(true);
mSquareRadio.setText("Square");
@@ -427,17 +438,11 @@
// Initial color
Display display = parent.getDisplay();
- //updateColor(display, new RGB(0xa4, 0xc6, 0x39), true /*background*/);
- updateColor(display, new RGB(0xff, 0x00, 0x00), true /*background*/);
- updateColor(display, new RGB(0x00, 0x00, 0x00), false /*background*/);
+ updateColor(display, mValues.background, true /*background*/);
+ updateColor(display, mValues.foreground, false /*background*/);
- // Start out showing the image form
- //mImageRadio.setSelection(true);
- //chooseForegroundTab(mImageRadio, mImageForm);
- // No, start out showing the text, since the user doesn't have to enter anything
- // initially and we still get images
- mTextRadio.setSelection(true);
- chooseForegroundTab(mTextRadio, mTextForm);
+ setSourceType(mValues.sourceType);
+
new Label(mConfigurationArea, SWT.NONE);
new Label(mConfigurationArea, SWT.NONE);
new Label(mConfigurationArea, SWT.NONE);
@@ -482,6 +487,8 @@
// that method is called when the wizard is created, and we want to wait until the
// user has chosen a project before attempting to look up the right default image to use
if (visible) {
+ mShown = true;
+
// Clear out old previews - important if the user goes back to page one, changes
// asset type and steps into page 2 - at that point we arrive here and we might
// display the old previews for a brief period until the preview delay timer expires.
@@ -492,15 +499,14 @@
// Update asset type configuration: will show/hide parameter controls depending
// on which asset type is chosen
- CreateAssetSetWizard wizard = (CreateAssetSetWizard) getWizard();
- AssetType type = wizard.getAssetType();
+ AssetType type = mValues.type;
assert type != null;
configureAssetType(type);
// Initial image - use the most recently used image, or the default launcher
// icon created in our default projects, if there
if (sImagePath == null) {
- IProject project = wizard.getProject();
+ IProject project = mValues.project;
if (project != null) {
IResource icon = project.findMember("res/drawable-hdpi/icon.png"); //$NON-NLS-1$
if (icon != null) {
@@ -511,8 +517,42 @@
}
}
if (sImagePath != null) {
+ mValues.imagePath = new File(sImagePath);
mImagePathText.setText(sImagePath);
}
+
+ try {
+ mIgnore = true;
+
+ mTrimCheckBox.setSelection(mValues.trim);
+
+ // This doesn't work right -- not sure why. For now just use a plain slider
+ // and subtract 10 from it to get the real range.
+ //mPaddingSlider.setValues(0, -10, 50, 0, 1, 10);
+ //mPaddingSlider.setSelection(10 + 15);
+ mPaddingSlider.setSelection(mValues.padding + 10);
+ mPercentLabel.setText(Integer.toString(mValues.padding) + '%');
+
+ if (mValues.imagePath != null) {
+ mImagePathText.setText(mValues.imagePath.getPath());
+ }
+
+ if (mValues.text != null) {
+ mText.setText(mValues.text);
+ }
+
+ setSourceType(mValues.sourceType);
+ setShape(mValues.shape);
+
+ // Initial color
+ Display display = mPreviewArea.getDisplay();
+ //updateColor(display, new RGB(0xa4, 0xc6, 0x39), true /*background*/);
+ updateColor(display, mValues.background, true /*background*/);
+ updateColor(display, mValues.foreground, false /*background*/);
+ } finally {
+ mIgnore = false;
+ }
+
validatePage();
requestUpdatePreview(true /*quickly*/);
@@ -523,13 +563,25 @@
}
}
+ private void setSourceType(CreateAssetSetWizardState.SourceType sourceType) {
+ if (sourceType == CreateAssetSetWizardState.SourceType.IMAGE) {
+ chooseForegroundTab(mImageRadio, mImageForm);
+ } else if (sourceType == CreateAssetSetWizardState.SourceType.CLIPART) {
+ chooseForegroundTab(mClipartRadio, mClipartForm);
+ } else if (sourceType == CreateAssetSetWizardState.SourceType.TEXT) {
+ updateFontLabel(mFontButton.getFont());
+ chooseForegroundTab(mTextRadio, mTextForm);
+ mText.setFocus();
+ }
+ }
+
private boolean validatePage() {
String error = null;
//String warning = null;
if (mImageRadio.getSelection()) {
- String path = mImagePathText.getText().trim();
- if (path.length() == 0) {
+ String path = mValues.imagePath != null ? mValues.imagePath.getPath() : null;
+ if (path == null || path.length() == 0) {
error = "Select an image";
} else if (!(new File(path).exists())) {
error = String.format("%1$s does not exist", path);
@@ -538,13 +590,12 @@
sImagePath = path;
}
} else if (mTextRadio.getSelection()) {
- String text = mText.getText().trim();
- if (text.length() == 0) {
+ if (mValues.text.length() == 0) {
error = "Enter text";
}
} else {
assert mClipartRadio.getSelection();
- if (mSelectedClipart == null) {
+ if (mValues.clipartName == null) {
error = "Select clip art";
}
}
@@ -565,16 +616,22 @@
@Override
public boolean isPageComplete() {
// Force user to reach second page before hitting Finish
- return isCurrentPage();
+ return mShown;
}
// ---- Implements ModifyListener ----
@Override
public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
if (e.getSource() == mImagePathText) {
+ mValues.imagePath = new File(mImagePathText.getText().trim());
requestUpdatePreview(false);
} else if (e.getSource() == mText) {
+ mValues.text = mText.getText().trim();
requestUpdatePreview(false);
}
@@ -590,15 +647,22 @@
@Override
public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
Object source = e.getSource();
boolean updateQuickly = true;
// Tabs
if (source == mImageRadio) {
+ mValues.sourceType = CreateAssetSetWizardState.SourceType.IMAGE;
chooseForegroundTab((Button) source, mImageForm);
} else if (source == mClipartRadio) {
+ mValues.sourceType = CreateAssetSetWizardState.SourceType.CLIPART;
chooseForegroundTab((Button) source, mClipartForm);
} else if (source == mTextRadio) {
+ mValues.sourceType = CreateAssetSetWizardState.SourceType.TEXT;
updateFontLabel(mFontButton.getFont());
chooseForegroundTab((Button) source, mTextForm);
mText.setFocus();
@@ -609,6 +673,7 @@
FileDialog dialog = new FileDialog(mPickImageButton.getShell(), SWT.OPEN);
String file = dialog.open();
if (file != null) {
+ mValues.imagePath = new File(file);
mImagePathText.setText(file);
}
}
@@ -622,11 +687,14 @@
mCropRadio.setSelection(false);
}
if (source == mSquareRadio) {
- mSquareRadio.setSelection(true);
- mCircleButton.setSelection(false);
+ mValues.shape = GraphicGenerator.Shape.SQUARE;
+ setShape(mValues.shape);
} else if (source == mCircleButton) {
- mCircleButton.setSelection(true);
- mSquareRadio.setSelection(false);
+ mValues.shape = GraphicGenerator.Shape.CIRCLE;
+ setShape(mValues.shape);
+ } else if (source == mNoShapeRadio) {
+ mValues.shape = GraphicGenerator.Shape.NONE;
+ setShape(mValues.shape);
}
if (SUPPORT_LAUNCHER_ICON_TYPES) {
@@ -645,6 +713,10 @@
}
}
+ if (source == mTrimCheckBox) {
+ mValues.trim = mTrimCheckBox.getSelection();
+ }
+
if (source == mHoloDarkRadio) {
mHoloDarkRadio.setSelection(true);
mHoloLightRadio.setSelection(false);
@@ -683,7 +755,7 @@
// Clicked on some of the sample art
if (event.widget instanceof ImageControl) {
ImageControl image = (ImageControl) event.widget;
- mSelectedClipart = (String) image.getData();
+ mValues.clipartName = (String) image.getData();
close();
for (Control c : mClipartPreviewPanel.getChildren()) {
@@ -691,8 +763,8 @@
}
if (mClipartPreviewPanel.getChildren().length == 0) {
try {
- BufferedImage icon =
- GraphicGenerator.getClipartIcon(mSelectedClipart);
+ BufferedImage icon = GraphicGenerator.getClipartIcon(
+ mValues.clipartName);
if (icon != null) {
Display display = mClipartForm.getDisplay();
Image swtImage = SwtUtils.convertToSwt(display, icon,
@@ -746,6 +818,7 @@
if (rgb != null) {
// Dispose the old color, create the
// new one, and set into the label
+ mValues.background = rgb;
updateColor(mBgButton.getDisplay(), rgb, true /*background*/);
}
} else if (source == mFgButton) {
@@ -756,6 +829,7 @@
if (rgb != null) {
// Dispose the old color, create the
// new one, and set into the label
+ mValues.foreground = rgb;
updateColor(mFgButton.getDisplay(), rgb, false /*background*/);
}
}
@@ -782,6 +856,7 @@
}
if (source == mPaddingSlider) {
+ mValues.padding = getPadding();
mPercentLabel.setText(Integer.toString(getPadding()) + '%');
// When dragging the slider, only do periodic updates
@@ -791,6 +866,24 @@
requestUpdatePreview(updateQuickly);
}
+ private void setShape(GraphicGenerator.Shape shape) {
+ if (shape == GraphicGenerator.Shape.SQUARE) {
+ mSquareRadio.setSelection(true);
+ mCircleButton.setSelection(false);
+ mNoShapeRadio.setSelection(false);
+ } else if (shape == GraphicGenerator.Shape.CIRCLE) {
+ mCircleButton.setSelection(true);
+ mSquareRadio.setSelection(false);
+ mNoShapeRadio.setSelection(false);
+ } else if (shape == GraphicGenerator.Shape.NONE) {
+ mNoShapeRadio.setSelection(true);
+ mCircleButton.setSelection(false);
+ mSquareRadio.setSelection(false);
+ } else {
+ assert false : shape;
+ }
+ }
+
private void updateFontLabel(Font f) {
FontData[] fd = f.getFontData();
FontData primary = fd[0];
@@ -909,86 +1002,90 @@
mPreviewArea.layout(true);
}
- Map<String, Map<String, BufferedImage>> generateImages(boolean previewOnly) {
+ public Map<String, Map<String, BufferedImage>> generateImages(boolean previewOnly) {
// Map of ids to images: Preserve insertion order (the densities)
Map<String, Map<String, BufferedImage>> categoryMap =
new LinkedHashMap<String, Map<String, BufferedImage>>();
- CreateAssetSetWizard wizard = (CreateAssetSetWizard) getWizard();
- AssetType type = wizard.getAssetType();
- boolean crop = mTrimCheckBox.getSelection();
+ AssetType type = mValues.type;
+ boolean trim = mValues.trim;
BufferedImage sourceImage = null;
- if (mImageRadio.getSelection()) {
- // Load the image
- // TODO: Only do this when the source image type is image
- String path = mImagePathText.getText().trim();
- if (path.length() == 0) {
- setErrorMessage("Enter a filename");
- return Collections.emptyMap();
- }
- File file = new File(path);
- if (!file.exists()) {
- setErrorMessage(String.format("%1$s does not exist", file.getPath()));
- return Collections.emptyMap();
- }
-
- setErrorMessage(null);
- sourceImage = getImage(path, false);
- if (sourceImage != null) {
- if (crop) {
- sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
+ switch (mValues.sourceType) {
+ case IMAGE: {
+ // Load the image
+ // TODO: Only do this when the source image type is image
+ String path = mValues.imagePath != null ? mValues.imagePath.getPath() : "";
+ if (path.length() == 0) {
+ setErrorMessage("Enter a filename");
+ return Collections.emptyMap();
}
- int padding = getPadding();
- if (padding != 0) {
- sourceImage = Util.paddedImage(sourceImage, padding);
- }
- }
- } else if (mTextRadio.getSelection()) {
- String text = mText.getText();
- TextRenderUtil.Options options = new TextRenderUtil.Options();
- options.font = getSelectedFont();
- int color;
- if (type.needsColors()) {
- color = 0xFF000000 | (mFgColor.red << 16) | (mFgColor.green << 8) | mFgColor.blue;
- } else {
- color = 0xFFFFFFFF;
- }
- options.foregroundColor = color;
- sourceImage = TextRenderUtil.renderTextImage(text, getPadding(), options);
-
- if (crop) {
- sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
- }
-
- int padding = getPadding();
- if (padding != 0) {
- sourceImage = Util.paddedImage(sourceImage, padding);
- }
- } else {
- assert mClipartRadio.getSelection();
- assert mSelectedClipart != null;
- try {
- sourceImage = GraphicGenerator.getClipartImage(mSelectedClipart);
-
- if (crop) {
- sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
+ File file = new File(path);
+ if (!file.exists()) {
+ setErrorMessage(String.format("%1$s does not exist", file.getPath()));
+ return Collections.emptyMap();
}
+ setErrorMessage(null);
+ sourceImage = getImage(path, false);
+ if (sourceImage != null) {
+ if (trim) {
+ sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
+ }
+ if (mValues.padding != 0) {
+ sourceImage = Util.paddedImage(sourceImage, mValues.padding);
+ }
+ }
+ break;
+ }
+ case CLIPART: {
+ try {
+ sourceImage = GraphicGenerator.getClipartImage(mValues.clipartName);
+
+ if (trim) {
+ sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
+ }
+
+ if (type.needsColors()) {
+ int color = 0xFF000000 | (mFgColor.red << 16) | (mFgColor.green << 8)
+ | mFgColor.blue;
+ Paint paint = new java.awt.Color(color);
+ sourceImage = Util.filledImage(sourceImage, paint);
+ }
+
+ int padding = mValues.padding;
+ if (padding != 0) {
+ sourceImage = Util.paddedImage(sourceImage, padding);
+ }
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ return categoryMap;
+ }
+ break;
+ }
+ case TEXT: {
+ String text = mValues.text;
+ TextRenderUtil.Options options = new TextRenderUtil.Options();
+ options.font = getSelectedFont();
+ int color;
if (type.needsColors()) {
- int color = 0xFF000000 | (mFgColor.red << 16) | (mFgColor.green << 8)
- | mFgColor.blue;
- Paint paint = new java.awt.Color(color);
- sourceImage = Util.filledImage(sourceImage, paint);
+ color = 0xFF000000
+ | (mFgColor.red << 16) | (mFgColor.green << 8) | mFgColor.blue;
+ } else {
+ color = 0xFFFFFFFF;
+ }
+ options.foregroundColor = color;
+ sourceImage = TextRenderUtil.renderTextImage(text, mValues.padding, options);
+
+ if (trim) {
+ sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
}
- int padding = getPadding();
+ int padding = mValues.padding;
if (padding != 0) {
sourceImage = Util.paddedImage(sourceImage, padding);
}
- } catch (IOException e) {
- AdtPlugin.log(e, null);
- return categoryMap;
+ break;
}
}
@@ -999,8 +1096,14 @@
generator = new LauncherIconGenerator();
LauncherIconGenerator.LauncherOptions launcherOptions =
new LauncherIconGenerator.LauncherOptions();
- launcherOptions.shape = mCircleButton.getSelection()
- ? GraphicGenerator.Shape.CIRCLE : GraphicGenerator.Shape.SQUARE;
+ if (mCircleButton.getSelection()) {
+ launcherOptions.shape = GraphicGenerator.Shape.CIRCLE;
+ } else if (mSquareRadio.getSelection()) {
+ launcherOptions.shape = GraphicGenerator.Shape.SQUARE;
+ } else {
+ assert mNoShapeRadio.getSelection();
+ launcherOptions.shape = GraphicGenerator.Shape.NONE;
+ }
launcherOptions.crop = mCropRadio.getSelection();
if (SUPPORT_LAUNCHER_ICON_TYPES) {
@@ -1038,8 +1141,7 @@
generator = new NotificationIconGenerator();
NotificationIconGenerator.NotificationOptions notificationOptions =
new NotificationIconGenerator.NotificationOptions();
- notificationOptions.shape = mCircleButton.getSelection()
- ? GraphicGenerator.Shape.CIRCLE : GraphicGenerator.Shape.SQUARE;
+ notificationOptions.shape = mValues.shape;
options = notificationOptions;
break;
}
@@ -1054,11 +1156,15 @@
options.sourceImage = sourceImage;
- IProject project = wizard.getProject();
- Pair<Integer, Integer> v = ManifestInfo.computeSdkVersions(project);
- options.minSdk = v.getFirst();
+ IProject project = mValues.project;
+ if (mValues.minSdk != -1) {
+ options.minSdk = mValues.minSdk;
+ } else {
+ Pair<Integer, Integer> v = ManifestInfo.computeSdkVersions(project);
+ options.minSdk = v.getFirst();
+ }
- String baseName = wizard.getBaseName();
+ String baseName = mValues.outputName;
generator.generate(null, categoryMap, this, options, baseName);
return categoryMap;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizard.java
index f42960c..0b5fc8e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizard.java
@@ -71,6 +71,7 @@
private ConfigureAssetSetPage mConfigureAssetPage;
private IProject mInitialProject;
private List<IResource> mCreatedFiles;
+ private CreateAssetSetWizardState mValues = new CreateAssetSetWizardState();
/** Creates a new asset set wizard */
public CreateAssetSetWizard() {
@@ -79,24 +80,21 @@
@Override
public void addPages() {
- mChooseAssetPage = new ChooseAssetTypePage();
- mChooseAssetPage.setProject(mInitialProject);
- mConfigureAssetPage = new ConfigureAssetSetPage();
+ mValues.project = mInitialProject;
+
+ mChooseAssetPage = new ChooseAssetTypePage(mValues);
+ mConfigureAssetPage = new ConfigureAssetSetPage(mValues);
addPage(mChooseAssetPage);
addPage(mConfigureAssetPage);
}
- String getBaseName() {
- return mChooseAssetPage.getOutputName();
- }
-
@Override
public boolean performFinish() {
Map<String, Map<String, BufferedImage>> categories =
mConfigureAssetPage.generateImages(false);
- IProject project = getProject();
+ IProject project = mValues.project;
// Write out the images into the project
boolean yesToAll = false;
@@ -236,28 +234,10 @@
}
}
- /**
- * Returns the project to be used by the wizard (which may differ from the
- * project initialized by {@link #init(IWorkbench, IStructuredSelection)} or
- * set by {@link #setProject(IProject)} if the user changes the project
- * in the first page of the wizard.
- */
- IProject getProject() {
- if (mChooseAssetPage != null) {
- return mChooseAssetPage.getProject();
- } else {
- return mInitialProject;
- }
- }
-
/** Sets the initial project to be used by the wizard */
void setProject(IProject project) {
mInitialProject = project;
- }
-
- /** Returns the {@link AssetType} to create */
- AssetType getAssetType() {
- return mChooseAssetPage.getAssetType();
+ mValues.project = project;
}
@Override
@@ -265,9 +245,7 @@
setHelpAvailable(false);
mInitialProject = guessProject(selection);
- if (mChooseAssetPage != null) {
- mChooseAssetPage.setProject(mInitialProject);
- }
+ mValues.project = mInitialProject;
}
private IProject guessProject(IStructuredSelection selection) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java
new file mode 100644
index 0000000..624a99a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.assetstudio;
+
+import com.android.assetstudiolib.GraphicGenerator.Shape;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.swt.graphics.RGB;
+
+import java.io.File;
+
+/**
+ * Value object for the AssetStudio wizard. These values are both set by the
+ * wizard as well as read by the wizard initially, so passing in a configured
+ * {@link CreateAssetSetWizardState} to the icon generator is possible.
+ */
+public class CreateAssetSetWizardState {
+ /**
+ * The type of asset being created. This field is static such that when you
+ * bring up the wizard repeatedly (for example to create multiple
+ * notification icons) you don't have to keep selecting the same type over
+ * and over.
+ */
+ public static AssetType sLastType = AssetType.LAUNCHER;
+
+ /** The type of asset to be created */
+ public AssetType type = sLastType;
+
+ /** The base name to use for the created icons */
+ public String outputName;
+
+ /** The minimum SDK being targeted */
+ public int minSdk = -1;
+
+ /** The project to create the icons into */
+ public IProject project;
+
+ /** Whether empty space around the source image should be trimmed */
+ public boolean trim;
+
+ /** The type of source the icon is being created from */
+ public SourceType sourceType = SourceType.TEXT;
+
+ /** If {@link #sourceType} is a {@link SourceType#CLIPART}, the name of the clipart image */
+ public String clipartName;
+
+ /** If {@link #sourceType} is a {@link SourceType#IMAGE}, the path to the input image */
+ public File imagePath;
+
+ /** If {@link #sourceType} is a {@link SourceType#TEXT}, the text to render */
+ public String text = "aA";
+
+ /** The amount of padding to add around the source image */
+ public int padding = 15;
+
+ /** The background shape */
+ public Shape shape = Shape.SQUARE;
+
+ /** The background color to use for the shape (unless the shape is {@link Shape#NONE} */
+ public RGB background = new RGB(0xff, 0x00, 0x00);
+
+ /** The background color to use for the text or clipart (unless shape is {@link Shape#NONE} */
+ public RGB foreground = new RGB(0x00, 0x00, 0x00);
+
+ /** Types of sources that the asset studio can use to generate icons from */
+ public enum SourceType {
+ /** Generate the icon using the image pointed to by {@link #imagePath} */
+ IMAGE,
+
+ /** Generate the icon using the clipart named by {@link #clipartName} */
+ CLIPART,
+
+ /** Generate the icon using the text in {@link #text} */
+ TEXT
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
index c213005..b471c3f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
@@ -23,6 +23,8 @@
import com.android.ide.eclipse.adt.internal.build.Messages;
import com.android.ide.eclipse.adt.internal.build.RenderScriptProcessor;
import com.android.ide.eclipse.adt.internal.build.SourceProcessor;
+import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
+import com.android.ide.eclipse.adt.internal.lint.LintDeltaProcessor;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
@@ -293,6 +295,11 @@
dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors);
delta.accept(dv);
+ // Check for errors on save/build, if enabled
+ if (AdtPrefs.getPrefs().isLintOnSave()) {
+ LintDeltaProcessor.create().process(delta);
+ }
+
// Check to see if Manifest.xml, Manifest.java, or R.java have changed:
mMustCompileResources |= dv.getCompileResources();
@@ -641,6 +648,9 @@
removeMarkersFromContainer(project, AdtConstants.MARKER_AIDL);
removeMarkersFromContainer(project, AdtConstants.MARKER_RENDERSCRIPT);
removeMarkersFromContainer(project, AdtConstants.MARKER_ANDROID);
+
+ // Also clean up lint
+ EclipseLintClient.clearMarkers(project);
}
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
index b7c95b5..7a2658b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
@@ -305,7 +305,7 @@
Node attr = attrs.item(n);
if (XmlnsAttributeDescriptor.XMLNS.equals(attr.getPrefix())) {
String uri = attr.getNodeValue();
- if (SdkConstants.NS_RESOURCES.equals(uri)) {
+ if (nsUri.equals(uri)) {
return attr.getLocalName();
}
visited.add(uri);
@@ -316,7 +316,7 @@
// Use a sensible default prefix if we can't find one.
// We need to make sure the prefix is not one that was declared in the scope
// visited above.
- prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$
+ prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "app"; //$NON-NLS-1$ //$NON-NLS-2$
String base = prefix;
for (int i = 1; visited.contains(prefix); i++) {
prefix = base + Integer.toString(i);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
index 58e156e..803478b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
@@ -246,6 +246,18 @@
mIgnoreXmlUpdate = ignore;
}
+ /**
+ * Returns whether XML model events are ignored or not. This is the case
+ * when we are deliberately modifying the document in a way which does not
+ * change the semantics (such as formatting), or when we have already
+ * directly updated the model ourselves.
+ *
+ * @return true if XML events should be ignored
+ */
+ public boolean getIgnoreXmlUpdate() {
+ return mIgnoreXmlUpdate;
+ }
+
// ---- Base Class Overrides, Interfaces Implemented ----
@Override
@@ -576,13 +588,15 @@
* Utility method that creates a Job to run Lint on the current document.
* Does not wait for the job to finish - just returns immediately.
*
- * @see EclipseLintRunner#startLint(java.util.List, IDocument, boolean, boolean)
+ * @return a new job, or null
+ * @see EclipseLintRunner#startLint(java.util.List, IResource, IDocument,
+ * boolean, boolean)
*/
@Nullable
public Job startLintJob() {
IFile file = getInputFile();
if (file != null) {
- return EclipseLintRunner.startLint(Collections.singletonList(file),
+ return EclipseLintRunner.startLint(Collections.singletonList(file), file,
getStructuredDocument(), false /*fatalOnly*/, false /*show*/);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
index 8ff2a34..bac7c56 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
@@ -39,6 +39,8 @@
import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY;
import static com.android.sdklib.xml.AndroidManifest.NODE_SERVICE;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.resources.ResourceFolder;
@@ -1079,14 +1081,25 @@
return getResourceLinks(range, url);
}
+ private static IHyperlink[] getResourceLinks(@Nullable IRegion range, @NonNull String url) {
+ IProject project = Hyperlinks.getProject();
+ FolderConfiguration configuration = getConfiguration();
+ return getResourceLinks(range, url, project, configuration);
+ }
+
/**
* Computes hyperlinks to resource definitions for resource urls (e.g.
* {@code @android:string/ok} or {@code @layout/foo}. May create multiple links.
+ * @param range TBD
+ * @param url the resource url
+ * @param project the relevant project
+ * @param configuration the applicable configuration
+ * @return an array of hyperlinks, or null
*/
- private static IHyperlink[] getResourceLinks(IRegion range, String url) {
+ @Nullable
+ public static IHyperlink[] getResourceLinks(@Nullable IRegion range, @NonNull String url,
+ @NonNull IProject project, @Nullable FolderConfiguration configuration) {
List<IHyperlink> links = new ArrayList<IHyperlink>();
- IProject project = Hyperlinks.getProject();
- FolderConfiguration configuration = getConfiguration();
Pair<ResourceType,String> resource = ResourceHelper.parseResource(url);
if (resource == null || resource.getFirst() == null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
index b00656e..19ff37a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
@@ -54,6 +54,7 @@
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
@@ -100,7 +101,7 @@
* entries in the form "elem-name/attr-name". Elem-name can be "*".
* @param overrides A map [attribute name => ITextAttributeCreator creator].
*/
- public static void appendAttributes(ArrayList<AttributeDescriptor> attributes,
+ public static void appendAttributes(List<AttributeDescriptor> attributes,
String elementXmlName,
String nsUri, AttributeInfo[] infos,
Set<String> requiredAttributes,
@@ -131,7 +132,7 @@
* a "*" to its UI name as a hint for the user.)
* @param overrides A map [attribute name => ITextAttributeCreator creator].
*/
- public static void appendAttribute(ArrayList<AttributeDescriptor> attributes,
+ public static void appendAttribute(List<AttributeDescriptor> attributes,
String elementXmlName,
String nsUri,
AttributeInfo info, boolean required,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
index 754da29..9cdbf81 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
@@ -83,7 +83,7 @@
mPrefs = prefs;
mStyle = style;
if (lineSeparator == null) {
- lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
+ lineSeparator = AdtUtils.getLineSeparator();
}
mLineSeparator = lineSeparator;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
index 9e821ad..4bc7641 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
@@ -487,6 +487,9 @@
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
getEditor().getSite().getSelectionProvider().setSelection(selection);
+ if (getEditor().getIgnoreXmlUpdate()) {
+ return;
+ }
SelectionManager manager =
mGraphicalEditor.getCanvasControl().getSelectionManager();
manager.setSelection(selection);
@@ -750,7 +753,10 @@
// Check if we can find a custom view specific to this project.
// This only works if there's an actual matching custom class in the project.
- desc = CustomViewDescriptorService.getInstance().getDescriptor(project, xmlLocalName);
+ if (xmlLocalName.indexOf('.') != -1) {
+ desc = CustomViewDescriptorService.getInstance().getDescriptor(project,
+ xmlLocalName);
+ }
if (desc == null) {
// If we didn't find a custom view, create a synthetic one using the
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
index 6fe6a95..0a80b5e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
@@ -16,9 +16,16 @@
package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
+import static com.android.AndroidConstants.FD_RES_LAYOUT;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_STYLE;
+import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI;
+import com.android.AndroidConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.Rect;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
@@ -38,8 +45,9 @@
import com.android.ide.common.resources.configuration.VersionQualifier;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
@@ -61,27 +69,44 @@
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.util.SparseIntArray;
import com.android.util.Pair;
+import com.google.common.collect.Maps;
+import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.jdt.ui.ISharedImages;
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+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.Combo;
import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
+import org.eclipse.wb.internal.core.nls.ui.FlagImagesRepository;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
import java.util.ArrayList;
import java.util.Collections;
@@ -119,9 +144,22 @@
* - Target reload. This is when the target used by the project is the edited file has finished<br>
* loading.<br>
*/
-public class ConfigurationComposite extends Composite {
- private final static String SEP = ":"; //$NON-NLS-1$
- private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
+public class ConfigurationComposite extends Composite implements SelectionListener {
+ public static final String ATTR_CONTEXT = "context"; //$NON-NLS-1$
+ private static final String ICON_SQUARE = "square"; //$NON-NLS-1$
+ private static final String ICON_LANDSCAPE = "landscape"; //$NON-NLS-1$
+ private static final String ICON_PORTRAIT = "portrait"; //$NON-NLS-1$
+ private static final String ICON_LANDSCAPE_FLIP = "flip_landscape";//$NON-NLS-1$
+ private static final String ICON_PORTRAIT_FLIP = "flip_portrait";//$NON-NLS-1$
+ private static final String ICON_DISPLAY = "display"; //$NON-NLS-1$
+ private static final String ICON_NEW_CONFIG = "newConfig"; //$NON-NLS-1$
+ private static final String ICON_GLOBE = "globe"; //$NON-NLS-1$
+ private static final String ICON_THEMES = "themes"; //$NON-NLS-1$
+ private static final String ICON_ACTIVITY = "activity"; //$NON-NLS-1$
+ private final static String SEP = ":"; //$NON-NLS-1$
+ private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
+ private final static String MARKER_FRAMEWORK = "-"; //$NON-NLS-1$
+ private final static String MARKER_PROJECT = "+"; //$NON-NLS-1$
/**
* Setting name for project-wide setting controlling rendering target and locale which
@@ -137,28 +175,16 @@
public final static QualifiedName NAME_CONFIG_STATE =
new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
- private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$
-
private final static int LOCALE_LANG = 0;
private final static int LOCALE_REGION = 1;
- private Label mCurrentLayoutLabel;
- private Button mCreateButton;
-
- private Combo mDeviceCombo;
- private Combo mDeviceConfigCombo;
- private Combo mLocaleCombo;
- private Combo mUiModeCombo;
- private Combo mNightCombo;
- private Combo mThemeCombo;
- private Combo mTargetCombo;
-
- /**
- * List of booleans, matching item for item the theme names in the mThemeCombo
- * combobox, where each boolean represents whether the corresponding theme is a
- * project theme
- */
- private List<Boolean> mIsProjectTheme = new ArrayList<Boolean>(40);
+ private ToolItem mDeviceCombo;
+ private ToolItem mThemeCombo;
+ private ToolItem mOrientationCombo;
+ private ToolItem mLocaleCombo;
+ private ToolItem mTargetCombo;
+ private ToolItem mConfigCombo;
+ private ToolItem mActivityCombo;
/** updates are disabled if > 0 */
private int mDisableUpdates = 0;
@@ -166,7 +192,9 @@
private List<LayoutDevice> mDeviceList;
private final List<IAndroidTarget> mTargetList = new ArrayList<IAndroidTarget>();
- private final ArrayList<ResourceQualifier[] > mLocaleList =
+ private final List<String> mThemeList = new ArrayList<String>();
+
+ private final List<ResourceQualifier[] > mLocaleList =
new ArrayList<ResourceQualifier[]>();
private final ConfigState mState = new ConfigState();
@@ -214,7 +242,7 @@
/**
* Called when the current theme changes. The theme can be queried with
- * {@link ConfigurationComposite#getTheme()}.
+ * {@link ConfigurationComposite#getThemeName()}.
*/
void onThemeChange();
@@ -224,6 +252,13 @@
void onCreate();
/**
+ * Called when an associated activity is picked
+ *
+ * @param fqcn the fully qualified class name for the associated activity context
+ */
+ void onSetActivity(String fqcn);
+
+ /**
* Called before the rendering target changes.
* @param oldTarget the old rendering target
*/
@@ -253,19 +288,29 @@
String configName;
ResourceQualifier[] locale;
String theme;
+ // TODO: Need to know if it's the project theme or the framework theme!
/** UI mode. Guaranteed to be non null */
UiMode uiMode = UiMode.NORMAL;
/** night mode. Guaranteed to be non null */
NightMode night = NightMode.NOTNIGHT;
/** the version being targeted for rendering */
IAndroidTarget target;
+ String activity;
String getData() {
StringBuilder sb = new StringBuilder();
if (device != null) {
sb.append(device.getName());
sb.append(SEP);
- sb.append(configName);
+ if (configName == null) {
+ DeviceConfig config = getSelectedDeviceConfig();
+ if (config != null) {
+ configName = config.getName();
+ }
+ }
+ if (configName != null) {
+ sb.append(configName);
+ }
sb.append(SEP);
if (isLocaleSpecificLayout() && locale != null) {
if (locale[0] != null && locale[1] != null) {
@@ -276,16 +321,34 @@
}
}
sb.append(SEP);
- sb.append(theme);
+ // Need to escape the theme: if we write the full theme style, then
+ // we can end up with ":"'s in the string (as in @android:style/Theme) which
+ // can be mistaken for {@link #SEP}. Instead use {@link #MARKER_FRAMEWORK}.
+ if (theme != null) {
+ String themeName = ResourceHelper.styleToTheme(theme);
+ if (theme.startsWith(PREFIX_STYLE)) {
+ sb.append(MARKER_PROJECT);
+ } else if (theme.startsWith(PREFIX_ANDROID_STYLE)) {
+ sb.append(MARKER_FRAMEWORK);
+ }
+ sb.append(themeName);
+ }
sb.append(SEP);
- sb.append(uiMode.getResourceValue());
+ if (uiMode != null) {
+ sb.append(uiMode.getResourceValue());
+ }
sb.append(SEP);
- sb.append(night.getResourceValue());
+ if (night != null) {
+ sb.append(night.getResourceValue());
+ }
sb.append(SEP);
// We used to store the render target here in R9. Leave a marker
// to ensure that we don't reuse this slot; add new extra fields after it.
sb.append(SEP);
+ if (activity != null) {
+ sb.append(activity);
+ }
}
return sb.toString();
@@ -293,14 +356,20 @@
boolean setData(String data) {
String[] values = data.split(SEP);
- if (values.length == 6 || values.length == 7) {
+ if (values.length >= 6 && values.length <= 8) {
for (LayoutDevice d : mDeviceList) {
if (d.getName().equals(values[0])) {
device = d;
- FolderConfiguration config = device.getFolderConfigByName(values[1]);
- if (config != null) {
+ FolderConfiguration config = null;
+ if (!values[1].isEmpty() && !values[1].equals("null")) { //$NON-NLS-1$
configName = values[1];
-
+ config = device.getFolderConfigByName(configName);
+ } else if (device.getConfigs().size() > 0) {
+ DeviceConfig first = device.getConfigs().get(0);
+ configName = first.getName();
+ config = first.getConfig();
+ }
+ if (config != null) {
// Load locale. Note that this can get overwritten by the
// project-wide settings read below.
locale = new ResourceQualifier[2];
@@ -314,7 +383,15 @@
}
}
+ // Decode the theme name: See {@link #getData}
theme = values[3];
+ if (theme.startsWith(MARKER_FRAMEWORK)) {
+ theme = PREFIX_ANDROID_STYLE
+ + theme.substring(MARKER_FRAMEWORK.length());
+ } else if (theme.startsWith(MARKER_PROJECT)) {
+ theme = PREFIX_STYLE + theme.substring(MARKER_PROJECT.length());
+ }
+
uiMode = UiMode.getEnum(values[4]);
if (uiMode == null) {
uiMode = UiMode.NORMAL;
@@ -336,6 +413,10 @@
}
target = pair.getSecond();
+ if (values.length == 8) {
+ activity = values[7];
+ }
+
return true;
}
}
@@ -399,133 +480,86 @@
public ConfigurationComposite(IConfigListener listener,
Composite parent, int style, String initialState) {
super(parent, style);
+ setVisible(false); // Delayed until the targets are loaded
+
mListener = listener;
mInitialState = initialState;
+ setLayout(new GridLayout(1, false));
- GridLayout gl;
- GridData gd;
- int cols = 7; // device+config+dock+day+separator*2+theme
+ IconFactory icons = IconFactory.getInstance();
- // ---- First line: editing config display, locale, theme, create-button
- Composite labelParent = new Composite(this, SWT.NONE);
- labelParent.setLayout(gl = new GridLayout(5, false));
- gl.marginWidth = gl.marginHeight = 0;
- gl.marginTop = 3;
- labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
- gd.horizontalSpan = cols;
+ // TODO: Consider switching to a CoolBar instead
+ ToolBar toolBar = new ToolBar(this, SWT.WRAP | SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
+ toolBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
- new Label(labelParent, SWT.NONE).setText("Editing config:");
- mCurrentLayoutLabel = new Label(labelParent, SWT.NONE);
- mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
- gd.widthHint = 50;
+ mConfigCombo = new ToolItem(toolBar, SWT.DROP_DOWN | SWT.BOLD);
+ mConfigCombo.setImage(null);
+ mConfigCombo.setToolTipText("Configuration to render this layout with in Eclipse");
- mLocaleCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY);
- mLocaleCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onLocaleChange();
- }
- });
+ @SuppressWarnings("unused")
+ ToolItem separator2 = new ToolItem(toolBar, SWT.SEPARATOR);
- // Layout bug workaround. Without this, in -some- scenarios the Locale combo box was
- // coming up tiny. Setting a minimumWidth hint does not work either. We need to have
- // 2 or more items in the locale combo box when the layout is first run. These items
- // are removed as part of the locale initialization when the SDK is loaded.
- mLocaleCombo.add("Locale"); //$NON-NLS-1$ // Dummy place holders
- mLocaleCombo.add("Locale"); //$NON-NLS-1$
+ mDeviceCombo = new ToolItem(toolBar, SWT.DROP_DOWN);
+ mDeviceCombo.setImage(icons.getIcon(ICON_DISPLAY));
- mTargetCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY);
- mTargetCombo.add("Android AOSP"); //$NON-NLS-1$ // Dummy place holders
- mTargetCombo.add("Android AOSP"); //$NON-NLS-1$
- mTargetCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onRenderingTargetChange();
- }
- });
+ @SuppressWarnings("unused")
+ ToolItem separator3 = new ToolItem(toolBar, SWT.SEPARATOR);
- mCreateButton = new Button(labelParent, SWT.PUSH | SWT.FLAT);
- mCreateButton.setText("Create...");
- mCreateButton.setEnabled(false);
- mCreateButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- if (mListener != null) {
- mListener.onCreate();
- }
- }
- });
+ mOrientationCombo = new ToolItem(toolBar, SWT.DROP_DOWN);
+ mOrientationCombo.setImage(icons.getIcon(ICON_PORTRAIT));
+ mOrientationCombo.setToolTipText("Flip Orientation");
- // ---- 2nd line: device/config/locale/theme Combos, create button.
+ @SuppressWarnings("unused")
+ ToolItem separator4 = new ToolItem(toolBar, SWT.SEPARATOR);
- setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- setLayout(gl = new GridLayout(cols, false));
- gl.marginHeight = 0;
- gl.horizontalSpacing = 0;
+ mThemeCombo = new ToolItem(toolBar, SWT.DROP_DOWN);
+ mThemeCombo.setImage(icons.getIcon(ICON_THEMES));
- mDeviceCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
- mDeviceCombo.setLayoutData(new GridData(
- GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
- mDeviceCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onDeviceChange(true /* recomputeLayout*/);
- }
- });
+ @SuppressWarnings("unused")
+ ToolItem separator5 = new ToolItem(toolBar, SWT.SEPARATOR);
- mDeviceConfigCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
- mDeviceConfigCombo.setLayoutData(new GridData(
- GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
- mDeviceConfigCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onDeviceConfigChange();
- }
- });
+ mActivityCombo = new ToolItem(toolBar, SWT.DROP_DOWN);
+ mActivityCombo.setToolTipText("Associated activity or fragment providing context");
+ // The JDT class icon is lopsided, presumably because they've left room in the
+ // bottom right corner for badges (for static, final etc). Unfortunately, this
+ // means that the icon looks out of place when sitting close to the language globe
+ // icon, the theme icon, etc so that it looks vertically misaligned:
+ //mActivityCombo.setImage(JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_CLASS));
+ // ...so use one that is centered instead:
+ mActivityCombo.setImage(icons.getIcon(ICON_ACTIVITY));
- // first separator
- Label separator = new Label(this, SWT.SEPARATOR | SWT.VERTICAL);
- separator.setLayoutData(gd = new GridData(
- GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
- gd.heightHint = 0;
+ @SuppressWarnings("unused")
+ ToolItem separator6 = new ToolItem(toolBar, SWT.SEPARATOR);
- mUiModeCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
- mUiModeCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL
- | GridData.GRAB_HORIZONTAL));
- for (UiMode mode : UiMode.values()) {
- mUiModeCombo.add(mode.getLongDisplayValue());
+ //ToolBar rightToolBar = new ToolBar(this, SWT.WRAP | SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
+ //rightToolBar.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ ToolBar rightToolBar = toolBar;
+
+ mLocaleCombo = new ToolItem(rightToolBar, SWT.DROP_DOWN);
+ mLocaleCombo.setImage(icons.getIcon(ICON_GLOBE));
+ mLocaleCombo.setToolTipText("Locale to use when rendering layouts in Eclipse");
+
+ @SuppressWarnings("unused")
+ ToolItem separator7 = new ToolItem(rightToolBar, SWT.SEPARATOR);
+
+ mTargetCombo = new ToolItem(rightToolBar, SWT.DROP_DOWN);
+ mTargetCombo.setImage(AdtPlugin.getAndroidLogo());
+ mLocaleCombo.setToolTipText("Android version to use when rendering layouts in Eclipse");
+
+ addConfigurationMenuListener(mConfigCombo);
+ addActivityMenuListener(mActivityCombo);
+ addLocaleMenuListener(mLocaleCombo);
+ addDeviceMenuListener(mDeviceCombo);
+ addTargetMenuListener(mTargetCombo);
+ addThemeListener(mThemeCombo);
+ addOrientationMenuListener(mOrientationCombo);
+ }
+
+ private void updateActivity() {
+ if (mEditedFile != null) {
+ String preferred = getPreferredActivity(mEditedFile);
+ selectActivity(preferred);
}
- mUiModeCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onDockChange();
- }
- });
-
- mNightCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
- mNightCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL
- | GridData.GRAB_HORIZONTAL));
- for (NightMode mode : NightMode.values()) {
- mNightCombo.add(mode.getLongDisplayValue());
- }
- mNightCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onDayChange();
- }
- });
-
- mThemeCombo = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN);
- mThemeCombo.setLayoutData(new GridData(
- GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
- mThemeCombo.setEnabled(false);
-
- mThemeCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onThemeChange();
- }
- });
}
// ---- Init and reset/reload methods ----
@@ -575,6 +609,8 @@
// only attempt to do anything if the SDK and targets are loaded.
LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
if (sdkStatus == LoadStatus.LOADED) {
+ setVisible(true);
+
LoadStatus targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget,
null /*project*/);
@@ -589,6 +625,8 @@
// update the string showing the config value
updateConfigDisplay(mEditedConfig);
+
+ updateActivity();
}
}
} finally {
@@ -682,6 +720,7 @@
}
if (targetStatus == LoadStatus.LOADED) {
+ setVisible(true);
if (mResources == null) {
mResources = ResourceManager.getInstance().getProjectResources(iProject);
}
@@ -713,13 +752,11 @@
if (loadedConfigData) {
// first make sure we have the config to adapt
selectDevice(mState.device);
- fillConfigCombo(mState.configName);
+ selectConfig(mState.configName);
adaptConfigSelection(false /*needBestMatch*/);
- mUiModeCombo.select(UiMode.getIndex(mState.uiMode));
- mNightCombo.select(NightMode.getIndex(mState.night));
- mTargetCombo.select(mTargetList.indexOf(mState.target));
+ selectTarget(mState.target);
targetData = Sdk.getCurrent().getTargetData(mState.target);
} else {
@@ -729,10 +766,16 @@
IAndroidTarget target = findDefaultRenderTarget();
if (target != null) {
targetData = Sdk.getCurrent().getTargetData(target);
- mTargetCombo.select(mTargetList.indexOf(target));
+ selectTarget(target);
}
}
+ // Update activity: This is done before updateThemes() since
+ // the themes selection can depend on the currently selected activity
+ // (e.g. when there are manifest registrations for the theme to use
+ // for a given activity)
+ updateActivity();
+
// Update themes. This is done after updating the devices above,
// since we want to look at the chosen device size to decide
// what the default theme (for example, with Honeycomb we choose
@@ -756,6 +799,241 @@
return targetData;
}
+ private void selectActivity(@Nullable String fqcn) {
+ if (fqcn != null) {
+ mActivityCombo.setData(fqcn);
+ String label = getActivityLabel(fqcn, true);
+ mActivityCombo.setText(label);
+ } else {
+ mActivityCombo.setText("(Select)");
+ }
+ resizeToolBar();
+ }
+
+ @Nullable
+ private String getPreferredActivity(@NonNull IFile file) {
+ // Store/restore the activity context in the config state to help with
+ // performance if for some reason we can't write it into the XML file and to
+ // avoid having to open the model below
+ if (mState.activity != null) {
+ return mState.activity;
+ }
+
+ IProject project = file.getProject();
+
+ // Look up from XML file
+ Document document = DomUtilities.getDocument(file);
+ if (document != null) {
+ Element element = document.getDocumentElement();
+ if (element != null) {
+ String activity = element.getAttributeNS(TOOLS_URI, ATTR_CONTEXT);
+ if (activity != null && !activity.isEmpty()) {
+ if (activity.startsWith(".") || activity.indexOf('.') == -1) { //$NON-NLS-1$
+ ManifestInfo manifest = ManifestInfo.get(project);
+ String pkg = manifest.getPackage();
+ if (!pkg.isEmpty()) {
+ if (activity.startsWith(".")) { //$NON-NLS-1$
+ activity = pkg + activity;
+ } else {
+ activity = activity + "." + pkg;
+ }
+ }
+ }
+
+ mState.activity = activity;
+ storeState();
+ return activity;
+ }
+ }
+ }
+
+ // No, not available there: try to infer it from the code index
+ String includedIn = mListener != null ? mListener.getIncludedWithin() : null;
+
+ ManifestInfo manifest = ManifestInfo.get(project);
+ String pkg = manifest.getPackage();
+ String layoutName = ResourceHelper.getLayoutName(mEditedFile);
+
+ // If we are rendering a layout in included context, pick the theme
+ // from the outer layout instead
+ if (includedIn != null) {
+ layoutName = includedIn;
+ }
+
+ String activity = ManifestInfo.guessActivity(project, layoutName, pkg);
+
+ if (activity == null) {
+ List<String> activities = ManifestInfo.getProjectActivities(project);
+ if (activities.size() == 1) {
+ activity = activities.get(0);
+ }
+ }
+
+ if (activity != null) {
+ mState.activity = activity;
+ storeState();
+ return activity;
+ }
+
+ // TODO: Do anything else, such as pick the first activity found?
+ // Or just leave some default label instead?
+ // Also, figure out what to store in the mState so I don't keep trying
+
+ return null;
+ }
+
+ private void onSelectActivity() {
+ String activity = getSelectedActivity();;
+ mState.activity = activity;
+ saveState();
+
+ if (activity == null) {
+ return;
+ }
+
+ // See if there is a default theme assigned to this activity, and if so, use it
+ ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject());
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ String preferred = activityThemes.get(activity);
+ if (preferred != null) {
+ // Yes, switch to it
+ selectTheme(preferred);
+ onThemeChange();
+ }
+
+ // Persist in XML
+ if (mListener != null) {
+ mListener.onSetActivity(activity);
+ }
+ }
+
+
+ /** Update the toolbar whenever a label has changed, to not only
+ * cause the layout in the current toolbar to update, but to possibly
+ * wrap the toolbars and update the layout of the surrounding area.
+ */
+ private void resizeToolBar() {
+ Point size = getSize();
+ Point newSize = computeSize(size.x, SWT.DEFAULT, true);
+ setSize(newSize);
+ Composite parent = getParent();
+ parent.layout();
+ parent.redraw();
+ }
+
+ private String getActivityLabel(String fqcn, boolean brief) {
+ if (brief) {
+ return fqcn.substring(fqcn.lastIndexOf('.') + 1);
+ }
+
+ return fqcn;
+ }
+
+ String getSelectedActivity() {
+ return (String) mActivityCombo.getData();
+ }
+
+ private void selectTarget(IAndroidTarget target) {
+ mTargetCombo.setData(target);
+ String label = getRenderingTargetLabel(target, true);
+ mTargetCombo.setText(label);
+ resizeToolBar();
+ }
+
+ private static String getRenderingTargetLabel(IAndroidTarget target, boolean brief) {
+ if (target == null) {
+ return "<null>";
+ }
+
+ AndroidVersion version = target.getVersion();
+
+ if (brief) {
+ if (target.isPlatform()) {
+ return Integer.toString(version.getApiLevel());
+ } else {
+ return target.getName() + ':' + Integer.toString(version.getApiLevel());
+ }
+ }
+
+ String label = String.format("API %1$d: %2$s",
+ version.getApiLevel(),
+ target.getShortClasspathName());
+
+ return label;
+ }
+
+ private String getLocaleLabel(ResourceQualifier[] qualifiers, boolean brief) {
+ if (qualifiers == null) {
+ return null;
+ }
+
+ LanguageQualifier language = (LanguageQualifier) qualifiers[LOCALE_LANG];
+
+ if (language.hasFakeValue()) {
+ if (brief) {
+ // Just use the icon
+ return "";
+ }
+
+ boolean hasLocale = false;
+ ResourceRepository projectRes = mListener.getProjectResources();
+ if (projectRes != null) {
+ hasLocale = projectRes.getLanguages().size() > 0;
+ }
+
+ if (hasLocale) {
+ return "Other";
+ } else {
+ return "Any";
+ }
+ }
+
+ RegionQualifier region = (RegionQualifier) qualifiers[LOCALE_REGION];
+ if (region.hasFakeValue()) {
+ // TODO: Make the region string use "Other" instead of "Any" if
+ // there is more than one region for a given language
+ //if (regions.size() > 0) {
+ // return String.format("%1$s / Other", language);
+ //} else {
+ // return String.format("%1$s / Any", language);
+ //}
+ return language.getValue();
+ } else {
+ return String.format("%1$s / %2$s", language.getValue(), region.getValue());
+ }
+ }
+
+ private void selectLocale(ResourceQualifier[] qualifiers) {
+ mLocaleCombo.setData(qualifiers);
+ String label = getLocaleLabel(qualifiers, true);
+
+ mLocaleCombo.setText(label);
+
+ Image image = getFlagImage(qualifiers);
+ mLocaleCombo.setImage(image);
+
+ resizeToolBar();
+ }
+
+ private ResourceQualifier[] getSelectedLocale() {
+ return (ResourceQualifier[]) mLocaleCombo.getData();
+ }
+
+ private IAndroidTarget getSelectedTarget() {
+ return (IAndroidTarget) mTargetCombo.getData();
+ }
+
+ void selectTheme(String theme) {
+ assert theme.startsWith(PREFIX_STYLE) || theme.startsWith(PREFIX_ANDROID_STYLE) : theme;
+ mThemeCombo.setData(theme);
+ if (theme != null) {
+ mThemeCombo.setText(getThemeLabel(theme, true));
+ } else {
+ mThemeCombo.setText("(Set Theme)");
+ }
+ resizeToolBar();
+ }
+
/** Return the default render target to use, or null if no strong preference */
private IAndroidTarget findDefaultRenderTarget() {
// Default to layoutlib version 5
@@ -966,10 +1244,11 @@
// select the best device anyway.
ConfigMatch match = selectConfigMatch(anyMatches);
selectDevice(mState.device = match.device);
- fillConfigCombo(match.name);
- mLocaleCombo.select(match.bundle.localeIndex);
- mUiModeCombo.select(match.bundle.dockModeIndex);
- mNightCombo.select(match.bundle.nightModeIndex);
+ selectConfig(match.name);
+ selectLocale(mLocaleList.get(match.bundle.localeIndex));
+
+ mState.uiMode = UiMode.getByIndex(match.bundle.dockModeIndex);
+ mState.night = NightMode.getByIndex(match.bundle.nightModeIndex);
// TODO: display a better warning!
computeCurrentConfig();
@@ -989,10 +1268,10 @@
} else {
ConfigMatch match = selectConfigMatch(bestMatches);
selectDevice(mState.device = match.device);
- fillConfigCombo(match.name);
- mLocaleCombo.select(match.bundle.localeIndex);
- mUiModeCombo.select(match.bundle.dockModeIndex);
- mNightCombo.select(match.bundle.nightModeIndex);
+ selectConfig(match.name);
+ selectLocale(mLocaleList.get(match.bundle.localeIndex));
+ mState.uiMode = UiMode.getByIndex(match.bundle.dockModeIndex);
+ mState.night = NightMode.getByIndex(match.bundle.nightModeIndex);
}
}
@@ -1191,9 +1470,9 @@
// check the device config (ie sans locale)
boolean needConfigChange = true; // if still true, we need to find another config.
boolean currentConfigIsCompatible = false;
- int configIndex = mDeviceConfigCombo.getSelectionIndex();
- if (configIndex != -1) {
- String configName = mDeviceConfigCombo.getItem(configIndex);
+ DeviceConfig selectedConfig = getSelectedDeviceConfig();
+ if (selectedConfig != null) {
+ String configName = selectedConfig.getName();
FolderConfiguration currentConfig = mState.device.getFolderConfigByName(configName);
if (currentConfig != null && mEditedConfig.isMatchFor(currentConfig)) {
currentConfigIsCompatible = true; // current config is compatible
@@ -1233,7 +1512,7 @@
if (matchName != null) {
selectConfig(matchName);
- mLocaleCombo.select(localeIndex);
+ selectLocale(mLocaleList.get(localeIndex));
} else {
// no match in current device with any config/locale
// attempt to find another device that can display this particular config.
@@ -1256,80 +1535,47 @@
// The only trick is that the region could be null in the fileConfig but in our
// list of locales, this is represented as a RegionQualifier with value of
// FAKE_LOCALE_VALUE.
- final int count = mLocaleList.size();
- for (int i = 0 ; i < count ; i++) {
- ResourceQualifier[] locale = mLocaleList.get(i);
-
- // the language qualifier in the locale list is never null.
- if (locale[LOCALE_LANG].equals(language)) {
- // region comparison is more complex, as the region could be null.
- if (region == null) {
- if (RegionQualifier.FAKE_REGION_VALUE.equals(
- ((RegionQualifier)locale[LOCALE_REGION]).getValue())) {
- // match!
- if (mLocaleCombo.getSelectionIndex() != i) {
- mLocaleCombo.select(i);
- changed = true;
- }
- break;
- }
- } else if (region.equals(locale[LOCALE_REGION])) {
- // match!
- if (mLocaleCombo.getSelectionIndex() != i) {
- mLocaleCombo.select(i);
- changed = true;
- }
- break;
- }
- }
+ ResourceQualifier[] selectedLocale = getSelectedLocale();
+ //changed = prevLanguage != language || region != prevRegion;
+ if (selectedLocale != null) {
+ ResourceQualifier prevLanguage = selectedLocale[LOCALE_LANG];
+ ResourceQualifier prevRegion = selectedLocale[LOCALE_REGION];
+ changed = !prevLanguage.equals(language) || !prevRegion.equals(region);
}
+ selectLocale(new ResourceQualifier[] { language, region});
+
return changed;
}
private void updateConfigDisplay(FolderConfiguration fileConfig) {
- String current = fileConfig.toDisplayString();
- String layoutLabel = current != null ? current : "(Default)";
- mCurrentLayoutLabel.setText(layoutLabel);
- mCurrentLayoutLabel.setToolTipText(layoutLabel);
+ // Label currently hidden
+ //String current = fileConfig.toDisplayString();
+ //String current = fileConfig.getFolderName(ResourceFolderType.LAYOUT);
+ String current = mEditedFile.getParent().getName();
+ if (current.equals(AndroidConstants.FD_RES_LAYOUT)) {
+ current = "default";
+ }
+
+ // Pretty things up a bit
+ //if (current == null || current.equals("default")) {
+ // current = "Default Configuration";
+ //}
+ mConfigCombo.setText(current);
+ resizeToolBar();
}
private void saveState() {
if (mDisableUpdates == 0) {
- int index = mDeviceConfigCombo.getSelectionIndex();
- if (index != -1) {
- mState.configName = mDeviceConfigCombo.getItem(index);
- } else {
- mState.configName = null;
- }
+ DeviceConfig deviceConfig = getSelectedDeviceConfig();
+ String configName = deviceConfig != null ? deviceConfig.getName() : null;
+ mState.configName = configName;
// since the locales are relative to the project, only keeping the index is enough
- index = mLocaleCombo.getSelectionIndex();
- if (index != -1) {
- mState.locale = mLocaleList.get(index);
- } else {
- mState.locale = null;
- }
-
- index = mThemeCombo.getSelectionIndex();
- if (index != -1) {
- mState.theme = mThemeCombo.getItem(index);
- }
-
- index = mUiModeCombo.getSelectionIndex();
- if (index != -1) {
- mState.uiMode = UiMode.getByIndex(index);
- }
-
- index = mNightCombo.getSelectionIndex();
- if (index != -1) {
- mState.night = NightMode.getByIndex(index);
- }
-
- index = mTargetCombo.getSelectionIndex();
- if (index != -1) {
- mState.target = mTargetList.get(index);
- }
+ mState.locale = getSelectedLocale();
+ mState.theme = getSelectedTheme();
+ mState.target = getRenderingTarget();
+ mState.activity = getSelectedActivity();
}
}
@@ -1340,6 +1586,475 @@
AdtPlugin.setFileProperty(mEditedFile, NAME_CONFIG_STATE, mState.getData());
}
+ private void addLocaleMenuListener(final ToolItem combo) {
+ Listener menuListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+ ResourceQualifier[] current = getSelectedLocale();
+
+ for (final ResourceQualifier[] qualifiers : mLocaleList) {
+ String title = getLocaleLabel(qualifiers, false);
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(title);
+ Image image = getFlagImage(qualifiers);
+ item.setImage(image);
+
+ boolean selected = current == qualifiers;
+ if (selected) {
+ item.setSelection(true);
+ }
+
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ selectLocale(qualifiers);
+ onLocaleChange();
+ }
+ });
+ }
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+ };
+ combo.addListener(SWT.Selection, menuListener);
+ }
+
+ private Map<String, String> mCountryToLanguage;
+
+ private String getCountry(String language, String region) {
+ if (RegionQualifier.FAKE_REGION_VALUE.equals(region)) {
+ region = "";
+ }
+
+ String country = region;
+
+ // Special cases
+ if (language.equals("ar")) { //$NON-NLS-1$
+ country = "AE"; //$NON-NLS-1$
+ } else if (language.equals("zh")) { //$NON-NLS-1$
+ country = "CN"; //$NON-NLS-1$
+ } else if (language.equals("en")) { //$NON-NLS-1$
+ country = "US"; //$NON-NLS-1$
+ } else if (language.equals("fa")) { //$NON-NLS-1$
+ country = "IR"; //$NON-NLS-1$
+ }
+
+ if (country.isEmpty()) {
+ if (mCountryToLanguage == null) {
+ Locale[] locales = Locale.getAvailableLocales();
+ mCountryToLanguage = Maps.newHashMapWithExpectedSize(locales.length);
+ Map<String, Locale> localeMap = Maps.newHashMapWithExpectedSize(locales.length);
+ for (int i = 0; i < locales.length; i++) {
+ Locale locale = locales[i];
+ String localeLanguage = locale.getLanguage();
+ String localeCountry = locale.getCountry();
+ if (!localeCountry.isEmpty()) {
+ localeCountry = localeCountry.toLowerCase(Locale.US);
+ Locale old = localeMap.get(localeLanguage);
+ if (old != null) {
+ // For Italian for example it has both a locale with country = Italy
+ // and one with country = Switzerland, so prefer the one where the
+ // language code matches the country.
+ if (!localeLanguage.equals(localeCountry)) {
+ continue;
+ }
+ }
+ mCountryToLanguage.put(localeLanguage, localeCountry);
+ localeMap.put(localeLanguage, locale);
+ }
+ }
+ }
+
+ country = mCountryToLanguage.get(language);
+ }
+
+ return country;
+ }
+
+ @NonNull
+ private Image getFlagImage(@NonNull ResourceQualifier[] qualifiers) {
+ Image image = null;
+ assert qualifiers.length == 2;
+ String language = ((LanguageQualifier) qualifiers[LOCALE_LANG]).getValue();
+ String region = ((RegionQualifier) qualifiers[LOCALE_REGION]).getValue();
+ if (LanguageQualifier.FAKE_LANG_VALUE.equals(language)) {
+ return IconFactory.getInstance().getIcon(ICON_GLOBE);
+ } else {
+ String country = getCountry(language, region);
+ if (country != null) {
+ image = FlagImagesRepository.getFlagImage(country, language);
+ }
+ if (image == null) {
+ image = FlagImagesRepository.getEmptyFlagImage();
+ }
+
+ return image;
+ }
+ }
+
+ private String getDeviceLabel(LayoutDevice device, boolean brief) {
+ String name = device.getName();
+
+ if (brief) {
+ // Produce a really brief summary of the device name, suitable for
+ // use in the narrow space available in the toolbar for example
+ int nexus = name.indexOf("Nexus"); //$NON-NLS-1$
+ if (nexus != -1) {
+ int begin = name.indexOf('(');
+ if (begin != -1) {
+ begin++;
+ int end = name.indexOf(')', begin);
+ if (end != -1) {
+ return name.substring(begin, end).trim();
+ }
+ }
+ }
+ }
+
+ return name;
+ }
+
+ private void addDeviceMenuListener(final ToolItem combo) {
+ Listener menuListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+
+ LayoutDevice current = getSelectedDevice();
+ if (mDeviceList != null && mDeviceList.size() > 0) {
+ for (final LayoutDevice device : mDeviceList) {
+ String title = getDeviceLabel(device, false);
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(title);
+
+ boolean selected = current == device;
+ if (selected) {
+ item.setSelection(true);
+ }
+
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ selectDevice(device);
+ onDeviceChange(true /*recomputeLayout*/);
+ }
+ });
+ }
+
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+
+ MenuItem item = new MenuItem(menu, SWT.PUSH);
+ item.setText("Add Custom...");
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onCustomDeviceConfig();
+ }
+ });
+
+ }
+
+ // TODO - how do I dispose of this?
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+ };
+ combo.addListener(SWT.Selection, menuListener);
+ }
+
+ private void addTargetMenuListener(final ToolItem combo) {
+ Listener menuListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+ IAndroidTarget current = getSelectedTarget();
+
+ for (final IAndroidTarget target : mTargetList) {
+ String title = getRenderingTargetLabel(target, false);
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(title);
+
+ boolean selected = current == target;
+ if (selected) {
+ item.setSelection(true);
+ }
+
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ selectTarget(target);
+ onRenderingTargetChange();
+ }
+ });
+ }
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+ };
+ combo.addListener(SWT.Selection, menuListener);
+ }
+
+ private void addThemeListener(final ToolItem combo) {
+ Listener menuListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ ThemeMenuAction.showThemeMenu(ConfigurationComposite.this, combo, mThemeList);
+ }
+ };
+ combo.addListener(SWT.Selection, menuListener);
+ }
+
+ private void addOrientationMenuListener(final ToolItem combo) {
+ Listener menuListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ if (event.detail == SWT.ARROW) {
+ OrientationMenuAction.showMenu(ConfigurationComposite.this, combo);
+ } else {
+ flipOrientation();
+ }
+ }
+ };
+ combo.addListener(SWT.Selection, menuListener);
+ }
+
+ /** Flip the current orientation to the next available device orientation, if any */
+ private void flipOrientation() {
+ DeviceConfig config = getSelectedDeviceConfig();
+ DeviceConfig flipped = getNextDeviceConfig(getSelectedDeviceConfig());
+ if (flipped != config) {
+ selectDeviceConfig(flipped);
+ onDeviceConfigChange();
+ }
+ }
+
+ /** Get the next cyclical orientation after the given orientation */
+ @Nullable
+ DeviceConfig getNextDeviceConfig(DeviceConfig config) {
+ LayoutDevice device = getSelectedDevice();
+ List<DeviceConfig> configs = device.getConfigs();
+ for (int i = 0; i < configs.size(); i++) {
+ if (configs.get(i) == config) {
+ return configs.get((i + 1) % configs.size());
+ }
+ }
+
+ return null;
+ }
+
+ protected String getThemeLabel(String theme, boolean brief) {
+ theme = ResourceHelper.styleToTheme(theme);
+
+ if (brief) {
+ int index = theme.lastIndexOf('.');
+ if (index < theme.length() - 1) {
+ return theme.substring(index + 1);
+ }
+ }
+ return theme;
+ }
+
+ private void addActivityMenuListener(final ToolItem combo) {
+ Listener menuListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ // TODO: Allow using fragments here as well?
+ Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+ ISharedImages sharedImages = JavaUI.getSharedImages();
+ String current = getSelectedActivity();
+
+ if (current != null) {
+ MenuItem item = new MenuItem(menu, SWT.PUSH);
+ String label = getActivityLabel(current, true);;
+ item.setText( String.format("Open %1$s...", label));
+ Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CUNIT);
+ item.setImage(image);
+
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ String fqcn = getSelectedActivity();
+ AdtPlugin.openJavaClass(mEditedFile.getProject(), fqcn);
+ }
+ });
+
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+ }
+
+ IProject project = mEditedFile.getProject();
+ Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CLASS);
+
+ // Add activities found to be relevant to this layout
+ String layoutName = ResourceHelper.getLayoutName(mEditedFile);
+ String pkg = ManifestInfo.get(project).getPackage();
+ List<String> preferred = ManifestInfo.guessActivities(project, layoutName, pkg);
+ current = addActivities(menu, current, image, preferred);
+
+ // Add all activities
+ List<String> activities = ManifestInfo.getProjectActivities(project);
+ if (preferred.size() > 0) {
+ // Filter out the activities we've already listed above
+ List<String> filtered = new ArrayList<String>(activities.size());
+ Set<String> remove = new HashSet<String>(preferred);
+ for (String fqcn : activities) {
+ if (!remove.contains(fqcn)) {
+ filtered.add(fqcn);
+ }
+ }
+ activities = filtered;
+ }
+
+ if (activities.size() > 0) {
+ if (preferred.size() > 0) {
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+ }
+
+ addActivities(menu, current, image, activities);
+ }
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+
+ private String addActivities(Menu menu, String current, Image image,
+ List<String> activities) {
+ for (final String fqcn : activities) {
+ String title = getActivityLabel(fqcn, false);
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(title);
+ item.setImage(image);
+
+ boolean selected = title.equals(current);
+ if (selected) {
+ item.setSelection(true);
+ current = null; // Only show the first occurrence as selected
+ // such that we don't show it selected again in the full activity list
+ }
+
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ selectActivity(fqcn);
+ onSelectActivity();
+ }
+ });
+ }
+
+ return current;
+ }
+ };
+ combo.addListener(SWT.Selection, menuListener);
+ }
+
+ private void addConfigurationMenuListener(final ToolItem combo) {
+ Listener menuListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+
+ // Compute the set of layout files defining this layout resource
+ String name = mEditedFile.getName();
+ IContainer resFolder = mEditedFile.getParent().getParent();
+ List<IFile> variations = new ArrayList<IFile>();
+ try {
+ for (IResource resource : resFolder.members()) {
+ if (resource.getName().startsWith(FD_RES_LAYOUT)
+ && resource instanceof IContainer) {
+ IContainer layoutFolder = (IContainer) resource;
+ IResource variation = layoutFolder.findMember(name);
+ if (variation instanceof IFile) {
+ variations.add((IFile) variation);
+ }
+ }
+ }
+ } catch (CoreException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+
+ ResourceManager manager = ResourceManager.getInstance();
+ for (final IFile resource : variations) {
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+
+ IFolder parent = (IFolder) resource.getParent();
+ ResourceFolder parentResource = manager.getResourceFolder(parent);
+ FolderConfiguration configuration = parentResource.getConfiguration();
+ String title = configuration.toDisplayString();
+ item.setText(title);
+
+ boolean selected = mEditedFile.equals(resource);
+ if (selected) {
+ item.setSelection(true);
+ }
+
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ ConfigurationComposite.this.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ AdtPlugin.openFile(resource, null, false);
+ } catch (PartInitException ex) {
+ AdtPlugin.log(ex, null);
+ }
+ }
+ });
+ }
+ });
+ }
+
+ if (!mEditedConfig.equals(mCurrentConfig)) {
+ if (variations.size() > 0) {
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+ }
+
+ // Add action for creating a new configuration
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText("Create New...");
+ item.setImage(IconFactory.getInstance().getIcon(ICON_NEW_CONFIG));
+ //item.setToolTipText("Duplicate: Create new configuration for this layout");
+
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mListener != null) {
+ mListener.onCreate();
+ }
+ }
+ });
+ }
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+ };
+ combo.addListener(SWT.Selection, menuListener);
+ }
+
/**
* Updates the locale combo.
* This must be called from the UI thread.
@@ -1352,12 +2067,9 @@
mDisableUpdates++;
try {
- // Reset the combo
- mLocaleCombo.removeAll();
mLocaleList.clear();
SortedSet<String> languages = null;
- boolean hasLocale = false;
// get the languages from the project.
ResourceRepository projectRes = mListener.getProjectResources();
@@ -1368,25 +2080,16 @@
languages = projectRes.getLanguages();
for (String language : languages) {
- hasLocale = true;
-
LanguageQualifier langQual = new LanguageQualifier(language);
// find the matching regions and add them
SortedSet<String> regions = projectRes.getRegions(language);
for (String region : regions) {
- mLocaleCombo.add(
- String.format("%1$s / %2$s", language, region));
RegionQualifier regionQual = new RegionQualifier(region);
mLocaleList.add(new ResourceQualifier[] { langQual, regionQual });
}
// now the entry for the other regions the language alone
- if (regions.size() > 0) {
- mLocaleCombo.add(String.format("%1$s / Other", language));
- } else {
- mLocaleCombo.add(String.format("%1$s / Any", language));
- }
// create a region qualifier that will never be matched by qualified resources.
mLocaleList.add(new ResourceQualifier[] {
langQual,
@@ -1395,14 +2098,6 @@
}
}
- // add a locale not present in the project resources. This will let the dev
- // tests his/her default values.
- if (hasLocale) {
- mLocaleCombo.add("Other");
- } else {
- mLocaleCombo.add("Any locale");
- }
-
// create language/region qualifier that will never be matched by qualified resources.
mLocaleList.add(new ResourceQualifier[] {
new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE),
@@ -1415,10 +2110,9 @@
setLocaleCombo(mState.locale[LOCALE_LANG],
mState.locale[LOCALE_REGION]);
} else {
- mLocaleCombo.select(0);
+ //mLocaleCombo.select(0);
+ selectLocale(mLocaleList.get(0));
}
-
- mThemeCombo.getParent().layout();
} finally {
mDisableUpdates--;
}
@@ -1469,112 +2163,18 @@
mDisableUpdates++;
try {
- // Reset the combo
- mThemeCombo.removeAll();
- mIsProjectTheme.clear();
-
- ArrayList<String> themes = new ArrayList<String>();
- String includedIn = mListener.getIncludedWithin();
-
- // First list any themes that are declared by the manifest
if (mEditedFile != null) {
- IProject project = mEditedFile.getProject();
- ManifestInfo manifest = ManifestInfo.get(project);
-
- // Look up the screen size for the current configuration
- ScreenSize screenSize = null;
- if (mState.device != null) {
- List<DeviceConfig> configs = mState.device.getConfigs();
- for (DeviceConfig config : configs) {
- ScreenSizeQualifier qualifier =
- config.getConfig().getScreenSizeQualifier();
- screenSize = qualifier.getValue();
- break;
- }
+ if (mState.theme == null || mState.theme.isEmpty()
+ || mListener.getIncludedWithin() != null) {
+ mState.theme = null;
+ getPreferredTheme();
}
- // Look up the default/fallback theme to use for this project (which
- // depends on the screen size when no particular theme is specified
- // in the manifest)
- String defaultTheme = manifest.getDefaultTheme(mState.target, screenSize);
-
- Map<String, String> activityThemes = manifest.getActivityThemes();
- String pkg = manifest.getPackage();
- String preferred = null;
- boolean isIncluded = includedIn != null;
- if (mState.theme == null || isIncluded) {
- String layoutName = ResourceHelper.getLayoutName(mEditedFile);
-
- // If we are rendering a layout in included context, pick the theme
- // from the outer layout instead
- if (includedIn != null) {
- layoutName = includedIn;
- }
-
- String activity = ManifestInfo.guessActivity(project, layoutName, pkg);
- if (activity != null) {
- preferred = activityThemes.get(activity);
- }
- if (preferred == null) {
- preferred = defaultTheme;
- }
- String preferredTheme = ResourceHelper.styleToTheme(preferred);
- if (includedIn == null) {
- mState.theme = preferredTheme;
- }
- boolean isProjectTheme = !preferred.startsWith(PREFIX_ANDROID_STYLE);
- mThemeCombo.add(preferredTheme);
- mIsProjectTheme.add(Boolean.valueOf(isProjectTheme));
-
- mThemeCombo.add(THEME_SEPARATOR);
- mIsProjectTheme.add(Boolean.FALSE);
- }
-
- // Create a sorted list of unique themes referenced in the manifest
- // (sort alphabetically, but place the preferred theme at the
- // top of the list)
- Set<String> themeSet = new HashSet<String>(activityThemes.values());
- themeSet.add(defaultTheme);
- List<String> themeList = new ArrayList<String>(themeSet);
- final String first = preferred;
- Collections.sort(themeList, new Comparator<String>() {
- @Override
- public int compare(String s1, String s2) {
- if (s1 == first) {
- return -1;
- } else if (s1 == first) {
- return 1;
- } else {
- return s1.compareTo(s2);
- }
- }
- });
-
- if (themeList.size() > 1 ||
- (themeList.size() == 1 && (preferred == null ||
- !preferred.equals(themeList.get(0))))) {
- for (String style : themeList) {
- String theme = ResourceHelper.styleToTheme(style);
-
- // Initialize the chosen theme to the first item
- // in the used theme list (that's what would be chosen
- // anyway) such that we stop attempting to look up
- // the associated activity (during initialization,
- // this method can be called repeatedly.)
- if (mState.theme == null) {
- mState.theme = theme;
- }
-
- boolean isProjectTheme = !style.startsWith(PREFIX_ANDROID_STYLE);
- mThemeCombo.add(theme);
- mIsProjectTheme.add(Boolean.valueOf(isProjectTheme));
- }
- mThemeCombo.add(THEME_SEPARATOR);
- mIsProjectTheme.add(Boolean.FALSE);
- }
+ assert mState.theme != null;
}
- // now get the themes and languages from the project.
- int projectThemeCount = 0;
+ mThemeList.clear();
+
+ ArrayList<String> themes = new ArrayList<String>();
ResourceRepository projectRes = mListener.getProjectResources();
// in cases where the opened file is not linked to a project, this could be null.
if (projectRes != null) {
@@ -1592,19 +2192,21 @@
// directly or indirectly a platform theme.
for (ResourceValue value : styleMap.values()) {
if (isTheme(value, styleMap, null)) {
- themes.add(value.getName());
+ String theme = value.getName();
+ themes.add(theme);
}
}
Collections.sort(themes);
for (String theme : themes) {
- mThemeCombo.add(theme);
- mIsProjectTheme.add(Boolean.TRUE);
+ if (!theme.startsWith(PREFIX_RESOURCE_REF)) {
+ theme = PREFIX_STYLE + theme;
+ }
+ mThemeList.add(theme);
}
}
}
- projectThemeCount = themes.size();
themes.clear();
}
@@ -1618,11 +2220,10 @@
// get the styles.
Map<String, ResourceValue> styles = frameworResources.get(ResourceType.STYLE);
-
// collect the themes out of all the styles.
for (ResourceValue value : styles.values()) {
String name = value.getName();
- if (name.startsWith("Theme.") || name.equals("Theme")) {
+ if (name.startsWith("Theme.") || name.equals("Theme")) { //$NON-NLS-1$ //$NON-NLS-2$
themes.add(value.getName());
}
}
@@ -1630,50 +2231,102 @@
// sort them and add them to the combo
Collections.sort(themes);
- if (projectThemeCount > 0 && themes.size() > 0) {
- mThemeCombo.add(THEME_SEPARATOR);
- mIsProjectTheme.add(Boolean.FALSE);
- }
-
for (String theme : themes) {
- mThemeCombo.add(theme);
- mIsProjectTheme.add(Boolean.FALSE);
+ if (!theme.startsWith(PREFIX_RESOURCE_REF)) {
+ theme = PREFIX_ANDROID_STYLE + theme;
+ }
+ mThemeList.add(theme);
}
themes.clear();
}
}
- // try to reselect the previous theme.
- boolean needDefaultSelection = true;
-
- if (mState.theme != null && includedIn == null) {
- final int count = mThemeCombo.getItemCount();
- for (int i = 0 ; i < count ; i++) {
- if (mState.theme.equals(mThemeCombo.getItem(i))) {
- mThemeCombo.select(i);
- needDefaultSelection = false;
- mThemeCombo.setEnabled(true);
+ // Migration: In the past we didn't store the style prefix in the settings;
+ // this meant we might lose track of whether the theme is a project style
+ // or a framework style. For now we need to migrate. Search through the
+ // theme list until we have a match
+ if (!mState.theme.startsWith(PREFIX_RESOURCE_REF)) {
+ String projectStyle = PREFIX_STYLE + mState.theme;
+ String frameworkStyle = PREFIX_ANDROID_STYLE + mState.theme;
+ for (String theme : mThemeList) {
+ if (theme.equals(projectStyle)) {
+ mState.theme = projectStyle;
+ break;
+ } else if (theme.equals(frameworkStyle)) {
+ mState.theme = frameworkStyle;
break;
}
}
}
- if (needDefaultSelection) {
- if (mThemeCombo.getItemCount() > 0) {
- mThemeCombo.select(0);
- mThemeCombo.setEnabled(true);
- } else {
- mThemeCombo.setEnabled(false);
- }
- }
+ // TODO: Handle the case where you have a theme persisted that isn't available??
+ // We could look up mState.theme and make sure it appears in the list! And if not,
+ // picking one.
- mThemeCombo.getParent().layout();
+ selectTheme(mState.theme);
} finally {
mDisableUpdates--;
}
+ }
- assert mIsProjectTheme.size() == mThemeCombo.getItemCount();
+ /** Returns the preferred theme, or null */
+ @Nullable
+ String getPreferredTheme() {
+ if (mListener == null) {
+ return null;
+ }
+ String includedIn = mListener.getIncludedWithin();
+ IProject project = mEditedFile.getProject();
+ ManifestInfo manifest = ManifestInfo.get(project);
+
+ // Look up the screen size for the current configuration
+ ScreenSize screenSize = null;
+ if (mState.device != null) {
+ List<DeviceConfig> configs = mState.device.getConfigs();
+ for (DeviceConfig config : configs) {
+ ScreenSizeQualifier qualifier =
+ config.getConfig().getScreenSizeQualifier();
+ screenSize = qualifier.getValue();
+ break;
+ }
+ }
+
+ // Look up the default/fallback theme to use for this project (which
+ // depends on the screen size when no particular theme is specified
+ // in the manifest)
+ String defaultTheme = manifest.getDefaultTheme(mState.target, screenSize);
+
+ String preferred = defaultTheme;
+ boolean isIncluded = includedIn != null;
+ if (mState.theme == null || isIncluded) {
+ // If we are rendering a layout in included context, pick the theme
+ // from the outer layout instead
+
+ // TODO: Pick a different activity when we're configuring an
+ // included layout??
+ //if (includedIn != null) {
+ // layoutName = includedIn;
+ //}
+
+ String activity = getSelectedActivity();
+ if (activity != null) {
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ preferred = activityThemes.get(activity);
+ }
+ if (preferred == null) {
+ preferred = defaultTheme;
+ }
+ if (includedIn == null) {
+ mState.theme = preferred;
+ }
+ }
+
+ if (mState.theme == null && includedIn == null) {
+ mState.theme = preferred;
+ }
+
+ return preferred;
}
// ---- getters for the config selection values ----
@@ -1774,13 +2427,19 @@
*
* @return the theme name, or null
*/
- public String getTheme() {
- int themeIndex = mThemeCombo.getSelectionIndex();
- if (themeIndex != -1) {
- return mThemeCombo.getItem(themeIndex);
+ @Nullable
+ public String getThemeName() {
+ String theme = getSelectedTheme();
+ if (theme != null) {
+ theme = ResourceHelper.styleToTheme(theme);
}
- return null;
+ return theme;
+ }
+
+ @Nullable
+ String getSelectedTheme() {
+ return (String) mThemeCombo.getData();
}
/**
@@ -1789,9 +2448,9 @@
* @return the device name, or null
*/
public String getDevice() {
- int deviceIndex = mDeviceCombo.getSelectionIndex();
- if (deviceIndex != -1) {
- return mDeviceCombo.getItem(deviceIndex);
+ LayoutDevice device = getSelectedDevice();
+ if (device != null) {
+ return device.getName();
}
return null;
@@ -1799,62 +2458,63 @@
/**
* Returns whether the current theme selection is a project theme.
- * <p/>The returned value is meaningless if {@link #getTheme()} returns <code>null</code>.
+ * <p/>The returned value is meaningless if {@link #getThemeName()} returns <code>null</code>.
* @return true for project theme, false for framework theme
*/
public boolean isProjectTheme() {
- return mIsProjectTheme.get(mThemeCombo.getSelectionIndex()).booleanValue();
- }
+ String theme = getSelectedTheme();
+ if (theme != null) {
+ assert theme.startsWith(PREFIX_STYLE) || theme.startsWith(PREFIX_ANDROID_STYLE);
- public IAndroidTarget getRenderingTarget() {
- int index = mTargetCombo.getSelectionIndex();
- if (index >= 0) {
- return mTargetList.get(index);
+ return ResourceHelper.isProjectStyle(theme);
}
- return null;
+ return false;
+ }
+
+ @Nullable
+ public IAndroidTarget getRenderingTarget() {
+ return getSelectedTarget();
}
/**
* Loads the list of {@link IAndroidTarget} and inits the UI with it.
*/
private void initTargets() {
- mTargetCombo.removeAll();
mTargetList.clear();
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
IAndroidTarget[] targets = currentSdk.getTargets();
- int match = -1;
+ IAndroidTarget match = null;
for (int i = 0 ; i < targets.length; i++) {
// FIXME: add check based on project minSdkVersion
if (targets[i].hasRenderingLibrary()) {
- mTargetCombo.add(targets[i].getShortClasspathName());
mTargetList.add(targets[i]);
if (mRenderingTarget != null) {
// use equals because the rendering could be from a previous SDK, so
// it may not be the same instance.
if (mRenderingTarget.equals(targets[i])) {
- match = mTargetList.indexOf(targets[i]);
+ match = targets[i];
}
} else if (mProjectTarget == targets[i]) {
- match = mTargetList.indexOf(targets[i]);
+ match = targets[i];
}
}
}
mTargetCombo.setEnabled(mTargetList.size() > 1);
- if (match == -1) {
- mTargetCombo.deselectAll();
+ if (match == null) {
+ selectTarget(null);
// the rendering target is the same as the project.
mRenderingTarget = mProjectTarget;
} else {
- mTargetCombo.select(match);
+ selectTarget(match);
// set the rendering target to the new object.
- mRenderingTarget = mTargetList.get(match);
+ mRenderingTarget = match;
}
}
}
@@ -1872,31 +2532,68 @@
}
- // remove older devices if applicable
- mDeviceCombo.removeAll();
- mDeviceConfigCombo.removeAll();
-
// fill with the devices
- if (mDeviceList != null) {
- for (LayoutDevice device : mDeviceList) {
- mDeviceCombo.add(device.getName());
- }
- mDeviceCombo.select(0);
+ if (mDeviceList != null && mDeviceList.size() > 0) {
+ LayoutDevice first = mDeviceList.get(0);
+ selectDevice(first);
+ List<DeviceConfig> configs = first.getConfigs();
+ selectDeviceConfig(configs.get(0));
+ } else {
+ selectDevice(null);
+ }
+ }
- if (mDeviceList.size() > 0) {
- List<DeviceConfig> configs = mDeviceList.get(0).getConfigs();
- for (DeviceConfig config : configs) {
- mDeviceConfigCombo.add(config.getName());
- }
- mDeviceConfigCombo.select(0);
- if (configs.size() == 1) {
- mDeviceConfigCombo.setEnabled(false);
- }
- }
+ Image getOrientationIcon(ScreenOrientation orientation, boolean flip) {
+ IconFactory icons = IconFactory.getInstance();
+ switch (orientation) {
+ case LANDSCAPE:
+ return icons.getIcon(flip ? ICON_LANDSCAPE_FLIP : ICON_LANDSCAPE);
+ case SQUARE:
+ return icons.getIcon(ICON_SQUARE);
+ case PORTRAIT:
+ default:
+ return icons.getIcon(flip ? ICON_PORTRAIT_FLIP : ICON_PORTRAIT);
+ }
+ }
+
+ ImageDescriptor getOrientationImage(ScreenOrientation orientation, boolean flip) {
+ IconFactory icons = IconFactory.getInstance();
+ switch (orientation) {
+ case LANDSCAPE:
+ return icons.getImageDescriptor(flip ? ICON_LANDSCAPE_FLIP : ICON_LANDSCAPE);
+ case SQUARE:
+ return icons.getImageDescriptor(ICON_SQUARE);
+ case PORTRAIT:
+ default:
+ return icons.getImageDescriptor(flip ? ICON_PORTRAIT_FLIP : ICON_PORTRAIT);
+ }
+ }
+
+ @NonNull
+ ScreenOrientation getOrientation(DeviceConfig config) {
+ ScreenOrientationQualifier qualifier = config.getConfig().getScreenOrientationQualifier();
+ ScreenOrientation orientation = null;
+ if (qualifier != null) {
+ orientation = qualifier.getValue();
}
- // add the custom item
- mDeviceCombo.add("Custom...");
+ if (orientation == null) {
+ orientation = ScreenOrientation.PORTRAIT;
+ }
+
+ return orientation;
+ }
+
+ void selectDeviceConfig(@Nullable DeviceConfig config) {
+ mOrientationCombo.setData(config);
+
+ DeviceConfig nextConfig = getNextDeviceConfig(config);
+ mOrientationCombo.setImage(getOrientationIcon(getOrientation(nextConfig),
+ nextConfig != config));
+ }
+
+ DeviceConfig getSelectedDeviceConfig() {
+ return (DeviceConfig) mOrientationCombo.getData();
}
/**
@@ -1904,32 +2601,33 @@
* @param device the device to select
* @return true if the device was found.
*/
- private boolean selectDevice(LayoutDevice device) {
- final int count = mDeviceList.size();
- for (int i = 0 ; i < count ; i++) {
- // since device comes from mDeviceList, we can use the == operator.
- if (device == mDeviceList.get(i)) {
- mDeviceCombo.select(i);
- return true;
- }
+ private boolean selectDevice(@Nullable LayoutDevice device) {
+ mDeviceCombo.setData(device);
+ if (device != null) {
+ mDeviceCombo.setText(getDeviceLabel(device, true));
+ } else {
+ mDeviceCombo.setText("Device");
}
+ resizeToolBar();
return false;
}
+ LayoutDevice getSelectedDevice() {
+ return (LayoutDevice) mDeviceCombo.getData();
+ }
+
/**
* Selects a config by name.
* @param name the name of the config to select.
*/
private void selectConfig(String name) {
- final int count = mDeviceConfigCombo.getItemCount();
- for (int i = 0 ; i < count ; i++) {
- String item = mDeviceConfigCombo.getItem(i);
- if (name.equals(item)) {
- mDeviceConfigCombo.select(i);
- return;
- }
+ LayoutDevice device = getSelectedDevice();
+ DeviceConfig config = null;
+ if (device != null) {
+ config = device.getDeviceConfigByName(name);
}
+ selectDeviceConfig(config);
}
/**
@@ -1945,33 +2643,17 @@
String newConfigName = null;
- int deviceIndex = mDeviceCombo.getSelectionIndex();
- if (deviceIndex != -1) {
- // check if the user is asking for the custom item
- if (deviceIndex == mDeviceCombo.getItemCount() - 1) {
- onCustomDeviceConfig();
- return;
- }
-
+ DeviceConfig prevConfig = getSelectedDeviceConfig();
+ LayoutDevice device = getSelectedDevice();
+ if (mState.device != null && prevConfig != null && device != null) {
// get the previous config, so that we can look for a close match
- if (mState.device != null) {
- int index = mDeviceConfigCombo.getSelectionIndex();
- if (index != -1) {
- FolderConfiguration oldConfig = mState.device.getFolderConfigByName(
- mDeviceConfigCombo.getItem(index));
-
- LayoutDevice newDevice = mDeviceList.get(deviceIndex);
-
- newConfigName = getClosestMatch(oldConfig, newDevice.getConfigs());
- }
- }
-
- mState.device = mDeviceList.get(deviceIndex);
- } else {
- mState.device = null;
+ FolderConfiguration oldConfig = mState.device.getFolderConfigByName(
+ prevConfig.getName());
+ newConfigName = getClosestMatch(oldConfig, device.getConfigs());
}
+ mState.device = device;
- fillConfigCombo(newConfigName);
+ selectConfig(newConfigName);
computeCurrentConfig();
@@ -2086,34 +2768,9 @@
}
/**
- * fills the config combo with new values based on {@link #mState}.device.
- * @param refName an optional name. if set the selection will match this name (if found)
- */
- private void fillConfigCombo(String refName) {
- mDeviceConfigCombo.removeAll();
-
- if (mState.device != null) {
- int selectionIndex = 0;
- int i = 0;
-
- for (DeviceConfig config : mState.device.getConfigs()) {
- mDeviceConfigCombo.add(config.getName());
-
- if (config.getName().equals(refName)) {
- selectionIndex = i;
- }
- i++;
- }
-
- mDeviceConfigCombo.select(selectionIndex);
- mDeviceConfigCombo.setEnabled(mState.device.getConfigs().size() > 1);
- }
- }
-
- /**
* Called when the device config selection changes.
*/
- private void onDeviceConfigChange() {
+ void onDeviceConfigChange() {
// because changing the content of a combo triggers a change event, respect the
// mDisableUpdates flag
if (mDisableUpdates > 0) {
@@ -2144,18 +2801,6 @@
saveRenderState();
}
- private void onDockChange() {
- if (computeCurrentConfig() && mListener != null) {
- mListener.onConfigurationChange();
- }
- }
-
- private void onDayChange() {
- if (computeCurrentConfig() && mListener != null) {
- mListener.onConfigurationChange();
- }
- }
-
/**
* Call back for api level combo selection
*/
@@ -2172,8 +2817,7 @@
mListener.onRenderingTargetPreChange(mRenderingTarget);
}
- int index = mTargetCombo.getSelectionIndex();
- mRenderingTarget = mTargetList.get(index);
+ mRenderingTarget = getRenderingTarget();
boolean computeOk = computeCurrentConfig();
@@ -2208,74 +2852,43 @@
if (mState.device != null) {
// get the device config from the device/config combos.
- int configIndex = mDeviceConfigCombo.getSelectionIndex();
- String name = mDeviceConfigCombo.getItem(configIndex);
- FolderConfiguration config = mState.device.getFolderConfigByName(name);
+ FolderConfiguration config = mState.device.getFolderConfigByName(
+ getSelectedDeviceConfig().getName());
// replace the config with the one from the device
mCurrentConfig.set(config);
// replace the locale qualifiers with the one coming from the locale combo
- int index = mLocaleCombo.getSelectionIndex();
- if (index != -1) {
- ResourceQualifier[] localeQualifiers = mLocaleList.get(index);
-
+ ResourceQualifier[] localeQualifiers = getSelectedLocale();
+ if (localeQualifiers != null) {
mCurrentConfig.setLanguageQualifier(
(LanguageQualifier)localeQualifiers[LOCALE_LANG]);
mCurrentConfig.setRegionQualifier(
(RegionQualifier)localeQualifiers[LOCALE_REGION]);
}
- index = mUiModeCombo.getSelectionIndex();
- if (index == -1) {
- index = 0; // no selection = 0
- }
- mCurrentConfig.setUiModeQualifier(new UiModeQualifier(UiMode.getByIndex(index)));
-
- index = mNightCombo.getSelectionIndex();
- if (index == -1) {
- index = 0; // no selection = 0
- }
- mCurrentConfig.setNightModeQualifier(
- new NightModeQualifier(NightMode.getByIndex(index)));
-
// replace the API level by the selection of the combo
- index = mTargetCombo.getSelectionIndex();
- if (index == -1) {
- index = mTargetList.indexOf(mProjectTarget);
+ IAndroidTarget target = getRenderingTarget();
+ if (target == null) {
+ target = mProjectTarget;
}
- if (index != -1) {
- IAndroidTarget target = mTargetList.get(index);
-
- if (target != null) {
- mCurrentConfig.setVersionQualifier(
- new VersionQualifier(target.getVersion().getApiLevel()));
- }
+ if (target != null) {
+ mCurrentConfig.setVersionQualifier(
+ new VersionQualifier(target.getVersion().getApiLevel()));
}
- // update the create button.
- checkCreateEnable();
-
return true;
}
return false;
}
- private void onThemeChange() {
+ void onThemeChange() {
saveState();
- int themeIndex = mThemeCombo.getSelectionIndex();
- if (themeIndex != -1) {
- String theme = mThemeCombo.getItem(themeIndex);
-
- if (theme.equals(THEME_SEPARATOR)) {
- mThemeCombo.select(0);
- }
-
- if (mListener != null) {
- mListener.onThemeChange();
- }
+ String theme = getSelectedTheme();
+ if (theme != null && mListener != null) {
+ mListener.onThemeChange();
}
}
@@ -2349,10 +2962,6 @@
return false;
}
- private void checkCreateEnable() {
- mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
- }
-
/**
* Checks whether the current edited file is the best match for a given config.
* <p/>
@@ -2434,9 +3043,8 @@
// Sync render target
IAndroidTarget target = pair.getSecond();
if (target != null) {
- int targetIndex = mTargetList.indexOf(target);
- if (targetIndex != mTargetCombo.getSelectionIndex()) {
- mTargetCombo.select(targetIndex);
+ if (getRenderingTarget() != target) {
+ selectTarget(target);
renderTargetChanged = true;
}
}
@@ -2453,8 +3061,7 @@
if (mListener != null && mRenderingTarget != null) {
mListener.onRenderingTargetPreChange(mRenderingTarget);
}
- int targetIndex = mTargetCombo.getSelectionIndex();
- mRenderingTarget = mTargetList.get(targetIndex);
+ mRenderingTarget = target;
}
// Compute the new configuration; we want to do this both for locale changes
@@ -2556,10 +3163,8 @@
private void saveRenderState() {
IProject project = mEditedFile.getProject();
try {
- int index = mLocaleCombo.getSelectionIndex();
- ResourceQualifier[] locale = mLocaleList.get(index);
- index = mTargetCombo.getSelectionIndex();
- IAndroidTarget target = mTargetList.get(index);
+ ResourceQualifier[] locale = getSelectedLocale();
+ IAndroidTarget target = getRenderingTarget();
// Generate a persistent string from locale+target
StringBuilder sb = new StringBuilder();
@@ -2583,4 +3188,49 @@
AdtPlugin.log(e, null);
}
}
+
+ // ---- Implements SelectionListener ----
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mDisableUpdates > 0) {
+ return;
+ }
+
+ final Object source = e.getSource();
+ if (source == mOrientationCombo) {
+ flipOrientation();
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ /** Returns the file whose rendering is being configured by this configuration composite */
+ IFile getEditedFile() {
+ return mEditedFile;
+ }
+
+ UiMode getSelectedUiMode() {
+ return mState.uiMode;
+ }
+
+ NightMode getSelectedNightMode() {
+ return mState.night;
+ }
+
+ void selectNightMode(NightMode night) {
+ mState.night = night;
+ if (computeCurrentConfig() && mListener != null) {
+ mListener.onConfigurationChange();
+ }
+ }
+
+ void selectUiMode(UiMode uiMode) {
+ mState.uiMode = uiMode;
+ if (computeCurrentConfig() && mListener != null) {
+ mListener.onConfigurationChange();
+ }
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java
new file mode 100644
index 0000000..c2f32c5
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SubmenuAction;
+import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice;
+import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
+import com.android.resources.NightMode;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.UiMode;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.util.List;
+
+/**
+ * Action which creates a submenu that shows the available orientations as well
+ * as some related options for night mode and dock mode
+ */
+class OrientationMenuAction extends SubmenuAction {
+ // Constants used to indicate what type of menu is being shown, such that
+ // the submenus can lazily construct their contents
+ private static final int MENU_NIGHTMODE = 1;
+ private static final int MENU_UIMODE = 2;
+
+ private final ConfigurationComposite mConfiguration;
+ /** Type of menu; one of the constants {@link #MENU_NIGHTMODE} etc */
+ private final int mType;
+
+ OrientationMenuAction(int type, String title, ConfigurationComposite configuration) {
+ super(title);
+ mType = type;
+ mConfiguration = configuration;
+ }
+
+ static void showMenu(ConfigurationComposite configuration, ToolItem combo) {
+ MenuManager manager = new MenuManager();
+
+ // Show toggles for all the available configurations
+ DeviceConfig current = configuration.getSelectedDeviceConfig();
+ LayoutDevice device = configuration.getSelectedDevice();
+ if (device != null) {
+ List<DeviceConfig> configs = device.getConfigs();
+
+ if (configs.size() > 1 && current != null) {
+ DeviceConfig flip = configuration.getNextDeviceConfig(current);
+ manager.add(new DeviceConfigAction(configuration,
+ String.format("Switch to %1$s", flip.getName()), flip, false, true));
+ manager.add(new Separator());
+ }
+
+ for (DeviceConfig config : configs) {
+ manager.add(new DeviceConfigAction(configuration, config.getName(),
+ config, config == current, false));
+ }
+ manager.add(new Separator());
+ }
+ manager.add(new OrientationMenuAction(MENU_UIMODE, "UI Mode", configuration));
+ manager.add(new Separator());
+ manager.add(new OrientationMenuAction(MENU_NIGHTMODE, "Night Mode", configuration));
+
+ Menu menu = manager.createContextMenu(configuration.getShell());
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+
+ @Override
+ protected void addMenuItems(Menu menu) {
+ switch (mType) {
+ case MENU_NIGHTMODE: {
+ NightMode selected = mConfiguration.getSelectedNightMode();
+ for (NightMode mode : NightMode.values()) {
+ boolean checked = mode == selected;
+ SelectNightModeAction action = new SelectNightModeAction(mode, checked);
+ new ActionContributionItem(action).fill(menu, -1);
+
+ }
+ break;
+ }
+ case MENU_UIMODE: {
+ UiMode selected = mConfiguration.getSelectedUiMode();
+ for (UiMode mode : UiMode.values()) {
+ boolean checked = mode == selected;
+ SelectUiModeAction action = new SelectUiModeAction(mode, checked);
+ new ActionContributionItem(action).fill(menu, -1);
+ }
+ break;
+ }
+ }
+ }
+
+ private class SelectNightModeAction extends Action {
+ private final NightMode mMode;
+
+ private SelectNightModeAction(NightMode mode, boolean checked) {
+ super(mode.getLongDisplayValue(), IAction.AS_RADIO_BUTTON);
+ mMode = mode;
+ if (checked) {
+ setChecked(true);
+ }
+ }
+
+ @Override
+ public void run() {
+ mConfiguration.selectNightMode(mMode);
+ }
+ }
+
+ private class SelectUiModeAction extends Action {
+ private final UiMode mMode;
+
+ private SelectUiModeAction(UiMode mode, boolean checked) {
+ super(mode.getLongDisplayValue(), IAction.AS_RADIO_BUTTON);
+ mMode = mode;
+ if (checked) {
+ setChecked(true);
+ }
+ }
+
+ @Override
+ public void run() {
+ mConfiguration.selectUiMode(mMode);
+ }
+ }
+
+ private static class DeviceConfigAction extends Action {
+ private final ConfigurationComposite mConfiguration;
+ private final DeviceConfig mConfig;
+
+ private DeviceConfigAction(ConfigurationComposite configuration, String title,
+ DeviceConfig config, boolean checked, boolean flip) {
+ super(title, IAction.AS_RADIO_BUTTON);
+ mConfiguration = configuration;
+ mConfig = config;
+ if (checked) {
+ setChecked(true);
+ }
+ ScreenOrientation orientation = configuration.getOrientation(config);
+ setImageDescriptor(configuration.getOrientationImage(orientation, flip));
+ }
+
+ @Override
+ public void run() {
+ mConfiguration.selectDeviceConfig(mConfig);
+ mConfiguration.onDeviceConfigChange();
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java
new file mode 100644
index 0000000..b8ba48e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+
+/**
+ * Action which brings up the "Create new XML File" wizard, pre-selected with the
+ * animation category
+ */
+class SelectThemeAction extends Action {
+ private final ConfigurationComposite mConfiguration;
+ private final String mTheme;
+
+ public SelectThemeAction(ConfigurationComposite configuration, String title, String theme,
+ boolean selected) {
+ super(title, IAction.AS_RADIO_BUTTON);
+ mConfiguration = configuration;
+ mTheme = theme;
+ if (selected) {
+ setChecked(selected);
+ }
+ }
+
+ @Override
+ public void run() {
+ mConfiguration.selectTheme(mTheme);
+ mConfiguration.onThemeChange();
+ }
+}
\ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java
new file mode 100644
index 0000000..262d317
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog;
+
+/**
+ * A dialog to let the user select a theme
+ */
+public class ThemeChooser extends AbstractElementListSelectionDialog {
+ /** The return code from the dialog for the user choosing "Clear" */
+ public static final int CLEAR_RETURN_CODE = -5;
+ /** The dialog button ID for the user choosing "Clear" */
+ private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE;
+
+ private String mCurrentResource;
+ private String[] mThemes;
+
+ private ThemeChooser(String[] themes, Shell parent) {
+ super(parent, new LabelProvider());
+ mThemes = themes;
+
+ setTitle("Theme Chooser");
+ setMessage(String.format("Choose a theme"));
+ }
+
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/);
+ super.createButtonsForButtonBar(parent);
+ }
+
+ @Override
+ protected void buttonPressed(int buttonId) {
+ super.buttonPressed(buttonId);
+
+ if (buttonId == CLEAR_BUTTON_ID) {
+ assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL;
+ setReturnCode(CLEAR_RETURN_CODE);
+ close();
+ }
+ }
+
+ private void setCurrentResource(String resource) {
+ mCurrentResource = resource;
+ }
+
+ private String getCurrentResource() {
+ return mCurrentResource;
+ }
+
+ @Override
+ protected void computeResult() {
+ computeResultFromSelection();
+ }
+
+ private void computeResultFromSelection() {
+ if (getSelectionIndex() == -1) {
+ mCurrentResource = null;
+ return;
+ }
+
+ Object[] elements = getSelectedElements();
+ if (elements.length == 1 && elements[0] instanceof String) {
+ String item = (String) elements[0];
+
+ mCurrentResource = item;
+ }
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite top = (Composite)super.createDialogArea(parent);
+
+ createMessageArea(top);
+
+ createFilterText(top);
+ createFilteredList(top);
+
+ setupResourceList();
+ selectResourceString(mCurrentResource);
+
+ return top;
+ }
+
+ /**
+ * Setups the current list.
+ */
+ private String[] setupResourceList() {
+ setListElements(mThemes);
+ fFilteredList.setEnabled(mThemes.length > 0);
+
+ return mThemes;
+ }
+
+ /**
+ * Select an item by its name, if possible.
+ */
+ private void selectItemName(String itemName, String[] items) {
+ if (itemName == null || items == null) {
+ return;
+ }
+ setSelection(new String[] { itemName });
+ }
+
+ /**
+ * Select an item by its full resource string.
+ * This also selects between project and system repository based on the resource string.
+ */
+ private void selectResourceString(String item) {
+ String itemName = item;
+
+ // Update the list
+ String[] items = setupResourceList();
+
+ // If we have a selection name, select it
+ if (itemName != null) {
+ selectItemName(itemName, items);
+ }
+ }
+
+ public static String chooseResource(
+ String[] themes,
+ String currentTheme) {
+ Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ if (shell == null) {
+ return null;
+ }
+
+ ThemeChooser dialog = new ThemeChooser(themes, shell);
+ dialog.setCurrentResource(currentTheme);
+
+ int result = dialog.open();
+ if (result == ThemeChooser.CLEAR_RETURN_CODE) {
+ return ""; //$NON-NLS-1$
+ } else if (result == Window.OK) {
+ return dialog.getCurrentResource();
+ }
+
+ return null;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
new file mode 100644
index 0000000..194cb3f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
+
+import com.android.ide.eclipse.adt.internal.editors.Hyperlinks;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SubmenuAction;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.text.hyperlink.IHyperlink;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Action which creates a submenu displaying available themes
+ */
+class ThemeMenuAction extends SubmenuAction {
+ private static final String DEVICE_LIGHT_PREFIX =
+ PREFIX_ANDROID_STYLE + "Theme.DeviceDefault.Light"; //$NON-NLS-1$
+ private static final String HOLO_LIGHT_PREFIX =
+ PREFIX_ANDROID_STYLE + "Theme.Holo.Light"; //$NON-NLS-1$
+ private static final String DEVICE_PREFIX =
+ PREFIX_ANDROID_STYLE + "Theme.DeviceDefault"; //$NON-NLS-1$
+ private static final String HOLO_PREFIX =
+ PREFIX_ANDROID_STYLE + "Theme.Holo"; //$NON-NLS-1$
+ private static final String LIGHT_PREFIX =
+ PREFIX_ANDROID_STYLE +"Theme.Light"; //$NON-NLS-1$
+ private static final String THEME_PREFIX =
+ PREFIX_ANDROID_STYLE +"Theme"; //$NON-NLS-1$
+
+ // Constants used to indicate what type of menu is being shown, such that
+ // the submenus can lazily construct their contents
+ private static final int MENU_MANIFEST = 1;
+ private static final int MENU_PROJECT = 2;
+ private static final int MENU_THEME = 3;
+ private static final int MENU_THEME_LIGHT = 4;
+ private static final int MENU_HOLO = 5;
+ private static final int MENU_HOLO_LIGHT = 6;
+ private static final int MENU_DEVICE = 7;
+ private static final int MENU_DEVICE_LIGHT = 8;
+ private static final int MENU_ALL = 9;
+
+ private final ConfigurationComposite mConfiguration;
+ private final List<String> mThemeList;
+ /** Type of menu; one of the constants {@link #MENU_ALL} etc */
+ private final int mType;
+
+ ThemeMenuAction(int type, String title, ConfigurationComposite configuration,
+ List<String> themeList) {
+ super(title);
+ mType = type;
+ mConfiguration = configuration;
+ mThemeList = themeList;
+ }
+
+ static void showThemeMenu(ConfigurationComposite configuration, ToolItem combo,
+ List<String> themeList) {
+ MenuManager manager = new MenuManager();
+
+ // First show the currently selected theme (grayed out since you can't
+ // reselect it)
+ String currentTheme = configuration.getSelectedTheme();
+ String currentName = null;
+ if (currentTheme != null) {
+ currentName = ResourceHelper.styleToTheme(currentTheme);
+ SelectThemeAction action = new SelectThemeAction(configuration,
+ currentName,
+ currentTheme,
+ true /* selected */);
+ action.setEnabled(false);
+ manager.add(action);
+ manager.add(new Separator());
+ }
+
+ String preferred = configuration.getPreferredTheme();
+ if (preferred != null && !preferred.equals(currentTheme)) {
+ manager.add(new SelectThemeAction(configuration,
+ ResourceHelper.styleToTheme(preferred),
+ preferred, false /* selected */));
+ manager.add(new Separator());
+ }
+
+ IAndroidTarget target = configuration.getRenderingTarget();
+ int apiLevel = target.getVersion().getApiLevel();
+ boolean hasHolo = apiLevel >= 11; // Honeycomb
+ boolean hasDeviceDefault = apiLevel >= 14; // ICS
+
+ // TODO: Add variations of the current theme here, e.g.
+ // if you're using Theme.Holo, add Theme.Holo.Dialog, Theme.Holo.Panel,
+ // Theme.Holo.Wallpaper etc
+
+ manager.add(new ThemeMenuAction(MENU_PROJECT, "Project Themes",
+ configuration, themeList));
+ manager.add(new ThemeMenuAction(MENU_MANIFEST, "Manifest Themes",
+ configuration, themeList));
+
+ manager.add(new Separator());
+
+ if (hasHolo) {
+ manager.add(new ThemeMenuAction(MENU_HOLO, "Holo",
+ configuration, themeList));
+ manager.add(new ThemeMenuAction(MENU_HOLO_LIGHT, "Holo.Light",
+ configuration, themeList));
+ }
+ if (hasDeviceDefault) {
+ manager.add(new ThemeMenuAction(MENU_DEVICE, "DeviceDefault",
+ configuration, themeList));
+ manager.add(new ThemeMenuAction(MENU_DEVICE_LIGHT, "DeviceDefault.Light",
+ configuration, themeList));
+ }
+ manager.add(new ThemeMenuAction(MENU_THEME, "Theme",
+ configuration, themeList));
+ manager.add(new ThemeMenuAction(MENU_THEME_LIGHT, "Theme.Light",
+ configuration, themeList));
+
+ // TODO: Add generic types like Wallpaper, Dialog, Alert, etc here, with
+ // submenus for picking it within each theme category?
+
+ manager.add(new Separator());
+ manager.add(new ThemeMenuAction(MENU_ALL, "All",
+ configuration, themeList));
+
+ if (currentTheme != null) {
+ assert currentName != null;
+ manager.add(new Separator());
+ String title = String.format("Open %1$s Declaration...", currentName);
+ manager.add(new OpenThemeAction(title, configuration.getEditedFile(), currentTheme));
+ }
+
+ Menu menu = manager.createContextMenu(configuration.getShell());
+
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
+ menu.setVisible(true);
+ }
+
+ @Override
+ protected void addMenuItems(Menu menu) {
+ switch (mType) {
+ case MENU_ALL:
+ addMenuItems(menu, mThemeList);
+ break;
+
+ case MENU_MANIFEST: {
+ IProject project = mConfiguration.getEditedFile().getProject();
+ ManifestInfo manifest = ManifestInfo.get(project);
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ String activity = mConfiguration.getSelectedActivity();
+ if (activity != null) {
+ String theme = activityThemes.get(activity);
+ if (theme != null) {
+ addMenuItem(menu, theme, isSelectedTheme(theme));
+ }
+ }
+
+ String manifestTheme = manifest.getmManifestTheme();
+ if (activityThemes.size() > 0 || manifestTheme != null) {
+ Set<String> allThemes = new HashSet<String>(activityThemes.values());
+ if (manifestTheme != null) {
+ allThemes.add(manifestTheme);
+ }
+ List<String> sorted = new ArrayList<String>(allThemes);
+ Collections.sort(sorted);
+ String current = mConfiguration.getSelectedTheme();
+ for (String theme : sorted) {
+ boolean selected = theme.equals(current);
+ theme = ResourceHelper.styleToTheme(theme);
+ addMenuItem(menu, theme, selected);
+ }
+ } else {
+ addDisabledMessageItem("No themes are registered in the manifest");
+ }
+ break;
+ }
+ case MENU_PROJECT: {
+ int size = mThemeList.size();
+ List<String> themes = new ArrayList<String>(size);
+ for (int i = 0; i < size; i++) {
+ String theme = mThemeList.get(i);
+ if (ResourceHelper.isProjectStyle(theme)) {
+ themes.add(theme);
+ }
+ }
+ if (themes.isEmpty()) {
+ addDisabledMessageItem("There are no local theme styles in the project");
+ } else {
+ addMenuItems(menu, themes);
+ }
+ break;
+ }
+ case MENU_THEME: {
+ // Can't just use the usual filterThemes() call here because we need
+ // to exclude on multiple prefixes: Holo, DeviceDefault, Light, ...
+ List<String> themes = new ArrayList<String>(mThemeList.size());
+ for (String theme : mThemeList) {
+ if (theme.startsWith(THEME_PREFIX)
+ && !theme.startsWith(LIGHT_PREFIX)
+ && !theme.startsWith(HOLO_PREFIX)
+ && !theme.startsWith(DEVICE_PREFIX)) {
+ themes.add(theme);
+ }
+ }
+
+ addMenuItems(menu, themes);
+ break;
+ }
+ case MENU_THEME_LIGHT:
+ addMenuItems(menu, filterThemes(LIGHT_PREFIX, null));
+ break;
+ case MENU_HOLO:
+ addMenuItems(menu, filterThemes(HOLO_PREFIX, HOLO_LIGHT_PREFIX));
+ break;
+ case MENU_HOLO_LIGHT:
+ addMenuItems(menu, filterThemes(HOLO_LIGHT_PREFIX, null));
+ break;
+ case MENU_DEVICE:
+ addMenuItems(menu, filterThemes(DEVICE_PREFIX, DEVICE_LIGHT_PREFIX));
+ break;
+ case MENU_DEVICE_LIGHT:
+ addMenuItems(menu, filterThemes(DEVICE_LIGHT_PREFIX, null));
+ break;
+ }
+ }
+
+ private List<String> filterThemes(String include, String exclude) {
+ List<String> themes = new ArrayList<String>(mThemeList.size());
+ for (String theme : mThemeList) {
+ if (theme.startsWith(include) && (exclude == null || !theme.startsWith(exclude))) {
+ themes.add(theme);
+ }
+ }
+
+ return themes;
+ }
+
+ private void addMenuItems(Menu menu, List<String> themes) {
+ String current = mConfiguration.getSelectedTheme();
+ for (String theme : themes) {
+ addMenuItem(menu, theme, theme.equals(current));
+ }
+ }
+
+ private boolean isSelectedTheme(String theme) {
+ return theme.equals(mConfiguration.getSelectedTheme());
+ }
+
+ private void addMenuItem(Menu menu, String theme, boolean selected) {
+ String title = ResourceHelper.styleToTheme(theme);
+ SelectThemeAction action = new SelectThemeAction(mConfiguration, title, theme, selected);
+ new ActionContributionItem(action).fill(menu, -1);
+ }
+
+ private static class OpenThemeAction extends Action {
+ private final String mTheme;
+ private final IFile mFile;
+
+ private OpenThemeAction(String title, IFile file, String theme) {
+ super(title, IAction.AS_PUSH_BUTTON);
+ mFile = file;
+ mTheme = theme;
+ }
+
+ @Override
+ public void run() {
+ IProject project = mFile.getProject();
+ IHyperlink[] links = Hyperlinks.getResourceLinks(null, mTheme, project, null);
+ if (links != null && links.length > 0) {
+ IHyperlink link = links[0];
+ link.open();
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
index 5abbb0c..23aa2a0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
@@ -16,19 +16,43 @@
package com.android.ide.eclipse.adt.internal.editors.layout.descriptors;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP;
+import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI;
+import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceFile;
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.common.resources.platform.AttrsXmlParser;
import com.android.ide.common.resources.platform.ViewClassInfo;
+import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;
+import com.google.common.collect.ObjectArrays;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
@@ -36,8 +60,13 @@
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.swt.graphics.Image;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Service responsible for creating/managing {@link ViewElementDescriptor} objects for custom
@@ -162,11 +191,24 @@
if (parentDescriptor != null) {
// we have a valid parent, lets create a new ViewElementDescriptor.
+ List<AttributeDescriptor> attrList = new ArrayList<AttributeDescriptor>();
+ List<AttributeDescriptor> paramList = new ArrayList<AttributeDescriptor>();
+ findCustomDescriptors(project, type, attrList, paramList);
+ AttributeDescriptor[] attributes =
+ getAttributeDescriptor(type, parentDescriptor);
+ if (!attrList.isEmpty()) {
+ attributes = join(attrList, attributes);
+ }
+ AttributeDescriptor[] layoutAttributes =
+ getLayoutAttributeDescriptors(type, parentDescriptor);
+ if (!paramList.isEmpty()) {
+ layoutAttributes = join(paramList, layoutAttributes);
+ }
String name = DescriptorsUtils.getBasename(fqcn);
ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
- getAttributeDescriptor(type, parentDescriptor),
- getLayoutAttributeDescriptors(type, parentDescriptor),
+ attributes,
+ layoutAttributes,
parentDescriptor.getChildren());
descriptor.setSuperClass(parentDescriptor);
@@ -193,6 +235,168 @@
return null;
}
+ private static AttributeDescriptor[] join(
+ @NonNull List<AttributeDescriptor> attributeList,
+ @NonNull AttributeDescriptor[] attributes) {
+ if (!attributeList.isEmpty()) {
+ return ObjectArrays.concat(
+ attributeList.toArray(new AttributeDescriptor[attributeList.size()]),
+ attributes,
+ AttributeDescriptor.class);
+ } else {
+ return attributes;
+ }
+
+ }
+
+ /** Cache used by {@link #getParser(ResourceFile)} */
+ private Map<ResourceFile, AttrsXmlParser> mParserCache;
+
+ private AttrsXmlParser getParser(ResourceFile file) {
+ if (mParserCache == null) {
+ mParserCache = new HashMap<ResourceFile, AttrsXmlParser>();
+ }
+
+ AttrsXmlParser parser = mParserCache.get(file);
+ if (parser == null) {
+ parser = new AttrsXmlParser(
+ file.getFile().getOsLocation(),
+ AdtPlugin.getDefault());
+ parser.preload();
+ mParserCache.put(file, parser);
+ }
+
+ return parser;
+ }
+
+ /** Compute/find the styleable resources for the given type, if possible */
+ private void findCustomDescriptors(
+ IProject project,
+ IType type,
+ List<AttributeDescriptor> customAttributes,
+ List<AttributeDescriptor> customLayoutAttributes) {
+ // Look up the project where the type is declared (could be a library project;
+ // we cannot use type.getJavaProject().getProject())
+ IProject library = getProjectDeclaringType(type);
+ if (library == null) {
+ library = project;
+ }
+
+ String className = type.getElementName();
+ Set<ResourceFile> resourceFiles = findAttrsFiles(library, className);
+ if (resourceFiles != null && resourceFiles.size() > 0) {
+ String appUri = getAppResUri(project);
+ for (ResourceFile file : resourceFiles) {
+ AttrsXmlParser attrsXmlParser = getParser(file);
+ String fqcn = type.getFullyQualifiedName();
+
+ // Attributes
+ ViewClassInfo classInfo = new ViewClassInfo(true, fqcn, className);
+ attrsXmlParser.loadViewAttributes(classInfo);
+ appendAttributes(customAttributes, classInfo.getAttributes(), appUri);
+
+ // Layout params
+ LayoutParamsInfo layoutInfo = new ViewClassInfo.LayoutParamsInfo(
+ classInfo, "Layout", null /*superClassInfo*/); //$NON-NLS-1$
+ attrsXmlParser.loadLayoutParamsAttributes(layoutInfo);
+ appendAttributes(customLayoutAttributes, layoutInfo.getAttributes(), appUri);
+ }
+ }
+ }
+
+ /**
+ * Finds the set of XML files (if any) in the given library declaring
+ * attributes for the given class name
+ */
+ @Nullable
+ private Set<ResourceFile> findAttrsFiles(IProject library, String className) {
+ Set<ResourceFile> resourceFiles = null;
+ ResourceManager manager = ResourceManager.getInstance();
+ ProjectResources resources = manager.getProjectResources(library);
+ if (resources != null) {
+ Collection<ResourceItem> items =
+ resources.getResourceItemsOfType(ResourceType.DECLARE_STYLEABLE);
+ for (ResourceItem item : items) {
+ String viewName = item.getName();
+ if (viewName.equals(className)
+ || (viewName.startsWith(className)
+ && viewName.equals(className + "_Layout"))) { //$NON-NLS-1$
+ if (resourceFiles == null) {
+ resourceFiles = new HashSet<ResourceFile>();
+ }
+ resourceFiles.addAll(item.getSourceFileList());
+ }
+ }
+ }
+ return resourceFiles;
+ }
+
+ /**
+ * Find the project containing this type declaration. We cannot use
+ * {@link IType#getJavaProject()} since that will return the including
+ * project and we're after the library project such that we can find the
+ * attrs.xml file in the same project.
+ */
+ @Nullable
+ private IProject getProjectDeclaringType(IType type) {
+ IClassFile classFile = type.getClassFile();
+ if (classFile != null) {
+ IPath path = classFile.getPath();
+ IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+ IResource resource;
+ if (path.isAbsolute()) {
+ resource = AdtUtils.fileToResource(path.toFile());
+ } else {
+ resource = workspace.findMember(path);
+ }
+ if (resource != null && resource.getProject() != null) {
+ return resource.getProject();
+ }
+ }
+
+ return null;
+ }
+
+ /** Returns the name space to use for application attributes */
+ private String getAppResUri(IProject project) {
+ String appResource;
+ ProjectState projectState = Sdk.getProjectState(project);
+ if (projectState != null && projectState.isLibrary()) {
+ appResource = AUTO_URI;
+ } else {
+ ManifestInfo manifestInfo = ManifestInfo.get(project);
+ appResource = URI_PREFIX + manifestInfo.getPackage();
+ }
+ return appResource;
+ }
+
+
+ /** Append the {@link AttributeInfo} objects converted {@link AttributeDescriptor}
+ * objects into the given attribute list.
+ * <p>
+ * This is nearly identical to
+ * {@link DescriptorsUtils#appendAttribute(List, String, String, AttributeInfo, boolean, Map)}
+ * but it handles namespace declarations in the attrs.xml file where the android:
+ * namespace is included in the names.
+ */
+ private static void appendAttributes(List<AttributeDescriptor> attributes,
+ AttributeInfo[] attributeInfos, String appResource) {
+ // Custom attributes
+ for (AttributeInfo info : attributeInfos) {
+ String nsUri;
+ if (info.getName().startsWith(ANDROID_NS_NAME_PREFIX)) {
+ info.setName(info.getName().substring(ANDROID_NS_NAME_PREFIX.length()));
+ nsUri = ANDROID_URI;
+ } else {
+ nsUri = appResource;
+ }
+
+ DescriptorsUtils.appendAttribute(attributes,
+ null /*elementXmlName*/, nsUri, info, false /*required*/,
+ null /*overrides*/);
+ }
+ }
+
/**
* Computes (if needed) and returns the {@link ViewElementDescriptor} for the specified type.
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
index 2da7678..51328f3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
@@ -21,10 +21,13 @@
import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
import static org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML.ContentTypeID_XML;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.util.Pair;
+import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.IDocument;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
@@ -70,7 +73,8 @@
* @param node2 the second node to test
* @return the nearest common parent of the two given nodes
*/
- public static Node getCommonAncestor(Node node1, Node node2) {
+ @Nullable
+ public static Node getCommonAncestor(@NonNull Node node1, @NonNull Node node2) {
while (node2 != null) {
Node current = node1;
while (current != null && current != node2) {
@@ -92,13 +96,14 @@
* @param node the node to search from
* @return all elements in the subtree formed by the node parameter
*/
- public static List<Element> getAllElements(Node node) {
+ @NonNull
+ public static List<Element> getAllElements(@NonNull Node node) {
List<Element> elements = new ArrayList<Element>(64);
addElements(node, elements);
return elements;
}
- private static void addElements(Node node, List<Element> elements) {
+ private static void addElements(@NonNull Node node, @NonNull List<Element> elements) {
if (node instanceof Element) {
elements.add((Element) node);
}
@@ -116,7 +121,7 @@
* @param node the node to test
* @return the depth in the document
*/
- public static int getDepth(Node node) {
+ public static int getDepth(@NonNull Node node) {
int depth = -1;
while (node != null) {
depth++;
@@ -132,7 +137,7 @@
* @param node the node to test for element children
* @return true if the node has one or more element children
*/
- public static boolean hasElementChildren(Node node) {
+ public static boolean hasElementChildren(@NonNull Node node) {
NodeList children = node.getChildNodes();
for (int i = 0, n = children.getLength(); i < n; i++) {
if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
@@ -144,6 +149,41 @@
}
/**
+ * Returns the DOM document for the given file
+ *
+ * @param file the XML file
+ * @return the document, or null if not found or not parsed properly (no
+ * errors are generated/thrown)
+ */
+ @Nullable
+ public static Document getDocument(@NonNull IFile file) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ if (modelManager == null) {
+ return null;
+ }
+ try {
+ IStructuredModel model = modelManager.getExistingModelForRead(file);
+ if (model == null) {
+ model = modelManager.getModelForRead(file);
+ }
+ if (model != null) {
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ return domModel.getDocument();
+ }
+ try {
+ } finally {
+ model.releaseFromRead();
+ }
+ }
+ } catch (Exception e) {
+ // Ignore exceptions.
+ }
+
+ return null;
+ }
+
+ /**
* Returns the XML DOM node corresponding to the given offset of the given
* document.
*
@@ -151,7 +191,8 @@
* @param offset The offset to look up the node for
* @return The node containing the offset, or null
*/
- public static Node getNode(IDocument document, int offset) {
+ @Nullable
+ public static Node getNode(@NonNull IDocument document, int offset) {
Node node = null;
IModelManager modelManager = StructuredModelManager.getModelManager();
if (modelManager == null) {
@@ -204,7 +245,8 @@
* return the parent. Note that the method can also return null if no
* document or model could be obtained or if the offset is invalid.
*/
- public static Pair<Node, Node> getNodeContext(IDocument document, int offset) {
+ @Nullable
+ public static Pair<Node, Node> getNodeContext(@NonNull IDocument document, int offset) {
Node node = null;
IModelManager modelManager = StructuredModelManager.getModelManager();
if (modelManager == null) {
@@ -283,7 +325,8 @@
* @return the node which surrounds the given offset, or the node adjacent to the offset
* where the side depends on the forward parameter
*/
- public static Node getNode(IDocument document, int offset, boolean forward) {
+ @Nullable
+ public static Node getNode(@NonNull IDocument document, int offset, boolean forward) {
Node node = getNode(document, offset);
if (node instanceof IndexedRegion) {
@@ -318,8 +361,9 @@
* @param endOffset the ending offset of the range
* @return a pair of begin+end elements, or null
*/
- public static Pair<Element, Element> getElementRange(IDocument document, int beginOffset,
- int endOffset) {
+ @Nullable
+ public static Pair<Element, Element> getElementRange(@NonNull IDocument document,
+ int beginOffset, int endOffset) {
Element beginElement = null;
Element endElement = null;
Node beginNode = getNode(document, beginOffset, true);
@@ -383,7 +427,8 @@
* @param node the starting node
* @return the next sibling element, or null
*/
- public static Element getNextElement(Node node) {
+ @Nullable
+ public static Element getNextElement(@NonNull Node node) {
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
node = node.getNextSibling();
}
@@ -397,7 +442,8 @@
* @param node the starting node
* @return the previous sibling element, or null
*/
- public static Element getPreviousElement(Node node) {
+ @Nullable
+ public static Element getPreviousElement(@NonNull Node node) {
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
node = node.getPreviousSibling();
}
@@ -411,7 +457,8 @@
* @param node the starting node
* @return the closest parent element, or null
*/
- public static Element getParentElement(Node node) {
+ @Nullable
+ public static Element getParentElement(@NonNull Node node) {
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
node = node.getParentNode();
}
@@ -426,7 +473,8 @@
* @param attrValue the value to be escaped
* @return the escaped value
*/
- public static String toXmlAttributeValue(String attrValue) {
+ @NonNull
+ public static String toXmlAttributeValue(@NonNull String attrValue) {
for (int i = 0, n = attrValue.length(); i < n; i++) {
char c = attrValue.charAt(i);
if (c == '"' || c == '\'' || c == '<' || c == '&') {
@@ -446,7 +494,8 @@
* @param sb the string builder
* @param attrValue the attribute value to be appended and escaped
*/
- public static void appendXmlAttributeValue(StringBuilder sb, String attrValue) {
+ public static void appendXmlAttributeValue(@NonNull StringBuilder sb,
+ @NonNull String attrValue) {
int n = attrValue.length();
// &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue
// (' legal in a " string and " is legal in a ' string but here we'll stay on the safe
@@ -474,7 +523,7 @@
* @param sb the string builder
* @param textValue the text value to be appended and escaped
*/
- public static void appendXmlTextValue(StringBuilder sb, String textValue) {
+ public static void appendXmlTextValue(@NonNull StringBuilder sb, @NonNull String textValue) {
for (int i = 0, n = textValue.length(); i < n; i++) {
char c = textValue.charAt(i);
if (c == '<') {
@@ -488,7 +537,7 @@
}
/** Utility used by {@link #getFreeWidgetId(Element)} */
- private static void addLowercaseIds(Element root, Set<String> seen) {
+ private static void addLowercaseIds(@NonNull Element root, @NonNull Set<String> seen) {
if (root.hasAttributeNS(ANDROID_URI, ATTR_ID)) {
String id = root.getAttributeNS(ANDROID_URI, ATTR_ID);
if (id.startsWith(NEW_ID_PREFIX)) {
@@ -514,7 +563,10 @@
* @return a unique id, never null, which does not include the {@code @id/} prefix
* @see DescriptorsUtils#getFreeWidgetId
*/
- public static String getFreeWidgetId(Element element, Set<String> reserved, String prefix) {
+ public static String getFreeWidgetId(
+ @NonNull Element element,
+ @Nullable Set<String> reserved,
+ @Nullable String prefix) {
Set<String> ids = new HashSet<String>();
if (reserved != null) {
for (String id : reserved) {
@@ -545,7 +597,8 @@
* @param element the parent element
* @return a list of child elements, possibly empty but never null
*/
- public static List<Element> getChildren(Element element) {
+ @NonNull
+ public static List<Element> getChildren(@NonNull Element element) {
// Convenience to avoid lots of ugly DOM access casting
NodeList children = element.getChildNodes();
// An iterator would have been more natural (to directly drive the child list
@@ -568,7 +621,7 @@
* @param elements the elements to be tested
* @return true if the elements are contiguous siblings with no gaps
*/
- public static boolean isContiguous(List<Element> elements) {
+ public static boolean isContiguous(@NonNull List<Element> elements) {
if (elements.size() > 1) {
// All elements must be siblings (e.g. same parent)
Node parent = elements.get(0).getParentNode();
@@ -621,7 +674,7 @@
* @param element2 the second element to compare
* @return true if the two element hierarchies are logically equal
*/
- public static boolean isEquivalent(Element element1, Element element2) {
+ public static boolean isEquivalent(@Nullable Element element1, @Nullable Element element2) {
if (element1 == null || element2 == null) {
return false;
}
@@ -713,7 +766,8 @@
* @param document the document to search for an equivalent element in
* @return an equivalent element, or null
*/
- public static Element findCorresponding(Element element, Document document) {
+ @Nullable
+ public static Element findCorresponding(@NonNull Element element, @NonNull Document document) {
// Make sure the method is called correctly -- the element is for a different
// document than the one we are searching
assert element.getOwnerDocument() != document;
@@ -736,7 +790,8 @@
}
/** Helper method for {@link #findCorresponding(Element, Document)} */
- private static Element findCorresponding(Element element, String targetId) {
+ @Nullable
+ private static Element findCorresponding(@NonNull Element element, @NonNull String targetId) {
String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
if (id != null) { // Work around DOM bug
if (id.equals(targetId)) {
@@ -772,7 +827,8 @@
* @param xml the XML content to be parsed (must be well formed)
* @return the DOM document, or null
*/
- public static Document parseStructuredDocument(String xml) {
+ @Nullable
+ public static Document parseStructuredDocument(@NonNull String xml) {
IStructuredModel model = createStructuredModel(xml);
if (model instanceof IDOMModel) {
IDOMModel domModel = (IDOMModel) model;
@@ -788,7 +844,8 @@
* @param xml the XML content to be parsed (must be well formed)
* @return the structured model
*/
- public static IStructuredModel createStructuredModel(String xml) {
+ @Nullable
+ public static IStructuredModel createStructuredModel(@NonNull String xml) {
IStructuredModel model = createEmptyModel();
IStructuredDocument document = model.getStructuredDocument();
model.aboutToChangeModel();
@@ -803,6 +860,7 @@
*
* @return a new Eclipse XML model
*/
+ @NonNull
public static IStructuredModel createEmptyModel() {
IModelManager modelManager = StructuredModelManager.getModelManager();
return modelManager.createUnManagedStructuredModelFor(ContentTypeID_XML);
@@ -813,6 +871,7 @@
*
* @return an empty Eclipse XML document
*/
+ @Nullable
public static Document createEmptyDocument() {
IStructuredModel model = createEmptyModel();
if (model instanceof IDOMModel) {
@@ -832,7 +891,8 @@
* silently return null
* @return the DOM document, or null
*/
- public static Document parseDocument(String xml, boolean logParserErrors) {
+ @Nullable
+ public static Document parseDocument(@NonNull String xml, boolean logParserErrors) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
InputSource is = new InputSource(new StringReader(xml));
factory.setNamespaceAware(true);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 06cce40..354ef9a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -53,9 +53,11 @@
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
@@ -73,6 +75,7 @@
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
@@ -160,6 +163,7 @@
import org.eclipse.wb.core.controls.flyout.IFlyoutListener;
import org.eclipse.wb.core.controls.flyout.PluginFlyoutPreferences;
import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite;
+import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.io.File;
@@ -350,8 +354,6 @@
}
}
- mConfigComposite = new ConfigurationComposite(mConfigListener, parent,
- SWT.BORDER, initialState);
PluginFlyoutPreferences preferences =
new PluginFlyoutPreferences(AdtPlugin.getDefault().getPreferenceStore(),
"design.palette"); //$NON-NLS-1$
@@ -389,8 +391,13 @@
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
layoutBarAndCanvas.setLayout(gridLayout);
+
+ mConfigComposite = new ConfigurationComposite(mConfigListener, layoutBarAndCanvas,
+ SWT.NONE /*SWT.BORDER*/, initialState);
+ mConfigComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
mActionBar = new LayoutActionBar(layoutBarAndCanvas, SWT.NONE, this);
- GridData detailsData = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
+ GridData detailsData = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
mActionBar.setLayoutData(detailsData);
if (file != null) {
mActionBar.updateErrorIndicator(EclipseLintClient.hasMarkers(file));
@@ -695,7 +702,15 @@
// display the error.
FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
displayError(
- "No resources match the configuration\n \n\t%1$s\n \nChange the configuration or create:\n \n\tres/%2$s/%3$s\n \nYou can also click the 'Create' button above.",
+ "No resources match the configuration\n" +
+ " \n" +
+ "\t%1$s\n" +
+ " \n" +
+ "Change the configuration or create:\n" +
+ " \n" +
+ "\tres/%2$s/%3$s\n" +
+ " \n" +
+ "You can also click the 'Create New...' item in the configuration dropdown menu above.",
currentConfig.toDisplayString(),
currentConfig.getFolderName(ResourceFolderType.LAYOUT),
mEditedFile.getName());
@@ -729,6 +744,21 @@
}
@Override
+ public void onSetActivity(String activity) {
+ ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject());
+ String pkg = manifest.getPackage();
+ if (activity.startsWith(pkg) && activity.length() > pkg.length()
+ && activity.charAt(pkg.length()) == '.') {
+ activity = activity.substring(pkg.length());
+ }
+ CommonXmlEditor editor = getEditorDelegate().getEditor();
+ Element element = editor.getUiRootNode().getXmlDocument().getDocumentElement();
+ AdtUtils.setToolsAttribute(editor,
+ element, "Choose Activity", ConfigurationComposite.ATTR_CONTEXT,
+ activity, false /*reveal*/, false /*append*/);
+ }
+
+ @Override
public void onRenderingTargetPreChange(IAndroidTarget oldTarget) {
preRenderingTargetChangeCleanUp(oldTarget);
}
@@ -912,6 +942,12 @@
// pass
}
});
+
+ // Switch to the new file as well
+ IFile file = AdtUtils.fileToIFile(newLayoutFile);
+ if (file != null) {
+ AdtPlugin.openFile(file, null, false);
+ }
} catch (IOException e2) {
String message = String.format(
"Failed to create File 'res/%1$s/%2$s' : %3$s",
@@ -1588,7 +1624,7 @@
*/
public ResourceResolver getResourceResolver() {
if (mResourceResolver == null) {
- String theme = mConfigComposite.getTheme();
+ String theme = mConfigComposite.getThemeName();
if (theme == null) {
displayError("Missing theme.");
return null;
@@ -1643,12 +1679,7 @@
* @return the resource name of this layout, NOT including the @layout/ prefix
*/
public String getLayoutResourceName() {
- String name = mEditedFile.getName();
- int dotIndex = name.indexOf('.');
- if (dotIndex != -1) {
- name = name.substring(0, dotIndex);
- }
- return name;
+ return ResourceHelper.getLayoutName(mEditedFile);
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
index 4704791..a5df4bd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
@@ -126,9 +126,17 @@
assert awtImage instanceof SwtReadyBufferedImage;
if (isAlphaChannelImage) {
+ if (mImage != null) {
+ mImage.dispose();
+ }
+
mImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), awtImage, true, -1);
} else {
+ Image prev = mImage;
mImage = ((SwtReadyBufferedImage)awtImage).getSwtImage();
+ if (prev != mImage && prev != null) {
+ prev.dispose();
+ }
}
}
@@ -195,6 +203,9 @@
scaledAwtImage = ImageUtils.scale(mAwtImage, xScale, yScale);
}
assert scaledAwtImage.getWidth() == hi.getScalledImgSize();
+ if (mPreScaledImage != null && !mPreScaledImage.isDisposed()) {
+ mPreScaledImage.dispose();
+ }
mPreScaledImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), scaledAwtImage,
true /*transferAlpha*/, -1);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
index e912f53..30a2e78 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
@@ -22,6 +22,8 @@
import static com.android.ide.eclipse.adt.AdtConstants.DOT_PNG;
import static com.android.ide.eclipse.adt.AdtUtils.endsWithIgnoreCase;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.Rect;
import org.eclipse.swt.graphics.RGB;
@@ -124,7 +126,10 @@
* @return a cropped version of the source image, or null if the whole image was blank
* and cropping completely removed everything
*/
- public static BufferedImage cropBlank(BufferedImage image, Rect initialCrop) {
+ @Nullable
+ public static BufferedImage cropBlank(
+ @NonNull BufferedImage image,
+ @Nullable Rect initialCrop) {
return cropBlank(image, initialCrop, image.getType());
}
@@ -167,8 +172,11 @@
* @return a cropped version of the source image, or null if the whole image was blank
* and cropping completely removed everything
*/
- public static BufferedImage cropColor(BufferedImage image,
- final int blankArgb, Rect initialCrop) {
+ @Nullable
+ public static BufferedImage cropColor(
+ @NonNull BufferedImage image,
+ final int blankArgb,
+ @Nullable Rect initialCrop) {
return cropColor(image, blankArgb, initialCrop, image.getType());
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 8f950b9..9898a89 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -18,6 +18,7 @@
import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
+import com.android.annotations.NonNull;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.RuleAction;
import com.android.ide.common.api.RuleAction.Choices;
@@ -31,6 +32,7 @@
import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
+import com.google.common.base.Strings;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.window.Window;
@@ -39,6 +41,7 @@
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
@@ -72,6 +75,7 @@
private ToolItem mZoomInButton;
private ToolItem mZoomFitButton;
private ToolItem mLintButton;
+ private List<RuleAction> mPrevActions;
/**
* Creates a new {@link LayoutActionBar} and adds it to the given parent.
@@ -87,10 +91,10 @@
GridLayout layout = new GridLayout(3, false);
setLayout(layout);
- mLayoutToolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
- mLayoutToolBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, true, false));
+ mLayoutToolBar = new ToolBar(this, /*SWT.WRAP |*/ SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
+ mLayoutToolBar.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
mZoomToolBar = createZoomControls();
- mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false));
+ mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false));
mLintToolBar = createLintControls();
GridData lintData = new GridData(SWT.END, SWT.BEGINNING, false, false);
@@ -98,14 +102,14 @@
mLintToolBar.setLayoutData(lintData);
}
+ @Override
+ public void dispose() {
+ super.dispose();
+ mPrevActions = null;
+ }
+
/** Updates the layout contents based on the current selection */
void updateSelection() {
- // Get rid of any previous children
- for (ToolItem c : mLayoutToolBar.getItems()) {
- c.dispose();
- }
- mLayoutToolBar.pack();
-
NodeProxy parent = null;
LayoutCanvas canvas = mEditor.getCanvasControl();
SelectionManager selectionManager = canvas.getSelectionManager();
@@ -167,12 +171,94 @@
}
}
- addActions(actions, index, label);
+ if (!updateActions(actions)) {
+ updateToolbar(actions, index, label);
+ }
+ mPrevActions = actions;
+ }
+ /** Update the toolbar widgets */
+ private void updateToolbar(final List<RuleAction> actions, final int labelIndex,
+ final String label) {
+ if (mLayoutToolBar == null || mLayoutToolBar.isDisposed()) {
+ return;
+ }
+ for (ToolItem c : mLayoutToolBar.getItems()) {
+ c.dispose();
+ }
+ mLayoutToolBar.pack();
+ addActions(actions, labelIndex, label);
mLayoutToolBar.pack();
mLayoutToolBar.layout();
}
+ /**
+ * Attempts to update the existing toolbar actions, if the action list is
+ * similar to the current list. Returns false if this cannot be done and the
+ * contents must be replaced.
+ */
+ private boolean updateActions(@NonNull List<RuleAction> actions) {
+ List<RuleAction> before = mPrevActions;
+ List<RuleAction> after = actions;
+
+ if (before == null) {
+ return false;
+ }
+
+ if (!before.equals(after) || after.size() > mLayoutToolBar.getItemCount()) {
+ return false;
+ }
+
+ int actionIndex = 0;
+ for (int i = 0, max = mLayoutToolBar.getItemCount(); i < max; i++) {
+ ToolItem item = mLayoutToolBar.getItem(i);
+ int style = item.getStyle();
+ Object data = item.getData();
+ if (data != null) {
+ // One action can result in multiple toolbar items (e.g. a choice action
+ // can result in multiple radio buttons), so we've have to replace all of
+ // them with the corresponding new action
+ RuleAction prevAction = before.get(actionIndex);
+ while (prevAction != data) {
+ actionIndex++;
+ if (actionIndex == before.size()) {
+ return false;
+ }
+ prevAction = before.get(actionIndex);
+ if (prevAction == data) {
+ break;
+ } else if (!(prevAction instanceof RuleAction.Separator)) {
+ return false;
+ }
+ }
+ RuleAction newAction = after.get(actionIndex);
+ assert newAction.equals(prevAction); // Maybe I can do this lazily instead?
+
+ // Update action binding to the new action
+ item.setData(newAction);
+
+ // Sync button states: the checked state is not considered part of
+ // RuleAction equality
+ if ((style & SWT.CHECK) != 0) {
+ assert newAction instanceof Toggle;
+ Toggle toggle = (Toggle) newAction;
+ item.setSelection(toggle.isChecked());
+ } else if ((style & SWT.RADIO) != 0) {
+ assert newAction instanceof Choices;
+ Choices choices = (Choices) newAction;
+ String current = choices.getCurrent();
+ String id = (String) item.getData(ATTR_ID);
+ boolean selected = Strings.nullToEmpty(current).equals(id);
+ item.setSelection(selected);
+ }
+ } else {
+ assert (style & SWT.SEPARATOR) != 0;
+ }
+ }
+
+ return true;
+ }
+
private void addActions(List<RuleAction> actions, int labelIndex, String label) {
if (actions.size() > 0) {
// Flag used to indicate that if there are any actions -after- this, it
@@ -226,7 +312,7 @@
}
}
- private void addToggle(final Toggle toggle) {
+ private void addToggle(Toggle toggle) {
final ToolItem button = new ToolItem(mLayoutToolBar, SWT.CHECK);
URL iconUrl = toggle.getIconUrl();
@@ -237,10 +323,12 @@
} else {
button.setText(title);
}
+ button.setData(toggle);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
+ Toggle toggle = (Toggle) button.getData();
toggle.getCallback().action(toggle, getSelectedNodes(),
toggle.getId(), button.getSelection());
updateSelection();
@@ -263,7 +351,7 @@
}
- private void addPlainAction(final RuleAction menuAction) {
+ private void addPlainAction(RuleAction menuAction) {
final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH);
URL iconUrl = menuAction.getIconUrl();
@@ -274,10 +362,12 @@
} else {
button.setText(title);
}
+ button.setData(menuAction);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
+ RuleAction menuAction = (RuleAction) button.getData();
menuAction.getCallback().action(menuAction, getSelectedNodes(), menuAction.getId(),
false);
updateSelection();
@@ -285,7 +375,7 @@
});
}
- private void addRadio(final RuleAction.Choices choices) {
+ private void addRadio(RuleAction.Choices choices) {
List<URL> icons = choices.getIconUrls();
List<String> titles = choices.getTitles();
List<String> ids = choices.getIds();
@@ -301,10 +391,13 @@
final ToolItem item = new ToolItem(mLayoutToolBar, SWT.RADIO);
item.setToolTipText(title);
item.setImage(IconFactory.getInstance().getIcon(iconUrl));
+ item.setData(choices);
+ item.setData(ATTR_ID, id);
item.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (item.getSelection()) {
+ RuleAction.Choices choices = (Choices) item.getData();
choices.getCallback().action(choices, getSelectedNodes(), id, null);
updateSelection();
}
@@ -317,7 +410,7 @@
}
}
- private void addDropdown(final RuleAction.Choices choices) {
+ private void addDropdown(RuleAction.Choices choices) {
final ToolItem combo = new ToolItem(mLayoutToolBar, SWT.DROP_DOWN);
URL iconUrl = choices.getIconUrl();
if (iconUrl != null) {
@@ -326,16 +419,13 @@
} else {
combo.setText(choices.getTitle());
}
+ combo.setData(choices);
Listener menuListener = new Listener() {
@Override
public void handleEvent(Event event) {
- // if (event.detail == SWT.ARROW) {
- Point point = new Point(event.x, event.y);
- point = combo.getDisplay().map(mLayoutToolBar, null, point);
-
Menu menu = new Menu(mLayoutToolBar.getShell(), SWT.POP_UP);
-
+ RuleAction.Choices choices = (Choices) combo.getData();
List<URL> icons = choices.getIconUrls();
List<String> titles = choices.getTitles();
List<String> ids = choices.getIds();
@@ -360,15 +450,17 @@
item.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
+ RuleAction.Choices choices = (Choices) combo.getData();
choices.getCallback().action(choices, getSelectedNodes(), id, null);
updateSelection();
}
});
}
- // TODO - how do I dispose of this?
-
- menu.setLocation(point);
+ Rectangle bounds = combo.getBounds();
+ Point location = new Point(bounds.x, bounds.y + bounds.height);
+ location = combo.getParent().toDisplay(location);
+ menu.setLocation(location.x, location.y);
menu.setVisible(true);
}
};
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 1354ebe..7040584 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -1392,7 +1392,7 @@
if (ViewMetadataRepository.INSETS_SUPPORTED) {
ConfigurationComposite configComposite =
mEditorDelegate.getGraphicalEditor().getConfigurationComposite();
- String theme = configComposite.getTheme();
+ String theme = configComposite.getThemeName();
Density density = configComposite.getDensity();
return ViewMetadataRepository.getInsets(fqcn, density, theme);
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java
index bef4fba..e349a1c 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java
@@ -114,6 +114,9 @@
*/
@Override
public void setSelection(ISelection selection, boolean reveal) {
+ if (mEditorDelegate.getEditor().getIgnoreXmlUpdate()) {
+ return;
+ }
mCanvas.getSelectionManager().setSelection(selection);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
index f043205..8af4f2a 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
@@ -375,7 +375,7 @@
*/
public void reloadPalette(IAndroidTarget target) {
ConfigurationComposite configuration = mEditor.getConfigurationComposite();
- String theme = configuration.getTheme();
+ String theme = configuration.getThemeName();
String device = configuration.getDevice();
AndroidTargetData targetData =
target != null ? Sdk.getCurrent().getTargetData(target) : null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java
index f031028..0923dda 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java
@@ -14,10 +14,10 @@
/**
* Action which creates a submenu that is dynamically populated by subclasses
*/
-abstract class SubmenuAction extends Action implements MenuListener, IMenuCreator {
+public abstract class SubmenuAction extends Action implements MenuListener, IMenuCreator {
private Menu mMenu;
- SubmenuAction(String title) {
+ public SubmenuAction(String title) {
super(title, IAction.AS_DROP_DOWN_MENU);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
index 5bd35a1..435529a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
@@ -60,6 +60,7 @@
AttributeDescriptor[] direct_attrs = super.getAttributeDescriptors();
mCachedAttributeDescriptors = direct_attrs;
+ // Compute layout attributes: These depend on the *parent* this widget is within
AttributeDescriptor[] layout_attrs = null;
boolean need_xmlns = false;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
index f749e2b..c08a67e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
@@ -51,6 +51,7 @@
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
@@ -59,12 +60,14 @@
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
+import org.eclipse.jdt.internal.core.BinaryType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;
import org.eclipse.ui.texteditor.IDocumentProvider;
@@ -74,10 +77,11 @@
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -159,6 +163,9 @@
* with respect to the manifest file
*/
private void sync() {
+ // TODO: Add a last synced timestamp so that I can avoid doing all this with rapid
+ // burst calls on separate methods which all call sync() first!
+
if (mManifestFile == null) {
IFolderWrapper projectFolder = new IFolderWrapper(mProject);
mManifestFile = AndroidManifest.getManifest(projectFolder);
@@ -297,6 +304,17 @@
}
/**
+ * Returns the manifest theme registered on the application, if any
+ *
+ * @return a manifest theme, or null if none was registered
+ */
+ @Nullable
+ public String getmManifestTheme() {
+ sync();
+ return mManifestTheme;
+ }
+
+ /**
* Returns the default theme for this project, by looking at the manifest default
* theme registration, target SDK, rendering target, etc.
*
@@ -404,7 +422,27 @@
*/
@Nullable
public static String guessActivity(IProject project, String layoutName, String pkg) {
- final AtomicReference<String> activity = new AtomicReference<String>();
+ List<String> activities = guessActivities(project, layoutName, pkg);
+ if (activities.size() > 0) {
+ return activities.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the activities associated with the given layout file. Makes an educated guess
+ * by peeking at the usages of the R.layout.name field corresponding to the layout and
+ * if it finds a usage.
+ *
+ * @param project the project containing the layout
+ * @param layoutName the layout whose activity we want to look up
+ * @param pkg the package containing activities
+ * @return the activity name
+ */
+ @NonNull
+ public static List<String> guessActivities(IProject project, String layoutName, String pkg) {
+ final LinkedList<String> activities = new LinkedList<String>();
SearchRequestor requestor = new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
@@ -413,11 +451,13 @@
IMethod method = (IMethod) element;
IType declaringType = method.getDeclaringType();
String fqcn = declaringType.getFullyQualifiedName();
- if (activity.get() == null
- || (declaringType.getSuperclassName() != null &&
- declaringType.getSuperclassName().endsWith("Activity")) //$NON-NLS-1$
- || method.getElementName().equals("onCreate")) { //$NON-NLS-1$
- activity.set(fqcn);
+
+ if ((declaringType.getSuperclassName() != null &&
+ declaringType.getSuperclassName().endsWith("Activity")) //$NON-NLS-1$
+ || method.getElementName().equals("onCreate")) { //$NON-NLS-1$
+ activities.addFirst(fqcn);
+ } else {
+ activities.addLast(fqcn);
}
}
}
@@ -441,17 +481,58 @@
IField field = type.getField(layoutName);
if (field.exists()) {
SearchPattern pattern = SearchPattern.createPattern(field, REFERENCES);
- search(requestor, javaProject, pattern);
+ try {
+ search(requestor, javaProject, pattern);
+ } catch (OperationCanceledException canceled) {
+ // pass
+ }
}
}
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
- return activity.get();
+ return activities;
}
/**
+ * Returns all activities found in the given project (including those in libraries,
+ * except for android.jar itself)
+ *
+ * @param project the project
+ * @return a list of activity classes as fully qualified class names
+ */
+ @SuppressWarnings("restriction") // BinaryType
+ @NonNull
+ public static List<String> getProjectActivities(IProject project) {
+ final List<String> activities = new ArrayList<String>();
+ try {
+ final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ if (javaProject != null) {
+ IType[] activityTypes = new IType[0];
+ IType activityType = javaProject.findType(SdkConstants.CLASS_ACTIVITY);
+ if (activityType != null) {
+ ITypeHierarchy hierarchy =
+ activityType.newTypeHierarchy(javaProject, new NullProgressMonitor());
+ activityTypes = hierarchy.getAllSubtypes(activityType);
+ for (IType type : activityTypes) {
+ if (type instanceof BinaryType && (type.getClassFile() == null
+ || type.getClassFile().getResource() == null)) {
+ continue;
+ }
+ activities.add(type.getFullyQualifiedName());
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return activities;
+ }
+
+
+ /**
* Returns the activity associated with the given layout file.
* <p>
* This is an alternative to {@link #guessActivity(IFile, String)}. Whereas
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
index d4bb476..16519fe 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.editors.manifest.pages;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
import com.android.ide.eclipse.adt.internal.editors.ui.UiElementPart;
@@ -219,7 +220,7 @@
}
mUndoXmlParent.insertBefore(mUndoXmlNode, next);
if (next == null) {
- Text sep = mUndoXmlDocument.createTextNode("\n"); //$NON-NLS-1$
+ Text sep = mUndoXmlDocument.createTextNode(AdtUtils.getLineSeparator());
mUndoXmlParent.insertBefore(sep, null); // insert separator before end tag
}
success = true;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
index 219754b..42a8679 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
@@ -17,6 +17,8 @@
package com.android.ide.eclipse.adt.internal.editors.uimodel;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_PKG_PREFIX;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_SUPPORT_PKG_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
@@ -30,6 +32,7 @@
import com.android.ide.common.api.IAttributeInfo.Format;
import com.android.ide.common.resources.platform.AttributeInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
@@ -48,10 +51,11 @@
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.sdklib.SdkConstants;
-import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.xml.core.internal.document.ElementImpl;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
@@ -1033,7 +1037,14 @@
}
// Insert indent text node before the new element
- Text indentNode = doc.createTextNode("\n" + indent); //$NON-NLS-1$
+ IStructuredDocument document = editor.getStructuredDocument();
+ String newLine;
+ if (document != null) {
+ newLine = TextUtilities.getDefaultLineDelimiter(document);
+ } else {
+ newLine = AdtUtils.getLineSeparator();
+ }
+ Text indentNode = doc.createTextNode(newLine + indent);
parentXmlNode.insertBefore(indentNode, xmlNextSibling);
// Insert the element itself
@@ -1043,7 +1054,7 @@
// a tag into an area where there was no whitespace before
// (e.g. a new child of <LinearLayout></LinearLayout>).
if (insertAfter != null) {
- Text sep = doc.createTextNode("\n" + insertAfter); //$NON-NLS-1$
+ Text sep = doc.createTextNode(newLine + insertAfter);
parentXmlNode.insertBefore(sep, xmlNextSibling);
}
@@ -1165,10 +1176,19 @@
if (xmlChild.getNodeType() == Node.ELEMENT_NODE) {
String elementName = xmlChild.getNodeName();
UiElementNode uiNode = null;
+ CustomViewDescriptorService service = CustomViewDescriptorService.getInstance();
if (mUiChildren.size() <= uiIndex) {
// A new node is being added at the end of the list
ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
false /* recursive */);
+ if (desc == null && elementName.indexOf('.') != -1 &&
+ (!elementName.startsWith(ANDROID_PKG_PREFIX)
+ || elementName.startsWith(ANDROID_SUPPORT_PKG_PREFIX))) {
+ AndroidXmlEditor editor = getEditor();
+ if (editor != null && editor.getProject() != null) {
+ desc = service.getDescriptor(editor.getProject(), elementName);
+ }
+ }
if (desc == null) {
// Unknown node. Create a temporary descriptor for it.
// We'll add unknown attributes to it later.
@@ -1227,11 +1247,12 @@
// Inserting new node
ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
false /* recursive */);
- if (desc == null && elementName.indexOf('.') != -1) {
- IProject project = getEditor().getProject();
- if (project != null) {
- desc = CustomViewDescriptorService.getInstance().getDescriptor(
- project, elementName);
+ if (desc == null && elementName.indexOf('.') != -1 &&
+ (!elementName.startsWith(ANDROID_PKG_PREFIX)
+ || elementName.startsWith(ANDROID_SUPPORT_PKG_PREFIX))) {
+ AndroidXmlEditor editor = getEditor();
+ if (editor != null && editor.getProject() != null) {
+ desc = service.getDescriptor(editor.getProject(), elementName);
}
}
if (desc == null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
index cd077ec..1d80c8c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
@@ -18,16 +18,14 @@
import static com.android.tools.lint.detector.api.LintConstants.ATTR_IGNORE;
import static com.android.tools.lint.detector.api.LintConstants.DOT_XML;
-import static com.android.tools.lint.detector.api.LintConstants.TOOLS_PREFIX;
-import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
@@ -36,9 +34,6 @@
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
-import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -90,60 +85,8 @@
@Override
public void apply(IDocument document) {
- mEditor.wrapUndoEditXmlModel("Suppress Lint Warning", new Runnable() {
- @Override
- public void run() {
- String prefix = UiElementNode.lookupNamespacePrefix(mElement,
- TOOLS_URI, null);
- if (prefix == null) {
- // Add in new prefix...
- prefix = UiElementNode.lookupNamespacePrefix(mElement,
- TOOLS_URI, TOOLS_PREFIX);
- // ...and ensure that the header is formatted such that
- // the XML namespace declaration is placed in the right
- // position and wrapping is applied etc.
- mEditor.scheduleNodeReformat(mEditor.getUiRootNode(),
- true /*attributesOnly*/);
- }
-
- String ignore = mElement.getAttributeNS(TOOLS_URI, ATTR_IGNORE);
- if (ignore.length() > 0) {
- ignore = ignore + ',' + mId;
- } else {
- ignore = mId;
- }
-
- // Use the non-namespace form of set attribute since we can't
- // reference the namespace until the model has been reloaded
- mElement.setAttribute(prefix + ':' + ATTR_IGNORE, ignore);
-
- UiElementNode rootUiNode = mEditor.getUiRootNode();
- if (rootUiNode != null) {
- final UiElementNode uiNode = rootUiNode.findXmlNode(mElement);
- if (uiNode != null) {
- mEditor.scheduleNodeReformat(uiNode, true /*attributesOnly*/);
-
- // Update editor selection after format
- Display display = AdtPlugin.getDisplay();
- if (display != null) {
- display.asyncExec(new Runnable() {
- @Override
- public void run() {
- Node xmlNode = uiNode.getXmlNode();
- Attr attribute = ((Element) xmlNode).getAttributeNodeNS(
- TOOLS_URI, ATTR_IGNORE);
- if (attribute instanceof IndexedRegion) {
- IndexedRegion region = (IndexedRegion) attribute;
- mEditor.getStructuredTextEditor().selectAndReveal(
- region.getStartOffset(), region.getLength());
- }
- }
- });
- }
- }
- }
- }
- });
+ AdtUtils.setToolsAttribute(mEditor, mElement, "Suppress Lint Warning", ATTR_IGNORE, mId,
+ true /*reveal*/, true /*append*/);
try {
// Remove the marker now that the suppress attribute has been added
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
index 9424bf0..68b3407 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
@@ -266,8 +266,12 @@
return null;
}
- /** Clears any lint markers from the given resource (project, folder or file) */
- static void clearMarkers(IResource resource) {
+ /**
+ * Clears any lint markers from the given resource (project, folder or file)
+ *
+ * @param resource the resource to remove markers from
+ */
+ public static void clearMarkers(@NonNull IResource resource) {
clearMarkers(Collections.singletonList(resource));
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
index 36a388a..25e80fc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
@@ -15,8 +15,12 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_CLASS;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
@@ -60,14 +64,19 @@
* true if fatal errors were found.
*
* @param resources the resources (project, folder or file) to be analyzed
+ * @param source if checking a single source file, the source file
* @param doc the associated document, if known, or null
* @param fatalOnly if true, only report fatal issues (severity=error)
* @return true if any fatal errors were encountered.
*/
- private static boolean runLint(List<? extends IResource> resources, IDocument doc,
+ private static boolean runLint(
+ @NonNull List<? extends IResource> resources,
+ @Nullable IResource source,
+ @Nullable IDocument doc,
boolean fatalOnly) {
resources = addLibraries(resources);
- CheckFileJob job = (CheckFileJob) startLint(resources, doc, fatalOnly, false /*show*/);
+ CheckFileJob job = (CheckFileJob) startLint(resources, source, doc, fatalOnly,
+ false /*show*/);
try {
job.join();
boolean fatal = job.isFatal();
@@ -85,23 +94,34 @@
}
/**
- * Runs lint and updates the markers. Does not wait for the job to
- * finish - just returns immediately.
+ * Runs lint and updates the markers. Does not wait for the job to finish -
+ * just returns immediately.
*
* @param resources the resources (project, folder or file) to be analyzed
+ * @param source if checking a single source file, the source file. When
+ * single checking an XML file, this is typically the same as the
+ * file passed in the list in the first parameter, but when
+ * checking the .class files of a Java file for example, the
+ * .class file and all the inner classes of the Java file are
+ * passed in the first parameter, and the corresponding .java
+ * source file is passed here.
* @param doc the associated document, if known, or null
* @param fatalOnly if true, only report fatal issues (severity=error)
* @param show if true, show the results in a {@link LintViewPart}
* @return the job running lint in the background.
*/
- public static Job startLint(List<? extends IResource> resources,
- IDocument doc, boolean fatalOnly, boolean show) {
+ public static Job startLint(
+ @NonNull List<? extends IResource> resources,
+ @Nullable IResource source,
+ @Nullable IDocument doc,
+ boolean fatalOnly,
+ boolean show) {
if (resources != null && !resources.isEmpty()) {
resources = addLibraries(resources);
cancelCurrentJobs(false);
- CheckFileJob job = new CheckFileJob(resources, doc, fatalOnly);
+ CheckFileJob job = new CheckFileJob(resources, source, doc, fatalOnly);
job.schedule();
if (show) {
@@ -125,8 +145,8 @@
*/
public static boolean runLintOnExport(Shell shell, IProject project) {
if (AdtPrefs.getPrefs().isLintOnExport()) {
- boolean fatal = EclipseLintRunner.runLint(Collections.singletonList(project), null,
- true /*fatalOnly*/);
+ boolean fatal = EclipseLintRunner.runLint(Collections.singletonList(project),
+ null, null, true /*fatalOnly*/);
if (fatal) {
MessageDialog.openWarning(shell,
"Export Aborted",
@@ -214,15 +234,20 @@
/** Job family */
private static final Object FAMILY_RUN_LINT = new Object();
private final List<? extends IResource> mResources;
+ private final IResource mSource;
private final IDocument mDocument;
private LintDriver mLint;
private boolean mFatal;
private boolean mFatalOnly;
- private CheckFileJob(List<? extends IResource> resources, IDocument doc,
+ private CheckFileJob(
+ @NonNull List<? extends IResource> resources,
+ @Nullable IResource source,
+ @Nullable IDocument doc,
boolean fatalOnly) {
super("Running Android Lint");
mResources = resources;
+ mSource = source;
mDocument = doc;
mFatalOnly = fatalOnly;
}
@@ -245,36 +270,64 @@
try {
monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
IssueRegistry registry = EclipseLintClient.getRegistry();
- EnumSet<Scope> scope = Scope.ALL;
+ EnumSet<Scope> scope = null;
List<File> files = new ArrayList<File>(mResources.size());
for (IResource resource : mResources) {
File file = AdtUtils.getAbsolutePath(resource).toFile();
files.add(file);
- if (resource instanceof IProject) {
+ if (resource instanceof IProject && mSource == null) {
scope = Scope.ALL;
- } else if (resource instanceof IFile
- && AdtUtils.endsWithIgnoreCase(resource.getName(), DOT_XML)) {
- if (resource.getName().equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
- scope = EnumSet.of(Scope.MANIFEST);
- } else {
- scope = Scope.RESOURCE_FILE_SCOPE;
- }
} else {
- return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
- "Only XML files are supported for single file lint", null); //$NON-NLS-1$
+ String name = resource.getName();
+ if (AdtUtils.endsWithIgnoreCase(name, DOT_XML)) {
+ if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
+ scope = EnumSet.of(Scope.MANIFEST);
+ } else {
+ scope = Scope.RESOURCE_FILE_SCOPE;
+ }
+ } else if (name.endsWith(DOT_JAVA) && resource instanceof IFile) {
+ if (scope != null) {
+ if (!scope.contains(Scope.JAVA_FILE)) {
+ scope = EnumSet.copyOf(scope);
+ scope.add(Scope.JAVA_FILE);
+ }
+ } else {
+ scope = Scope.JAVA_FILE_SCOPE;
+ }
+ } else if (name.endsWith(DOT_CLASS) && resource instanceof IFile) {
+ if (scope != null) {
+ if (!scope.contains(Scope.CLASS_FILE)) {
+ scope = EnumSet.copyOf(scope);
+ scope.add(Scope.CLASS_FILE);
+ }
+ } else {
+ scope = Scope.CLASS_FILE_SCOPE;
+ }
+ } else {
+ return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
+ "Only XML files are supported for single file lint", null); //$NON-NLS-1$
+ }
}
}
- if (Scope.checkSingleFile(scope)) {
+ if (scope == null) {
+ scope = Scope.ALL;
+ }
+ if (mSource == null) {
+ assert !Scope.checkSingleFile(scope) : scope + " with " + mResources;
+ }
+ // Check single file?
+ if (mSource != null) {
// Delete specific markers
- for (IResource resource : mResources) {
- IMarker[] markers = EclipseLintClient.getMarkers(resource);
- for (IMarker marker : markers) {
- String id = marker.getAttribute(MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$
- Issue issue = registry.getIssue(id);
- if (issue == null || issue.getScope().equals(scope)) {
- marker.delete();
- }
+ IMarker[] markers = EclipseLintClient.getMarkers(mSource);
+ for (IMarker marker : markers) {
+ String id = marker.getAttribute(MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$
+ Issue issue = registry.getIssue(id);
+ if (issue == null) {
+ continue;
+ }
+ if (issue.isAdequate(scope)) {
+ marker.delete();
}
}
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java
new file mode 100644
index 0000000..e5fe46e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.lint;
+
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_CLASS;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
+
+import com.android.annotations.NonNull;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.swt.widgets.Display;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Delta processor for Java files, which runs single-file lints if it finds that
+ * the currently active file has been updated.
+ */
+public class LintDeltaProcessor implements Runnable {
+ private List<IResource> mFiles;
+ private IFile mActiveFile;
+
+ private LintDeltaProcessor() {
+ }
+
+ /**
+ * Creates a new {@link LintDeltaProcessor}
+ *
+ * @return a visitor
+ */
+ @NonNull
+ public static LintDeltaProcessor create() {
+ return new LintDeltaProcessor();
+ }
+
+ /**
+ * Process the given delta: update lint on any Java source and class files found.
+ *
+ * @param delta the delta describing recently changed files
+ */
+ public void process(@NonNull IResourceDelta delta) {
+ // Get the active editor file, if any
+ Display display = AdtPlugin.getDisplay();
+ if (display == null) {
+ return;
+ }
+ if (display.getThread() != Thread.currentThread()) {
+ display.syncExec(this);
+ } else {
+ run();
+ }
+
+ if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) {
+ return;
+ }
+
+ mFiles = new ArrayList<IResource>();
+ gatherFiles(delta);
+
+ if (!mFiles.isEmpty()) {
+ EclipseLintRunner.startLint(mFiles, mActiveFile, null,
+ false /*fatalOnly*/, false /*show*/);
+ }
+ }
+
+ /**
+ * Collect .java and .class files to be run in lint. Only collects files
+ * that match the active editor.
+ */
+ private void gatherFiles(@NonNull IResourceDelta delta) {
+ IResource resource = delta.getResource();
+ String name = resource.getName();
+ if (name.endsWith(DOT_JAVA)) {
+ if (resource.equals(mActiveFile)) {
+ mFiles.add(resource);
+ }
+ } else if (name.endsWith(DOT_CLASS)) {
+ // Make sure this class corresponds to the .java file, meaning it has
+ // the same basename, or that it is an inner class of a class that
+ // matches the same basename. (We could potentially make sure the package
+ // names match too, but it's unlikely that the class names match without a
+ // package match, and there's no harm in including some extra classes here,
+ // since lint will resolve full paths and the resource markers won't go
+ // to the wrong place, we simply end up analyzing some extra files.)
+ String className = mActiveFile.getName();
+ if (name.regionMatches(0, className, 0, className.length() - DOT_JAVA.length())) {
+ if (name.length() == className.length() - DOT_JAVA.length() + DOT_CLASS.length()
+ || name.charAt(className.length() - DOT_JAVA.length()) == '$') {
+ mFiles.add(resource);
+ }
+ }
+ } else {
+ IResourceDelta[] children = delta.getAffectedChildren();
+ if (children != null && children.length > 0) {
+ for (IResourceDelta d : children) {
+ gatherFiles(d);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ // Get the active file: this must be run on the GUI thread
+ mActiveFile = AdtUtils.getActiveFile();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
index 478a07c..c81c0fc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -262,6 +262,8 @@
}
try {
+ // See if the current active file is the one containing this marker;
+ // if so we can take some shortcuts
IEditorPart activeEditor = AdtUtils.getActiveEditor();
IEditorPart part = null;
if (activeEditor != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
index c40f3b7..561c5fd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
@@ -48,7 +48,6 @@
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TreeNodeContentProvider;
-import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
@@ -64,6 +63,8 @@
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TreeEvent;
+import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
@@ -83,6 +84,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -103,6 +105,7 @@
private final IWorkbenchPartSite mSite;
private final TreeViewer mTreeViewer;
private final Tree mTree;
+ private Set<String> mExpandedIds;
private ContentProvider mContentProvider;
private String mSelectedId;
private List<? extends IResource> mResources;
@@ -179,6 +182,34 @@
}
}
});
+ mTree.addTreeListener(new TreeListener() {
+ @Override
+ public void treeExpanded(TreeEvent e) {
+ Object data = e.item.getData();
+ if (data instanceof IMarker) {
+ String id = EclipseLintClient.getId((IMarker) data);
+ if (id != null) {
+ if (mExpandedIds == null) {
+ mExpandedIds = new HashSet<String>();
+ }
+ mExpandedIds.add(id);
+ }
+ }
+ }
+
+ @Override
+ public void treeCollapsed(TreeEvent e) {
+ if (mExpandedIds != null) {
+ Object data = e.item.getData();
+ if (data instanceof IMarker) {
+ String id = EclipseLintClient.getId((IMarker) data);
+ if (id != null) {
+ mExpandedIds.remove(id);
+ }
+ }
+ }
+ }
+ });
}
private boolean treePainted;
@@ -479,9 +510,6 @@
return Status.CANCEL_STATUS;
}
- Object[] expandedElements = mTreeViewer.getExpandedElements();
- TreePath[] expandedTreePaths = mTreeViewer.getExpandedTreePaths();
-
mTreeViewer.setInput(null);
List<IMarker> markerList = getMarkers();
if (markerList.size() == 0) {
@@ -504,8 +532,21 @@
mTreeViewer.setInput(markerList);
mTreeViewer.refresh();
- mTreeViewer.setExpandedElements(expandedElements);
- mTreeViewer.setExpandedTreePaths(expandedTreePaths);
+ if (mExpandedIds != null) {
+ List<IMarker> expanded = new ArrayList<IMarker>(mExpandedIds.size());
+ IMarker[] topMarkers = mContentProvider.getTopMarkers();
+ if (topMarkers != null) {
+ for (IMarker marker : topMarkers) {
+ String id = EclipseLintClient.getId(marker);
+ if (id != null && mExpandedIds.contains(id)) {
+ expanded.add(marker);
+ }
+ }
+ }
+ if (!expanded.isEmpty()) {
+ mTreeViewer.setExpandedElements(expanded.toArray());
+ }
+ }
if (mSelectedId != null) {
IMarker[] topMarkers = mContentProvider.getTopMarkers();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
index e9f94cc..39c6d25 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
@@ -474,7 +474,7 @@
if (resources == null) {
return;
}
- Job job = EclipseLintRunner.startLint(resources, null,
+ Job job = EclipseLintRunner.startLint(resources, null, null,
false /*fatalOnly*/, false /*show*/);
if (job != null && workbench != null) {
job.addJobChangeListener(LintViewPart.this);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
index e77e910..18f3db3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
@@ -76,7 +76,7 @@
List<IProject> projects = getProjects(mSelection, true /* warn */);
if (!projects.isEmpty()) {
- EclipseLintRunner.startLint(projects, null, false /*fatalOnly*/, true /*show*/);
+ EclipseLintRunner.startLint(projects, null, null, false /*fatalOnly*/, true /*show*/);
} else {
MessageDialog.openWarning(AdtPlugin.getDisplay().getActiveShell(), "Lint",
"Could not run Lint: Select a project first.");
@@ -210,7 +210,7 @@
ISharedImages images = PlatformUI.getWorkbench().getSharedImages();
ImageDescriptor clear = images.getImageDescriptor(ISharedImages.IMG_ELCL_REMOVEALL);
- LintMenuAction clearAction = new LintMenuAction("Clear Lint Markers", clear, true, null);
+ LintMenuAction clearAction = new LintMenuAction("Clear Lint Warnings", clear, true, null);
addSeparator();
addAction(clearAction);
@@ -271,7 +271,8 @@
if (mClear) {
EclipseLintClient.clearMarkers(resources);
} else {
- EclipseLintRunner.startLint(resources, null, false /*fatalOnly*/, true /*show*/);
+ EclipseLintRunner.startLint(resources, null, null, false /*fatalOnly*/,
+ true /*show*/);
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
index bd2423e..827f8a4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
@@ -378,7 +378,7 @@
}
}
- EclipseLintRunner.startLint(androidProjects, null, false /*fatalOnly*/,
+ EclipseLintRunner.startLint(androidProjects, null, null, false /*fatalOnly*/,
true /*show*/);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
index ef47494..5d38bf1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
@@ -172,6 +172,10 @@
// check if the project has a valid target.
ProjectState state = Sdk.getProjectState(iProject);
+ if (state == null) {
+ // getProjectState should already have logged an error. Just bail out.
+ return null;
+ }
/*
* At this point we're going to gather a list of all that need to go in the
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
index 584d49f..212263a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
@@ -143,7 +143,9 @@
mPropertiesWorkingCopy.save();
IResource projectProp = mProject.findMember(SdkConstants.FN_PROJECT_PROPERTIES);
- projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
+ if (projectProp != null) {
+ projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
+ }
} catch (Exception e) {
String msg = String.format(
"Failed to save %1$s for project %2$s",
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
index a87f992..57c016e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
@@ -19,6 +19,7 @@
import static com.android.AndroidConstants.FD_RES_VALUES;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF;
import static com.android.ide.common.resources.ResourceResolver.PREFIX_STYLE;
import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
@@ -507,11 +508,30 @@
style = style.substring(PREFIX_STYLE.length());
} else if (style.startsWith(PREFIX_ANDROID_STYLE)) {
style = style.substring(PREFIX_ANDROID_STYLE.length());
+ } else if (style.startsWith(PREFIX_RESOURCE_REF)) {
+ // @package:style/foo
+ int index = style.indexOf('/');
+ if (index != -1) {
+ style = style.substring(index + 1);
+ }
}
return style;
}
/**
+ * Returns true if the given style represents a project theme
+ *
+ * @param style a theme style string
+ * @return true if the style string represents a project theme, as opposed
+ * to a framework theme
+ */
+ public static boolean isProjectStyle(String style) {
+ assert style.startsWith(PREFIX_STYLE) || style.startsWith(PREFIX_ANDROID_STYLE) : style;
+
+ return style.startsWith(PREFIX_STYLE);
+ }
+
+ /**
* Returns the layout resource name for the given layout file, e.g. for
* /res/layout/foo.xml returns foo.
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index 2a1fba9..4f9bb09 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -442,6 +442,10 @@
// delete the old file.
ProjectProperties.delete(projectLocation, PropertyType.LEGACY_DEFAULT);
+
+ // make sure to use the new properties
+ properties = ProjectProperties.load(projectLocation,
+ PropertyType.PROJECT);
} catch (Exception e) {
AdtPlugin.log(IStatus.ERROR,
"Failed to rename properties file to %1$s for project '%s2$'",
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitor.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitor.java
index f9c94a7..65ddbd4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitor.java
@@ -19,7 +19,7 @@
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmuilib.logcat.ILogCatMessageEventListener;
+import com.android.ddmuilib.logcat.ILogCatBufferChangeListener;
import com.android.ddmuilib.logcat.LogCatMessage;
import com.android.ddmuilib.logcat.LogCatReceiver;
import com.android.ddmuilib.logcat.LogCatReceiverFactory;
@@ -28,7 +28,9 @@
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
@@ -38,6 +40,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* LogCatMonitor helps in monitoring the logcat output from a set of devices.
@@ -45,15 +48,27 @@
* if any message is deemed important.
*/
public class LogCatMonitor {
- public static final String AUTO_MONITOR_PREFKEY = "ddms.logcat.automonitor";
+ public static final String AUTO_MONITOR_PREFKEY = "ddms.logcat.automonitor"; //$NON-NLS-1$
+ public static final String AUTO_MONITOR_LOGLEVEL = "ddms.logcat.auotmonitor.level"; //$NON-NLS-1$
+ private static final String AUTO_MONITOR_PROMPT_SHOWN = "ddms.logcat.automonitor.userprompt"; //$NON-NLS-1$
private IPreferenceStore mPrefStore;
private Map<String, DeviceData> mMonitoredDevices;
private IDebuggerConnector[] mConnectors;
+ private int mMinMessagePriority;
+
+ /**
+ * Flag that controls when the logcat stream is checked. This flag is set when the user
+ * performs a launch, and is reset as soon as the logcat view is displayed.
+ */
+ final AtomicBoolean mMonitorEnabled = new AtomicBoolean(false);
+
public LogCatMonitor(IDebuggerConnector[] debuggerConnectors, IPreferenceStore prefStore) {
mConnectors = debuggerConnectors;
mPrefStore = prefStore;
+ mMinMessagePriority =
+ LogLevel.getByString(mPrefStore.getString(AUTO_MONITOR_LOGLEVEL)).getPriority();
mMonitoredDevices = new HashMap<String, DeviceData>();
@@ -79,6 +94,9 @@
if (AUTO_MONITOR_PREFKEY.equals(event.getProperty())
&& event.getNewValue().equals(false)) {
unmonitorAllDevices();
+ } else if (AUTO_MONITOR_LOGLEVEL.equals(event.getProperty())) {
+ mMinMessagePriority =
+ LogLevel.getByString((String) event.getNewValue()).getPriority();
}
}
});
@@ -98,7 +116,7 @@
return;
}
- data.receiver.removeMessageReceivedEventListener(data.messageEventListener);
+ data.receiver.removeMessageReceivedEventListener(data.bufferChangeListener);
}
public void monitorDevice(final IDevice device) {
@@ -107,16 +125,19 @@
return;
}
+ mMonitorEnabled.set(true);
+
if (mMonitoredDevices.keySet().contains(device.getSerialNumber())) {
// the device is already monitored
return;
}
LogCatReceiver r = LogCatReceiverFactory.INSTANCE.newReceiver(device, mPrefStore);
- ILogCatMessageEventListener l = new ILogCatMessageEventListener() {
+ ILogCatBufferChangeListener l = new ILogCatBufferChangeListener() {
@Override
- public void messageReceived(List<LogCatMessage> receivedMessages) {
- checkMessages(receivedMessages, device);
+ public void bufferChanged(List<LogCatMessage> addedMessages,
+ List<LogCatMessage> deletedMessages) {
+ checkMessages(addedMessages, device);
}
};
r.addMessageReceivedEventListener(l);
@@ -125,12 +146,20 @@
}
private void checkMessages(List<LogCatMessage> receivedMessages, IDevice device) {
+ if (!mMonitorEnabled.get()) {
+ return;
+ }
+
// check the received list of messages to see if any of them are
// significant enough to be seen by the user. If so, activate the logcat view
// to display those messages
for (LogCatMessage m : receivedMessages) {
if (isImportantMessage(m)) {
focusLogCatView(device, m.getAppName());
+
+ // now that logcat view is active, no need to check messages until the next
+ // time user launches an application.
+ mMonitorEnabled.set(false);
break;
}
}
@@ -141,7 +170,7 @@
* it is of severity level error or higher, and it belongs to an app currently in the workspace.
*/
private boolean isImportantMessage(LogCatMessage m) {
- if (m.getLogLevel().getPriority() < LogLevel.ERROR.getPriority()) {
+ if (m.getLogLevel().getPriority() < mMinMessagePriority) {
return false;
}
@@ -169,6 +198,15 @@
return;
}
+ // if the logcat view is not visible, then prompt the user once to set
+ // logcat monitoring preferences
+ if (!isLogCatViewVisible(page)) {
+ boolean showLogCatView = promptUserOnce(page.getWorkbenchWindow().getShell());
+ if (!showLogCatView) {
+ return;
+ }
+ }
+
// display view
final LogCatView v = displayLogCatView(page);
if (v == null) {
@@ -182,6 +220,11 @@
v.selectTransientAppFilter(appName);
}
+ private boolean isLogCatViewVisible(IWorkbenchPage page) {
+ IViewPart view = page.findView(LogCatView.ID);
+ return view != null && page.isPartVisible(view);
+ }
+
private LogCatView displayLogCatView(IWorkbenchPage page) {
// if the view is already in the page, just bring it to the front
// without giving it focus.
@@ -200,16 +243,35 @@
return null;
}
}
+
+ private boolean promptUserOnce(Shell shell) {
+ // see if this prompt was already displayed
+ boolean promptShown = mPrefStore.getBoolean(AUTO_MONITOR_PROMPT_SHOWN);
+ if (promptShown) {
+ return mPrefStore.getBoolean(AUTO_MONITOR_PREFKEY);
+ }
+
+ LogCatMonitorDialog dlg = new LogCatMonitorDialog(shell);
+ int r = dlg.open();
+
+ // save preference indicating that this dialog has been displayed once
+ mPrefStore.setValue(AUTO_MONITOR_PROMPT_SHOWN, true);
+ mPrefStore.setValue(AUTO_MONITOR_PREFKEY, dlg.shouldMonitor());
+ mPrefStore.setValue(AUTO_MONITOR_LOGLEVEL, dlg.getMinimumPriority());
+
+ return r == Window.OK && dlg.shouldMonitor();
+ }
+
});
}
private static class DeviceData {
public final LogCatReceiver receiver;
- public final ILogCatMessageEventListener messageEventListener;
+ public final ILogCatBufferChangeListener bufferChangeListener;
- public DeviceData(LogCatReceiver r, ILogCatMessageEventListener l) {
+ public DeviceData(LogCatReceiver r, ILogCatBufferChangeListener l) {
receiver = r;
- messageEventListener = l;
+ bufferChangeListener = l;
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitorDialog.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitorDialog.java
new file mode 100644
index 0000000..6194a0d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitorDialog.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 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.ide.eclipse.ddms;
+
+import com.android.ddmlib.Log.LogLevel;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+
+public class LogCatMonitorDialog extends TitleAreaDialog {
+ private static final String TITLE = "Auto Monitor Logcat";
+ private static final String DEFAULT_MESSAGE =
+ "Would you like ADT to automatically monitor logcat \n" +
+ "output for messages from applications in the workspace?";
+
+ private boolean mShouldMonitor = true;
+
+ private static final String[] LOG_PRIORITIES = new String[] {
+ LogLevel.VERBOSE.getStringValue(),
+ LogLevel.DEBUG.getStringValue(),
+ LogLevel.INFO.getStringValue(),
+ LogLevel.WARN.getStringValue(),
+ LogLevel.ERROR.getStringValue(),
+ LogLevel.ASSERT.getStringValue(),
+ };
+ private static final int ERROR_PRIORITY_INDEX = 4;
+
+ private String mMinimumLogPriority = LOG_PRIORITIES[ERROR_PRIORITY_INDEX];
+
+ public LogCatMonitorDialog(Shell parentShell) {
+ super(parentShell);
+ setHelpAvailable(false);
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ setTitle(TITLE);
+ setMessage(DEFAULT_MESSAGE);
+
+ parent = (Composite) super.createDialogArea(parent);
+ Composite c = new Composite(parent, SWT.BORDER);
+ c.setLayout(new GridLayout(2, false));
+ GridData gd_c = new GridData(GridData.FILL_BOTH);
+ gd_c.grabExcessVerticalSpace = false;
+ gd_c.grabExcessHorizontalSpace = false;
+ c.setLayoutData(gd_c);
+
+ final Button disableButton = new Button(c, SWT.RADIO);
+ disableButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ disableButton.setText("No, do not monitor logcat output.");
+
+ final Button enableButton = new Button(c, SWT.RADIO);
+ enableButton.setText("Yes, monitor logcat and display logcat view if there are\n" +
+ "messages with priority higher than:");
+ enableButton.setSelection(true);
+
+ final Combo levelCombo = new Combo(c, SWT.READ_ONLY | SWT.DROP_DOWN);
+ levelCombo.setItems(LOG_PRIORITIES);
+ levelCombo.select(ERROR_PRIORITY_INDEX);
+
+ SelectionListener s = new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (e.getSource() == enableButton) {
+ mShouldMonitor = enableButton.getSelection();
+ levelCombo.setEnabled(mShouldMonitor);
+ } else if (e.getSource() == levelCombo) {
+ mMinimumLogPriority = LOG_PRIORITIES[levelCombo.getSelectionIndex()];
+ }
+ }
+ };
+
+ levelCombo.addSelectionListener(s);
+ enableButton.addSelectionListener(s);
+
+ return parent;
+ }
+
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ // Only need OK button
+ createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL,
+ true);
+ }
+
+ public boolean shouldMonitor() {
+ return mShouldMonitor;
+ }
+
+ public String getMinimumPriority() {
+ return mMinimumLogPriority;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/Messages.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/Messages.java
index 45602e6..576b598 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/Messages.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/Messages.java
@@ -80,6 +80,7 @@
public static String LogCatPreferencePage_Switch_Perspective;
public static String LogCatPreferencePage_Switch_To;
public static String LogCatPreferencePage_AutoMonitorLogcat;
+ public static String LogCatPreferencePage_SessionFilterLogLevel;
public static String LogCatView_Clear_Log;
public static String LogCatView_Copy;
public static String LogCatView_Create_Filter;
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/messages.properties b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/messages.properties
index 07c4f16..3698ebc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/messages.properties
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/messages.properties
@@ -73,7 +73,8 @@
LogCatPreferencePage_MaxMessages=Maximum number of logcat messages to buffer:
LogCatPreferencePage_Switch_Perspective=Switch Perspective
LogCatPreferencePage_Switch_To=Switch to:
-LogCatPreferencePage_AutoMonitorLogcat=Display logcat view when there are messages from an application in the workspace
+LogCatPreferencePage_AutoMonitorLogcat=Monitor logcat for messages from applications in workspace
+LogCatPreferencePage_SessionFilterLogLevel=Show logcat view if message priority is atleast:
LogCatView_Clear_Log=Clear Log
LogCatView_Copy=Copy
LogCatView_Create_Filter=Create Filter
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
index b315ee9..9971e18 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
@@ -16,6 +16,7 @@
package com.android.ide.eclipse.ddms.preferences;
+import com.android.ddmlib.Log.LogLevel;
import com.android.ddmuilib.logcat.LogCatMessageList;
import com.android.ddmuilib.logcat.LogCatPanel;
import com.android.ide.eclipse.base.InstallDetails;
@@ -43,6 +44,7 @@
private ComboFieldEditor mWhichPerspective;
private IntegerFieldEditor mMaxMessages;
private BooleanFieldEditor mAutoMonitorLogcat;
+ private ComboFieldEditor mAutoMonitorLogcatLevel;
public LogCatPreferencePage() {
super(GRID);
@@ -92,6 +94,22 @@
Messages.LogCatPreferencePage_AutoMonitorLogcat,
getFieldEditorParent());
addField(mAutoMonitorLogcat);
+
+ mAutoMonitorLogcatLevel = new ComboFieldEditor(LogCatMonitor.AUTO_MONITOR_LOGLEVEL,
+ Messages.LogCatPreferencePage_SessionFilterLogLevel,
+ new String[][] {
+ { LogLevel.VERBOSE.toString(), LogLevel.VERBOSE.getStringValue() },
+ { LogLevel.DEBUG.toString(), LogLevel.DEBUG.getStringValue() },
+ { LogLevel.INFO.toString(), LogLevel.INFO.getStringValue() },
+ { LogLevel.WARN.toString(), LogLevel.WARN.getStringValue() },
+ { LogLevel.ERROR.toString(), LogLevel.ERROR.getStringValue() },
+ { LogLevel.ASSERT.toString(), LogLevel.ASSERT.getStringValue() },
+ },
+ getFieldEditorParent());
+ mAutoMonitorLogcatLevel.setEnabled(
+ getPreferenceStore().getBoolean(LogCatMonitor.AUTO_MONITOR_PREFKEY),
+ getFieldEditorParent());
+ addField(mAutoMonitorLogcatLevel);
}
@Override
@@ -101,8 +119,11 @@
@Override
public void propertyChange(PropertyChangeEvent event) {
if (event.getSource().equals(mSwitchPerspective)) {
- mWhichPerspective.setEnabled(mSwitchPerspective.getBooleanValue()
- , getFieldEditorParent());
+ mWhichPerspective.setEnabled(mSwitchPerspective.getBooleanValue(),
+ getFieldEditorParent());
+ } else if (event.getSource().equals(mAutoMonitorLogcat)) {
+ mAutoMonitorLogcatLevel.setEnabled(mAutoMonitorLogcat.getBooleanValue(),
+ getFieldEditorParent());
}
}
@@ -113,5 +134,8 @@
mMaxMessages.setStringValue(
Integer.toString(LogCatMessageList.MAX_MESSAGES_DEFAULT));
+
+ mAutoMonitorLogcatLevel.setEnabled(mAutoMonitorLogcat.getBooleanValue(),
+ getFieldEditorParent());
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/PreferenceInitializer.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/PreferenceInitializer.java
index fa73dbc..2415e20 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/PreferenceInitializer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/PreferenceInitializer.java
@@ -21,8 +21,11 @@
import com.android.ide.eclipse.ddms.views.DeviceView.HProfHandler;
import com.android.ide.eclipse.ddms.views.LogCatView;
import com.android.ddmlib.DdmPreferences;
+import com.android.ddmlib.Log.LogLevel;
import com.android.ddmuilib.DdmUiPreferences;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
@@ -106,7 +109,15 @@
store.setDefault(ATTR_LOGCAT_FONT,
new FontData("Courier", 10, SWT.NORMAL).toString()); //$NON-NLS-1$
+ // When obtaining hprof files from the device, default to opening the file
+ // only if there is a registered content type for the hprof extension.
store.setDefault(ATTR_HPROF_ACTION, HProfHandler.ACTION_SAVE);
+ for (IContentType contentType: Platform.getContentTypeManager().getAllContentTypes()) {
+ if (contentType.isAssociatedWith(HProfHandler.DOT_HPROF)) {
+ store.setDefault(ATTR_HPROF_ACTION, HProfHandler.ACTION_OPEN);
+ break;
+ }
+ }
store.setDefault(ATTR_TIME_OUT, DdmPreferences.DEFAULT_TIMEOUT);
@@ -116,6 +127,7 @@
store.setDefault(ATTR_PERSPECTIVE_ID, LogCatView.DEFAULT_PERSPECTIVE_ID);
store.setDefault(LogCatMonitor.AUTO_MONITOR_PREFKEY, true);
+ store.setDefault(LogCatMonitor.AUTO_MONITOR_LOGLEVEL, LogLevel.VERBOSE.getStringValue());
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
index 9b7544d..2b07680 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
@@ -18,6 +18,7 @@
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.Client;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IDevice.DeviceUnixSocketNamespace;
import com.android.ddmlib.IShellOutputReceiver;
@@ -168,8 +169,10 @@
private void startActivity(IDevice device, String appName)
throws TimeoutException, AdbCommandRejectedException,
ShellCommandUnresponsiveException, IOException, InterruptedException {
+ killApp(device, appName); // kill app if it is already running
+
String startAppCmd = String.format(
- "am start -S --opengl-trace %s -a android.intent.action.MAIN -c android.intent.category.LAUNCHER", //$NON-NLS-1$
+ "am start --opengl-trace %s -a android.intent.action.MAIN -c android.intent.category.LAUNCHER", //$NON-NLS-1$
appName);
Semaphore launchCompletionSempahore = new Semaphore(0);
@@ -187,6 +190,13 @@
}
}
+ private void killApp(IDevice device, String appName) {
+ Client client = device.getClient(appName);
+ if (client != null) {
+ client.kill();
+ }
+ }
+
private void setupForwarding(IDevice device, int i)
throws TimeoutException, AdbCommandRejectedException, IOException {
device.createForward(i, GLTRACE_UDS, DeviceUnixSocketNamespace.ABSTRACT);
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/ProtoBufUtils.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/ProtoBufUtils.java
index 7165e63..ed6a277 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/ProtoBufUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/ProtoBufUtils.java
@@ -30,6 +30,10 @@
int width = glMsg.getFb().getWidth();
int height = glMsg.getFb().getHeight();
+ if (width * height == 0) {
+ return null;
+ }
+
byte[] compressed = glMsg.getFb().getContents(0).toByteArray();
byte[] uncompressed = new byte[width * height * 4];
@@ -59,7 +63,12 @@
return null;
}
- return new Image(display, getImageData(glMsg));
+ ImageData imageData = getImageData(glMsg);
+ if (imageData == null) {
+ return null;
+ }
+
+ return new Image(display, imageData);
}
/**
@@ -72,6 +81,10 @@
}
ImageData imageData = getImageData(glMsg);
+ if (imageData == null) {
+ return null;
+ }
+
return new Image(display, imageData.scaledTo(width, height));
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/BufferSubDataTransform.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/BufferSubDataTransform.java
index 844c7dc..393f1b9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/BufferSubDataTransform.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/BufferSubDataTransform.java
@@ -44,12 +44,17 @@
IGLProperty property = mAccessor.getProperty(state);
mOldData = (byte[]) property.getValue();
- if (mNewData != null) {
+ if (mOldData != null) {
mNewData = new byte[mOldData.length];
ByteBuffer bb = ByteBuffer.wrap(mNewData);
- bb.put(mOldData); // copy all of the old buffer
+
+ // copy all of the old buffer
+ bb.put(mOldData);
bb.rewind();
- bb.put(mSubData, mOffset, mSubData.length); // update with the sub buffer data
+
+ // update with the sub buffer data at specified offset
+ bb.position(mOffset);
+ bb.put(mSubData);
}
property.setValue(mNewData);
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
index 576e87b..df92ea7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
@@ -425,7 +425,7 @@
IStateTransform transform = new BufferSubDataTransform(
new CurrentVboPropertyAccessor(msg.getContextId(),
target,
- GLStateType.BUFFER_SIZE),
+ GLStateType.BUFFER_DATA),
offset, data);
return Collections.singletonList(transform);
diff --git a/eclipse/plugins/com.android.ide.eclipse.monitor/build.properties b/eclipse/plugins/com.android.ide.eclipse.monitor/build.properties
index 97144a1..44471de 100644
--- a/eclipse/plugins/com.android.ide.eclipse.monitor/build.properties
+++ b/eclipse/plugins/com.android.ide.eclipse.monitor/build.properties
@@ -6,4 +6,5 @@
libs/,\
plugin_customization.ini,\
plugin.properties,\
- images/
+ images/,\
+ splash.bmp
diff --git a/eclipse/plugins/com.android.ide.eclipse.monitor/splash.bmp b/eclipse/plugins/com.android.ide.eclipse.monitor/splash.bmp
new file mode 100644
index 0000000..54880bf
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.monitor/splash.bmp
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
index 177662d..9eeb2ea 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
@@ -114,4 +114,14 @@
assertSame("Foo", AdtUtils.capitalize("Foo"));
assertNull(null, AdtUtils.capitalize(null));
}
+
+ public void testStripSuffix() {
+ assertEquals("Foo", AdtUtils.stripSuffix("Foo", ""));
+ assertEquals("Fo", AdtUtils.stripSuffix("Foo", "o"));
+ assertEquals("F", AdtUtils.stripSuffix("Fo", "o"));
+ assertEquals("", AdtUtils.stripSuffix("Foo", "Foo"));
+ assertEquals("LinearLayout_Layout",
+ AdtUtils.stripSuffix("LinearLayout_LayoutParams", "Params"));
+ assertEquals("Foo", AdtUtils.stripSuffix("Foo", "Bar"));
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
index 4bd0bba..9e9c734 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
@@ -157,12 +157,6 @@
assertEquals(0xFFFF0000, crop.getRGB(49, 49));
}
- public void testNullOk() throws Exception {
- ImageUtils.cropBlank(null, null);
- ImageUtils.cropColor(null, 0, null);
- }
-
-
public void testNothingTodo() throws Exception {
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = image.getGraphics();
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
index 48765ec..0b498e5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
@@ -32,6 +32,7 @@
/**
* Test ResourceHelper
*/
+@SuppressWarnings("javadoc")
public class ResourceHelperTest extends TestCase {
/**
@@ -188,5 +189,11 @@
assertEquals("Foo", ResourceHelper.styleToTheme("Foo"));
assertEquals("Theme", ResourceHelper.styleToTheme("@android:style/Theme"));
assertEquals("LocalTheme", ResourceHelper.styleToTheme("@style/LocalTheme"));
+ assertEquals("LocalTheme", ResourceHelper.styleToTheme("@foo.bar/style/LocalTheme"));
+ }
+
+ public void testIsProjectStyle() throws Exception {
+ assertTrue(ResourceHelper.isProjectStyle("@android:style/Theme"));
+ assertFalse(ResourceHelper.isProjectStyle("@style/LocalTheme"));
}
}
diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh
index 3ab01d3..0e11631 100755
--- a/eclipse/scripts/create_all_symlinks.sh
+++ b/eclipse/scripts/create_all_symlinks.sh
@@ -126,7 +126,7 @@
ADT_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.adt/libs"
ADT_LIBS="layoutlib_api lint_api lint_checks ide_common rule_api ninepatch sdkuilib assetstudio propertysheet"
ADT_PREBUILTS="\
- prebuilt/common/kxml2/kxml2-2.3.0.jar \
+ prebuilts/misc/common/kxml2/kxml2-2.3.0.jar \
prebuilts/tools/common/asm-tools/asm-4.0.jar \
prebuilts/tools/common/asm-tools/asm-tree-4.0.jar \
prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar"
@@ -152,7 +152,7 @@
TEST_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.tests"
TEST_LIBS="easymock"
-TEST_PREBUILTS="prebuilt/common/kxml2/kxml2-2.3.0.jar"
+TEST_PREBUILTS="prebuilts/misc/common/kxml2/kxml2-2.3.0.jar"
LIBS="$LIBS $TEST_LIBS"
CP_FILES="$CP_FILES @:$TEST_DEST $TEST_LIBS $TEST_PREBUILTS"
diff --git a/emulator/opengl/host/include/libOpenglRender/render_api.h b/emulator/opengl/host/include/libOpenglRender/render_api.h
index 9c76b3e..4fe54f4 100644
--- a/emulator/opengl/host/include/libOpenglRender/render_api.h
+++ b/emulator/opengl/host/include/libOpenglRender/render_api.h
@@ -16,31 +16,85 @@
#ifndef _OPENGL_RENDERER_RENDER_API_H
#define _OPENGL_RENDERER_RENDER_API_H
+/* This header and its declarations must be usable from C code.
+ *
+ * If RENDER_API_NO_PROTOTYPES is #defined before including this header, only
+ * the interface function pointer types will be declared, not the prototypes.
+ * This allows the client to use those names for its function pointer variables.
+ *
+ * All interfaces which can fail return an int, with zero indicating failure
+ * and anything else indicating success.
+ */
+
#ifdef __cplusplus
extern "C" {
#endif
#include "render_api_platform_types.h"
-/* If a function with this signature is passed to initOpenGLRenderer(),
- * it will be called by the renderer just before each new frame is displayed,
- * providing a copy of the framebuffer contents.
+#if defined(RENDER_API_NO_PROTOTYPES)
+#define DECL(ret, name, args) \
+ typedef ret (* name##Fn) args
+#else
+#define DECL(ret, name, args) \
+ typedef ret (* name##Fn) args ; \
+ ret name args
+#endif
+
+/* initLibrary - initialize the library and tries to load the corresponding
+ * GLES translator libraries. This function must be called before anything
+ * else to ensure that everything works. If it returns an error, then
+ * you cannot use the library at all (this can happen under certain
+ * environments where the desktop GL libraries are not available)
+ */
+DECL(int, initLibrary, (void));
+
+/* list of constants to be passed to setStreamMode */
+#define STREAM_MODE_DEFAULT 0
+#define STREAM_MODE_TCP 1
+#define STREAM_MODE_UNIX 2
+#define STREAM_MODE_PIPE 3
+
+/* Change the stream mode. This must be called before initOpenGLRenderer */
+DECL(int, setStreamMode, (int mode));
+
+/* initOpenGLRenderer - initialize the OpenGL renderer process.
+ * portNum is the tcp port number the renderer is listening to.
+ * width and height are the framebuffer dimensions that will be
+ * reported to the guest display driver.
+ *
+ * This function is *NOT* thread safe and should be called first
+ * to initialize the renderer after initLibrary().
+ */
+DECL(int, initOpenGLRenderer, (int width, int height, int portNum));
+
+/* getHardwareStrings - describe the GPU hardware and driver.
+ * The underlying GL's vendor/renderer/version strings are returned to the
+ * caller. The pointers become invalid after a call to stopOpenGLRenderer().
+ */
+DECL(void, getHardwareStrings, (const char** vendor, const char** renderer,
+ const char** version));
+
+/* A per-frame callback can be registered with setPostCallback(); to remove it
+ * pass NULL for both parameters. While a callback is registered, the renderer
+ * will call it just before each new frame is displayed, providing a copy of
+ * the framebuffer contents.
*
* The callback will be called from one of the renderer's threads, so will
* probably need synchronization on any data structures it modifies. The
- * pixels buffer may be overwritten as soon as the callback returns; if it needs
- * the pixels afterwards it must copy them.
+ * pixels buffer may be overwritten as soon as the callback returns; if it
+ * needs the pixels afterwards it must copy them.
*
* The pixels buffer is intentionally not const: the callback may modify the
- * data without copying to another buffer if it wants, e.g. in-place RGBA to RGB
- * conversion, or in-place y-inversion.
+ * data without copying to another buffer if it wants, e.g. in-place RGBA to
+ * RGB conversion, or in-place y-inversion.
*
* Parameters are:
* context The pointer optionally provided when the callback was
* registered. The client can use this to pass whatever
* information it wants to the callback.
- * width, height Dimensions of the image, in pixels. Rows are tightly packed;
- * there is no inter-row padding.
+ * width, height Dimensions of the image, in pixels. Rows are tightly
+ * packed; there is no inter-row padding.
* ydir Indicates row order: 1 means top-to-bottom order, -1 means
* bottom-to-top order.
* format, type Format and type GL enums, as used in glTexImage2D() or
@@ -53,81 +107,43 @@
*/
typedef void (*OnPostFn)(void* context, int width, int height, int ydir,
int format, int type, unsigned char* pixels);
+DECL(void, setPostCallback, (OnPostFn onPost, void* onPostContext));
-// initLibrary - initialize the library and tries to load the corresponding
-// GLES translator libraries. This function must be called before anything
-// else to ensure that everything works. If it returns an error, then
-// you cannot use the library at all (this can happen under certain
-// environments where the desktop GL libraries are not available)
-//
-// returns true if the library could be initialized successfully;
-//
-bool initLibrary(void);
+/* createOpenGLSubwindow -
+ * Create a native subwindow which is a child of 'window'
+ * to be used for framebuffer display.
+ * Framebuffer will not get displayed if a subwindow is not
+ * created.
+ * x,y,width,height are the dimensions of the rendering subwindow.
+ * zRot is the rotation to apply on the framebuffer display image.
+ */
+DECL(int, createOpenGLSubwindow, (FBNativeWindowType window,
+ int x, int y, int width, int height, float zRot));
-// list of constants to be passed to setStreamMode, which determines
-// which
-#define STREAM_MODE_DEFAULT 0
-#define STREAM_MODE_TCP 1
-#define STREAM_MODE_UNIX 2
-#define STREAM_MODE_PIPE 3
+/* destroyOpenGLSubwindow -
+ * destroys the created native subwindow. Once destroyed,
+ * Framebuffer content will not be visible until a new
+ * subwindow will be created.
+ */
+DECL(int, destroyOpenGLSubwindow, (void));
-// Change the stream mode. This must be called before initOpenGLRenderer
-int setStreamMode(int mode);
+/* setOpenGLDisplayRotation -
+ * set the framebuffer display image rotation in units
+ * of degrees around the z axis
+ */
+DECL(void, setOpenGLDisplayRotation, (float zRot));
-//
-// initOpenGLRenderer - initialize the OpenGL renderer process.
-// portNum is the tcp port number the renderer is listening to.
-// width and height are the framebuffer dimensions that will be
-// reported to the guest display driver.
-//
-// returns true if renderer has been started successfully;
-//
-// This function is *NOT* thread safe and should be called first
-// to initialize the renderer after initLibrary().
-//
-bool initOpenGLRenderer(int width, int height, int portNum,
- OnPostFn onPost, void* onPostContext);
+/* repaintOpenGLDisplay -
+ * causes the OpenGL subwindow to get repainted with the
+ * latest framebuffer content.
+ */
+DECL(void, repaintOpenGLDisplay, (void));
-//
-// createOpenGLSubwindow -
-// Create a native subwindow which is a child of 'window'
-// to be used for framebuffer display.
-// Framebuffer will not get displayed if a subwindow is not
-// created.
-// x,y,width,height are the dimensions of the rendering subwindow.
-// zRot is the rotation to apply on the framebuffer display image.
-//
-bool createOpenGLSubwindow(FBNativeWindowType window,
- int x, int y, int width, int height, float zRot);
-
-//
-// destroyOpenGLSubwindow -
-// destroys the created native subwindow. Once destroyed,
-// Framebuffer content will not be visible until a new
-// subwindow will be created.
-//
-bool destroyOpenGLSubwindow();
-
-//
-// setOpenGLDisplayRotation -
-// set the framebuffer display image rotation in units
-// of degrees around the z axis
-//
-void setOpenGLDisplayRotation(float zRot);
-
-//
-// repaintOpenGLDisplay -
-// causes the OpenGL subwindow to get repainted with the
-// latest framebuffer content.
-//
-void repaintOpenGLDisplay();
-
-//
-// stopOpenGLRenderer - stops the OpenGL renderer process.
-// This functions is *NOT* thread safe and should be called
-// only if previous initOpenGLRenderer has returned true.
-//
-bool stopOpenGLRenderer();
+/* stopOpenGLRenderer - stops the OpenGL renderer process.
+ * This functions is *NOT* thread safe and should be called
+ * only if previous initOpenGLRenderer has returned true.
+ */
+DECL(int, stopOpenGLRenderer, (void));
#ifdef __cplusplus
}
diff --git a/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmContext.cpp b/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmContext.cpp
index abf461c..ecf51bb 100644
--- a/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmContext.cpp
+++ b/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmContext.cpp
@@ -31,13 +31,10 @@
m_texCoords = new GLESpointer[s_glSupport.maxTexUnits];
m_map[GL_TEXTURE_COORD_ARRAY] = &m_texCoords[m_clientActiveTexture];
- const char* baseRenderer = (const char*)dispatcher().glGetString(GL_RENDERER);
- size_t baseRendererLen = strlen(baseRenderer);
- s_glRenderer.clear();
- s_glRenderer.reserve(19 + baseRendererLen);
- s_glRenderer.append("OpenGL ES-CM 1.1 (", 18);
- s_glRenderer.append(baseRenderer, baseRendererLen);
- s_glRenderer.append(")", 1);
+ buildStrings((const char*)dispatcher().glGetString(GL_VENDOR),
+ (const char*)dispatcher().glGetString(GL_RENDERER),
+ (const char*)dispatcher().glGetString(GL_VERSION),
+ "OpenGL ES-CM 1.1");
}
m_initialized = true;
}
diff --git a/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmImp.cpp b/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmImp.cpp
index b78a022..3ae271f 100644
--- a/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmImp.cpp
+++ b/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmImp.cpp
@@ -248,16 +248,13 @@
GL_API const GLubyte * GL_APIENTRY glGetString( GLenum name) {
GET_CTX_RET(NULL)
- static const GLubyte VENDOR[] = "Google";
- static const GLubyte VERSION[] = "OpenGL ES-CM 1.1";
-
switch(name) {
case GL_VENDOR:
- return VENDOR;
+ return (const GLubyte*)ctx->getVendorString();
case GL_RENDERER:
return (const GLubyte*)ctx->getRendererString();
case GL_VERSION:
- return VERSION;
+ return (const GLubyte*)ctx->getVersionString();
case GL_EXTENSIONS:
return (const GLubyte*)ctx->getExtensionString();
default:
diff --git a/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Context.cpp b/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Context.cpp
index 73acb61..1457cec 100644
--- a/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Context.cpp
+++ b/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Context.cpp
@@ -28,13 +28,10 @@
}
setAttribute0value(0.0, 0.0, 0.0, 1.0);
- const char* baseRenderer = (const char*)dispatcher().glGetString(GL_RENDERER);
- size_t baseRendererLen = strlen(baseRenderer);
- s_glRenderer.clear();
- s_glRenderer.reserve(16 + baseRendererLen);
- s_glRenderer.append("OpenGL ES 2.0 (", 15);
- s_glRenderer.append(baseRenderer, baseRendererLen);
- s_glRenderer.append(")", 1);
+ buildStrings((const char*)dispatcher().glGetString(GL_VENDOR),
+ (const char*)dispatcher().glGetString(GL_RENDERER),
+ (const char*)dispatcher().glGetString(GL_VERSION),
+ "OpenGL ES 2.0");
}
m_initialized = true;
}
diff --git a/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Imp.cpp b/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Imp.cpp
index 412584d..7ae9427 100644
--- a/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Imp.cpp
+++ b/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Imp.cpp
@@ -1316,16 +1316,14 @@
GL_APICALL const GLubyte* GL_APIENTRY glGetString(GLenum name){
GET_CTX_RET(NULL)
- static const GLubyte VENDOR[] = "Google";
- static const GLubyte VERSION[] = "OpenGL ES 2.0";
static const GLubyte SHADING[] = "OpenGL ES GLSL ES 1.0.17";
switch(name) {
case GL_VENDOR:
- return VENDOR;
+ return (const GLubyte*)ctx->getVendorString();
case GL_RENDERER:
return (const GLubyte*)ctx->getRendererString();
case GL_VERSION:
- return VERSION;
+ return (const GLubyte*)ctx->getVersionString();
case GL_SHADING_LANGUAGE_VERSION:
return SHADING;
case GL_EXTENSIONS:
diff --git a/emulator/opengl/host/libs/Translator/GLcommon/GLEScontext.cpp b/emulator/opengl/host/libs/Translator/GLcommon/GLEScontext.cpp
index cff639e..387eb2d 100644
--- a/emulator/opengl/host/libs/Translator/GLcommon/GLEScontext.cpp
+++ b/emulator/opengl/host/libs/Translator/GLcommon/GLEScontext.cpp
@@ -86,7 +86,9 @@
GLDispatch GLEScontext::s_glDispatch;
android::Mutex GLEScontext::s_lock;
std::string* GLEScontext::s_glExtensions= NULL;
+std::string GLEScontext::s_glVendor;
std::string GLEScontext::s_glRenderer;
+std::string GLEScontext::s_glVersion;
GLSupport GLEScontext::s_glSupport;
Version::Version():m_major(0),
@@ -483,10 +485,18 @@
return ret;
}
+const char * GLEScontext::getVendorString() const {
+ return s_glVendor.c_str();
+}
+
const char * GLEScontext::getRendererString() const {
return s_glRenderer.c_str();
}
+const char * GLEScontext::getVersionString() const {
+ return s_glVersion.c_str();
+}
+
void GLEScontext::getGlobalLock() {
s_lock.lock();
}
@@ -547,6 +557,38 @@
}
+void GLEScontext::buildStrings(const char* baseVendor,
+ const char* baseRenderer, const char* baseVersion, const char* version)
+{
+ static const char VENDOR[] = {"Google ("};
+ static const char RENDERER[] = {"Android Emulator OpenGL ES Translator ("};
+ const size_t VENDOR_LEN = sizeof(VENDOR) - 1;
+ const size_t RENDERER_LEN = sizeof(RENDERER) - 1;
+
+ size_t baseVendorLen = strlen(baseVendor);
+ s_glVendor.clear();
+ s_glVendor.reserve(baseVendorLen + VENDOR_LEN + 1);
+ s_glVendor.append(VENDOR, VENDOR_LEN);
+ s_glVendor.append(baseVendor, baseVendorLen);
+ s_glVendor.append(")", 1);
+
+ size_t baseRendererLen = strlen(baseRenderer);
+ s_glRenderer.clear();
+ s_glRenderer.reserve(baseRendererLen + RENDERER_LEN + 1);
+ s_glRenderer.append(RENDERER, RENDERER_LEN);
+ s_glRenderer.append(baseRenderer, baseRendererLen);
+ s_glRenderer.append(")", 1);
+
+ size_t baseVersionLen = strlen(baseVersion);
+ size_t versionLen = strlen(version);
+ s_glVersion.clear();
+ s_glVersion.reserve(baseVersionLen + versionLen + 3);
+ s_glVersion.append(version, versionLen);
+ s_glVersion.append(" (", 2);
+ s_glVersion.append(baseVersion, baseVersionLen);
+ s_glVersion.append(")", 1);
+}
+
bool GLEScontext::isTextureUnitEnabled(GLenum unit) {
for (int i=0;i<NUM_TEXTURE_TARGETS;++i) {
if (m_texState[unit-GL_TEXTURE0][i].enabled)
diff --git a/emulator/opengl/host/libs/Translator/include/GLcommon/GLEScontext.h b/emulator/opengl/host/libs/Translator/include/GLcommon/GLEScontext.h
index fbc118f..20509fc 100644
--- a/emulator/opengl/host/libs/Translator/include/GLcommon/GLEScontext.h
+++ b/emulator/opengl/host/libs/Translator/include/GLcommon/GLEScontext.h
@@ -147,7 +147,9 @@
bool setBufferData(GLenum target,GLsizeiptr size,const GLvoid* data,GLenum usage);
bool setBufferSubData(GLenum target,GLintptr offset,GLsizeiptr size,const GLvoid* data);
const char * getExtensionString();
+ const char * getVendorString() const;
const char * getRendererString() const;
+ const char * getVersionString() const;
void getGlobalLock();
void releaseGlobalLock();
virtual GLSupport* getCaps(){return &s_glSupport;};
@@ -176,6 +178,7 @@
virtual bool glGetFixedv(GLenum pname, GLfixed *params);
protected:
+ static void buildStrings(const char* baseVendor, const char* baseRenderer, const char* baseVersion, const char* version);
virtual bool needConvert(GLESConversionArrays& fArrs,GLint first,GLsizei count,GLenum type,const GLvoid* indices,bool direct,GLESpointer* p,GLenum array_id) = 0;
void convertDirect(GLESConversionArrays& fArrs,GLint first,GLsizei count,GLenum array_id,GLESpointer* p);
void convertDirectVBO(GLESConversionArrays& fArrs,GLint first,GLsizei count,GLenum array_id,GLESpointer* p);
@@ -183,6 +186,7 @@
void convertIndirectVBO(GLESConversionArrays& fArrs,GLsizei count,GLenum indices_type,const GLvoid* indices,GLenum array_id,GLESpointer* p);
void initCapsLocked(const GLubyte * extensionString);
virtual void initExtensionString() =0;
+
static android::Mutex s_lock;
static GLDispatch s_glDispatch;
bool m_initialized;
@@ -190,7 +194,6 @@
GLint m_unpackAlignment;
ArraysMap m_map;
static std::string* s_glExtensions;
- static std::string s_glRenderer;
static GLSupport s_glSupport;
private:
@@ -205,6 +208,10 @@
unsigned int m_elementBuffer;
GLuint m_renderbuffer;
GLuint m_framebuffer;
+
+ static std::string s_glVendor;
+ static std::string s_glRenderer;
+ static std::string s_glVersion;
};
#endif
diff --git a/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.cpp b/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.cpp
index b7599ce..2f7cd61 100644
--- a/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.cpp
+++ b/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.cpp
@@ -101,7 +101,7 @@
}
}
-bool FrameBuffer::initialize(int width, int height, OnPostFn onPost, void* onPostContext)
+bool FrameBuffer::initialize(int width, int height)
{
if (s_theFrameBuffer != NULL) {
return true;
@@ -110,7 +110,7 @@
//
// allocate space for the FrameBuffer object
//
- FrameBuffer *fb = new FrameBuffer(width, height, onPost, onPostContext);
+ FrameBuffer *fb = new FrameBuffer(width, height);
if (!fb) {
ERR("Failed to create fb\n");
return false;
@@ -335,15 +335,12 @@
fb->initGLState();
//
- // Allocate space for the onPost framebuffer image
+ // Cache the GL strings so we don't have to think about threading or
+ // current-context when asked for them.
//
- if (onPost) {
- fb->m_fbImage = (unsigned char*)malloc(4 * width * height);
- if (!fb->m_fbImage) {
- delete fb;
- return false;
- }
- }
+ fb->m_glVendor = (const char*)s_gl.glGetString(GL_VENDOR);
+ fb->m_glRenderer = (const char*)s_gl.glGetString(GL_RENDERER);
+ fb->m_glVersion = (const char*)s_gl.glGetString(GL_VERSION);
// release the FB context
fb->unbind_locked();
@@ -355,8 +352,7 @@
return true;
}
-FrameBuffer::FrameBuffer(int p_width, int p_height,
- OnPostFn onPost, void* onPostContext) :
+FrameBuffer::FrameBuffer(int p_width, int p_height) :
m_width(p_width),
m_height(p_height),
m_eglDisplay(EGL_NO_DISPLAY),
@@ -366,16 +362,19 @@
m_prevContext(EGL_NO_CONTEXT),
m_prevReadSurf(EGL_NO_SURFACE),
m_prevDrawSurf(EGL_NO_SURFACE),
- m_subWin(NULL),
+ m_subWin((EGLNativeWindowType)0),
m_subWinDisplay(NULL),
m_lastPostedColorBuffer(0),
m_zRot(0.0f),
m_eglContextInitialized(false),
m_statsNumFrames(0),
m_statsStartTime(0LL),
- m_onPost(onPost),
- m_onPostContext(onPostContext),
- m_fbImage(NULL)
+ m_onPost(NULL),
+ m_onPostContext(NULL),
+ m_fbImage(NULL),
+ m_glVendor(NULL),
+ m_glRenderer(NULL),
+ m_glVersion(NULL)
{
m_fpsStats = getenv("SHOW_FPS_STATS") != NULL;
}
@@ -385,6 +384,22 @@
free(m_fbImage);
}
+void FrameBuffer::setPostCallback(OnPostFn onPost, void* onPostContext)
+{
+ android::Mutex::Autolock mutex(m_lock);
+ m_onPost = onPost;
+ m_onPostContext = onPostContext;
+ if (m_onPost && !m_fbImage) {
+ m_fbImage = (unsigned char*)malloc(4 * m_width * m_height);
+ if (!m_fbImage) {
+ ERR("out of memory, cancelling OnPost callback");
+ m_onPost = NULL;
+ m_onPostContext = NULL;
+ return;
+ }
+ }
+}
+
bool FrameBuffer::setupSubWindow(FBNativeWindowType p_window,
int p_x, int p_y,
int p_width, int p_height, float zRot)
@@ -412,7 +427,7 @@
if (fb->m_eglSurface == EGL_NO_SURFACE) {
ERR("Failed to create surface\n");
destroySubWindow(fb->m_subWinDisplay, fb->m_subWin);
- fb->m_subWin = NULL;
+ fb->m_subWin = (EGLNativeWindowType)0;
}
else if (fb->bindSubwin_locked()) {
// Subwin creation was successfull,
@@ -445,7 +460,7 @@
s_theFrameBuffer->m_subWin);
s_theFrameBuffer->m_eglSurface = EGL_NO_SURFACE;
- s_theFrameBuffer->m_subWin = NULL;
+ s_theFrameBuffer->m_subWin = (EGLNativeWindowType)0;
removed = true;
}
s_theFrameBuffer->m_lock.unlock();
diff --git a/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.h b/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.h
index 343f384..de0b71c 100644
--- a/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.h
+++ b/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.h
@@ -46,7 +46,7 @@
class FrameBuffer
{
public:
- static bool initialize(int width, int height, OnPostFn onPost, void* onPostContext);
+ static bool initialize(int width, int height);
static bool setupSubWindow(FBNativeWindowType p_window,
int x, int y,
int width, int height, float zRot);
@@ -59,6 +59,14 @@
int getWidth() const { return m_width; }
int getHeight() const { return m_height; }
+ void setPostCallback(OnPostFn onPost, void* onPostContext);
+
+ void getGLStrings(const char** vendor, const char** renderer, const char** version) const {
+ *vendor = m_glVendor;
+ *renderer = m_glRenderer;
+ *version = m_glVersion;
+ }
+
HandleType createRenderContext(int p_config, HandleType p_share, bool p_isGL2 = false);
HandleType createWindowSurface(int p_config, int p_width, int p_height);
HandleType createColorBuffer(int p_width, int p_height, GLenum p_internalFormat);
@@ -90,7 +98,7 @@
}
private:
- FrameBuffer(int p_width, int p_height, OnPostFn onPost, void* onPostContext);
+ FrameBuffer(int p_width, int p_height);
~FrameBuffer();
HandleType genHandle();
bool bindSubwin_locked();
@@ -133,5 +141,9 @@
OnPostFn m_onPost;
void* m_onPostContext;
unsigned char* m_fbImage;
+
+ const char* m_glVendor;
+ const char* m_glRenderer;
+ const char* m_glVersion;
};
#endif
diff --git a/emulator/opengl/host/libs/libOpenglRender/render_api.cpp b/emulator/opengl/host/libs/libOpenglRender/render_api.cpp
index c8d3e06..72cd9ba 100644
--- a/emulator/opengl/host/libs/libOpenglRender/render_api.cpp
+++ b/emulator/opengl/host/libs/libOpenglRender/render_api.cpp
@@ -50,7 +50,7 @@
#define RENDER_API_USE_THREAD
//#endif
-bool initLibrary(void)
+int initLibrary(void)
{
//
// Load EGL Plugin
@@ -76,8 +76,7 @@
return true;
}
-bool initOpenGLRenderer(int width, int height, int portNum,
- OnPostFn onPost, void* onPostContext)
+int initOpenGLRenderer(int width, int height, int portNum)
{
//
@@ -94,7 +93,7 @@
// initialize the renderer and listen to connections
// on a thread in the current process.
//
- bool inited = FrameBuffer::initialize(width, height, onPost, onPostContext);
+ bool inited = FrameBuffer::initialize(width, height);
if (!inited) {
return false;
}
@@ -191,7 +190,38 @@
return true;
}
-bool stopOpenGLRenderer()
+void setPostCallback(OnPostFn onPost, void* onPostContext)
+{
+#ifdef RENDER_API_USE_THREAD // should be defined for mac
+ FrameBuffer* fb = FrameBuffer::getFB();
+ if (fb) {
+ fb->setPostCallback(onPost, onPostContext);
+ }
+#else
+ if (onPost) {
+ // onPost callback not supported with separate renderer process.
+ //
+ // If we ever revive separate process support, we could make the choice
+ // between thread and process at runtime instead of compile time, and
+ // choose the thread path if an onPost callback is requested. Or, the
+ // callback could be supported with a separate process using shmem or
+ // other IPC mechanism.
+ return false;
+ }
+#endif
+}
+
+void getHardwareStrings(const char** vendor, const char** renderer, const char** version)
+{
+ FrameBuffer* fb = FrameBuffer::getFB();
+ if (fb) {
+ fb->getGLStrings(vendor, renderer, version);
+ } else {
+ *vendor = *renderer = *version = NULL;
+ }
+}
+
+int stopOpenGLRenderer(void)
{
bool ret = false;
@@ -224,7 +254,7 @@
return ret;
}
-bool createOpenGLSubwindow(FBNativeWindowType window,
+int createOpenGLSubwindow(FBNativeWindowType window,
int x, int y, int width, int height, float zRot)
{
if (s_renderThread) {
@@ -240,7 +270,7 @@
return false;
}
-bool destroyOpenGLSubwindow()
+int destroyOpenGLSubwindow(void)
{
if (s_renderThread) {
return FrameBuffer::removeSubWindow();
@@ -272,7 +302,7 @@
}
}
-void repaintOpenGLDisplay()
+void repaintOpenGLDisplay(void)
{
if (s_renderThread) {
FrameBuffer *fb = FrameBuffer::getFB();
@@ -352,8 +382,8 @@
#endif /* _WIN32 */
default:
// Invalid stream mode
- return -1;
+ return false;
}
gRendererStreamMode = mode;
- return 0;
+ return true;
}
diff --git a/emulator/opengl/host/renderer/main.cpp b/emulator/opengl/host/renderer/main.cpp
index ae7ace3..4549c56 100644
--- a/emulator/opengl/host/renderer/main.cpp
+++ b/emulator/opengl/host/renderer/main.cpp
@@ -120,7 +120,7 @@
//
// initialize Framebuffer
//
- bool inited = FrameBuffer::initialize(winWidth, winHeight, NULL, NULL);
+ bool inited = FrameBuffer::initialize(winWidth, winHeight);
if (!inited) {
fprintf(stderr,"Failed to initialize Framebuffer\n");
return -1;
diff --git a/emulator/opengl/shared/OpenglOsUtils/Android.mk b/emulator/opengl/shared/OpenglOsUtils/Android.mk
index 82391cd..2e5a8a1 100644
--- a/emulator/opengl/shared/OpenglOsUtils/Android.mk
+++ b/emulator/opengl/shared/OpenglOsUtils/Android.mk
@@ -38,7 +38,7 @@
endif
ifeq ($(HOST_OS),linux)
- host_common_LDLIBS += -lpthread -lrt
+ host_common_LDLIBS += -lpthread -lrt -lX11
endif
### 32-bit host library ####
diff --git a/emulator/opengl/tests/translator_tests/GLES_CM/Android.mk b/emulator/opengl/tests/translator_tests/GLES_CM/Android.mk
index 77cd272..d47d340 100644
--- a/emulator/opengl/tests/translator_tests/GLES_CM/Android.mk
+++ b/emulator/opengl/tests/translator_tests/GLES_CM/Android.mk
@@ -10,8 +10,8 @@
ifeq ($(HOST_OS),darwin)
DARWIN_VERSION := $(strip $(shell sw_vers -productVersion))
- ifneq ($(filter 10.7 10.7.%,$(DARWIN_VERSION)),)
- # Lion needs to be forced to link dylib to avoid problems
+ ifneq ($(filter 10.7 10.7.% 10.8 10.8.%,$(DARWIN_VERSION)),)
+ # OS X 10.7+ needs to be forced to link dylib to avoid problems
# with the dynamic function lookups in SDL 1.2
LOCAL_SDL_LDLIBS += /usr/lib/dylib1.o
endif
diff --git a/emulator/opengl/tests/translator_tests/GLES_V2/Android.mk b/emulator/opengl/tests/translator_tests/GLES_V2/Android.mk
index 53f774c..2371da7 100644
--- a/emulator/opengl/tests/translator_tests/GLES_V2/Android.mk
+++ b/emulator/opengl/tests/translator_tests/GLES_V2/Android.mk
@@ -18,8 +18,8 @@
ifeq ($(HOST_OS),darwin)
DARWIN_VERSION := $(strip $(shell sw_vers -productVersion))
-ifneq ($(filter 10.7 10.7.%,$(DARWIN_VERSION)),)
- # Lion needs to be forced to link dylib to avoid problems
+ifneq ($(filter 10.7 10.7.% 10.8 10.8.%,$(DARWIN_VERSION)),)
+ # OS X 10.7+ needs to be forced to link dylib to avoid problems
# with the dynamic function lookups in SDL 1.2
LOCAL_LDLIBS += /usr/lib/dylib1.o
endif
diff --git a/ide_common/.classpath b/ide_common/.classpath
index 89ce2dd..79fe47a 100644
--- a/ide_common/.classpath
+++ b/ide_common/.classpath
@@ -3,7 +3,7 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/layoutlib_api"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/layoutlib_api/.classpath b/layoutlib_api/.classpath
index 4095535..064639b 100644
--- a/layoutlib_api/.classpath
+++ b/layoutlib_api/.classpath
@@ -2,7 +2,7 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/layoutlib_api/sample/.classpath b/layoutlib_api/sample/.classpath
index 47c38c1..53c678b 100644
--- a/layoutlib_api/sample/.classpath
+++ b/layoutlib_api/sample/.classpath
@@ -2,7 +2,7 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
<classpathentry combineaccessrules="false" kind="src" path="/ide_common"/>
<classpathentry combineaccessrules="false" kind="src" path="/layoutlib_api"/>
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java
index 7d9471d..74df385 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java
@@ -124,7 +124,7 @@
}
// Determine if the scope matches
- if (!scope.containsAll(issueScope)) {
+ if (!issue.isAdequate(scope)) {
continue;
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
index e221faa..0f985f0 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
@@ -21,6 +21,7 @@
import static com.android.tools.lint.detector.api.LintConstants.DOT_CLASS;
import static com.android.tools.lint.detector.api.LintConstants.DOT_JAR;
import static com.android.tools.lint.detector.api.LintConstants.DOT_JAVA;
+import static com.android.tools.lint.detector.api.LintConstants.DOT_XML;
import static com.android.tools.lint.detector.api.LintConstants.OLD_PROGUARD_FILE;
import static com.android.tools.lint.detector.api.LintConstants.PROGUARD_FILE;
import static com.android.tools.lint.detector.api.LintConstants.RES_FOLDER;
@@ -245,7 +246,7 @@
String name = file.getName();
if (name.equals(ANDROID_MANIFEST_XML)) {
mScope.add(Scope.MANIFEST);
- } else if (name.endsWith(".xml")) {
+ } else if (name.endsWith(DOT_XML)) {
mScope.add(Scope.RESOURCE_FILE);
} else if (name.equals(PROGUARD_FILE) || name.equals(OLD_PROGUARD_FILE)) {
mScope.add(Scope.PROGUARD_FILE);
@@ -766,8 +767,13 @@
List<Detector> checks = union(mScopeDetectors.get(Scope.JAVA_FILE),
mScopeDetectors.get(Scope.ALL_JAVA_FILES));
if (checks != null && checks.size() > 0) {
- List<File> sourceFolders = project.getJavaSourceFolders();
- checkJava(project, main, sourceFolders, checks);
+ if (project.getSubset() != null) {
+ checkIndividualJavaFiles(project, main, checks,
+ project.getSubset());
+ } else {
+ List<File> sourceFolders = project.getJavaSourceFolders();
+ checkJava(project, main, sourceFolders, checks);
+ }
}
}
@@ -775,7 +781,9 @@
return;
}
- checkClasses(project, main);
+ if (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.JAVA_LIBRARIES)) {
+ checkClasses(project, main);
+ }
if (mCanceled) {
return;
@@ -785,7 +793,6 @@
checkProGuard(project, main);
}
}
-
private void checkProGuard(Project project, Project main) {
List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE);
if (detectors != null) {
@@ -884,7 +891,8 @@
/** Check the classes in this project (and if applicable, in any library projects */
private void checkClasses(Project project, Project main) {
- if (!(mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.JAVA_LIBRARIES))) {
+ if (project.getSubset() != null) {
+ checkIndividualClassFiles(project, main, project.getSubset());
return;
}
@@ -935,6 +943,47 @@
runClassDetectors(Scope.CLASS_FILE, classEntries, project, main);
}
+ private void checkIndividualClassFiles(
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull List<File> files) {
+ List<ClassEntry> entries = new ArrayList<ClassEntry>(files.size());
+
+ List<File> classFolders = project.getJavaClassFolders();
+ if (!classFolders.isEmpty()) {
+ for (File file : files) {
+ String path = file.getPath();
+ if (file.isFile() && path.endsWith(DOT_CLASS)) {
+ try {
+ byte[] bytes = Files.toByteArray(file);
+ if (bytes != null) {
+ for (File dir : classFolders) {
+ if (path.startsWith(dir.getPath())) {
+ entries.add(new ClassEntry(file, null /* jarFile*/, dir,
+ bytes));
+ break;
+ }
+ }
+ }
+ } catch (IOException e) {
+ mClient.log(e, null);
+ continue;
+ }
+
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+
+ if (entries.size() > 0) {
+ // No superclass info available on individual lint runs
+ mSuperClassMap = Collections.emptyMap();
+ runClassDetectors(Scope.CLASS_FILE, entries, project, main);
+ }
+ }
+ }
+
/**
* Stack of {@link ClassNode} nodes for outer classes of the currently
* processed class, including that class itself. Populated by
@@ -1186,6 +1235,32 @@
}
}
+ private void checkIndividualJavaFiles(
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull List<Detector> checks,
+ @NonNull List<File> files) {
+
+ IJavaParser javaParser = mClient.getJavaParser();
+ if (javaParser == null) {
+ mClient.log(null, "No java parser provided to lint: not running Java checks");
+ return;
+ }
+
+ JavaVisitor visitor = new JavaVisitor(javaParser, checks);
+
+ for (File file : files) {
+ if (file.isFile() && file.getPath().endsWith(DOT_JAVA)) {
+ JavaContext context = new JavaContext(this, project, main, file);
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitFile(context, file);
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ }
+
private void gatherJavaFiles(@NonNull File dir, @NonNull List<File> result) {
File[] files = dir.listFiles();
if (files != null) {
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
index f4f4efa..44d5392 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
@@ -344,7 +344,7 @@
* @param node the instruction node to get a line number for
* @return the closest line number, or -1 if not known
*/
- public static int findLineNumber(AbstractInsnNode node) {
+ public static int findLineNumber(@NonNull AbstractInsnNode node) {
AbstractInsnNode curr = node;
// First search backwards
@@ -373,7 +373,7 @@
* @param node the method node to get a line number for
* @return the closest line number, or -1 if not known
*/
- public static int findLineNumber(MethodNode node) {
+ public static int findLineNumber(@NonNull MethodNode node) {
if (node.instructions != null && node.instructions.size() > 0) {
return findLineNumber(node.instructions.get(0));
}
@@ -382,6 +382,79 @@
}
/**
+ * Finds the line number closest to the given class declaration
+ *
+ * @param node the method node to get a line number for
+ * @return the closest line number, or -1 if not known
+ */
+ public static int findLineNumber(@NonNull ClassNode node) {
+ if (node.methods != null && !node.methods.isEmpty()) {
+ MethodNode firstMethod = getFirstRealMethod(node);
+ if (firstMethod != null) {
+ return ClassContext.findLineNumber(firstMethod);
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns a location for the given {@link ClassNode}, where class node is
+ * either the top level class, or an inner class, in the current context.
+ *
+ * @param classNode the class in the current context
+ * @return a location pointing to the class declaration, or as close to it
+ * as possible
+ */
+ @NonNull
+ public Location getLocation(@NonNull ClassNode classNode) {
+ // Attempt to find a proper location for this class. This is tricky
+ // since classes do not have line number entries in the class file; we need
+ // to find a method, look up the corresponding line number then search
+ // around it for a suitable tag, such as the class name.
+ String pattern;
+ if (isAnonymousClass(classNode.name)) {
+ pattern = classNode.superName.substring(classNode.superName.lastIndexOf('/') + 1);
+ } else {
+ pattern = classNode.name.substring(classNode.name.lastIndexOf('$') + 1);
+ }
+
+ return getLocationForLine(findLineNumber(classNode), pattern, null);
+ }
+
+ @Nullable
+ private static MethodNode getFirstRealMethod(@NonNull ClassNode classNode) {
+ // Return the first method in the class for line number purposes. Skip <init>,
+ // since it's typically not located near the real source of the method.
+ if (classNode.methods != null) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List methods = classNode.methods;
+ for (Object m : methods) {
+ MethodNode method = (MethodNode) m;
+ if (method.name.charAt(0) != '<') {
+ return method;
+ }
+ }
+
+ if (classNode.methods.size() > 0) {
+ return (MethodNode) classNode.methods.get(0);
+ }
+ }
+
+ return null;
+ }
+
+ private static boolean isAnonymousClass(@NonNull String fqcn) {
+ int lastIndex = fqcn.lastIndexOf('$');
+ if (lastIndex != -1 && lastIndex < fqcn.length() - 1) {
+ if (Character.isDigit(fqcn.charAt(lastIndex + 1))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Computes a user-readable type signature from the given class owner, name
* and description. For example, for owner="foo/bar/Foo$Baz", name="foo",
* description="(I)V", it returns "void foo.bar.Foo.Bar#foo(int)".
@@ -443,7 +516,8 @@
* @param fqcn the fully qualified class name
* @return the internal class name
*/
- public static String getInternalName(String fqcn) {
+ @NonNull
+ public static String getInternalName(@NonNull String fqcn) {
String[] parts = fqcn.split("\\."); //$NON-NLS-1$
StringBuilder sb = new StringBuilder();
String prev = null;
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
index 3e49bc4..9f42fb4 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
@@ -21,7 +21,10 @@
import com.android.tools.lint.client.api.Configuration;
import com.google.common.annotations.Beta;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.EnumSet;
+import java.util.List;
/**
@@ -47,6 +50,7 @@
private String mMoreInfoUrl;
private boolean mEnabledByDefault = true;
private final EnumSet<Scope> mScope;
+ private List<EnumSet<Scope>> mAnalysisScopes;
private final Class<? extends Detector> mClass;
// Use factory methods
@@ -244,6 +248,135 @@
}
/**
+ * Returns the sets of scopes required to analyze this issue, or null if all
+ * scopes named by {@link Issue#getScope()} are necessary. Note that only
+ * <b>one</b> match out of this collection is required, not all, and that
+ * the scope set returned by {@link #getScope()} does not have to be returned
+ * by this method, but is always implied to be included.
+ * <p>
+ * The scopes returned by {@link Issue#getScope()} list all the various
+ * scopes that are <b>affected</b> by this issue, meaning the detector
+ * should consider it. Frequently, the detector must analyze all these
+ * scopes in order to properly decide whether an issue is found. For
+ * example, the unused resource detector needs to consider both the XML
+ * resource files and the Java source files in order to decide if a resource
+ * is unused. If it analyzes just the Java files for example, it might
+ * incorrectly conclude that a resource is unused because it did not
+ * discover a resource reference in an XML file.
+ * <p>
+ * However, there are other issues where the issue can occur in a variety of
+ * files, but the detector can consider each in isolation. For example, the
+ * API checker is affected by both XML files and Java class files (detecting
+ * both layout constructor references in XML layout files as well as code
+ * references in .class files). It doesn't have to analyze both; it is
+ * capable of incrementally analyzing just an XML file, or just a class
+ * file, without considering the other.
+ * <p>
+ * The required scope list provides a list of scope sets that can be used to
+ * analyze this issue. For each scope set, all the scopes must be matched by
+ * the incremental analysis, but any one of the scope sets can be analyzed
+ * in isolation.
+ * <p>
+ * The required scope list is not required to include the full scope set
+ * returned by {@link #getScope()}; that set is always assumed to be
+ * included.
+ * <p>
+ * NOTE: You would normally call {@link #isAdequate(EnumSet)} rather
+ * than calling this method directly.
+ *
+ * @return a list of required scopes, or null.
+ */
+ @Nullable
+ public Collection<EnumSet<Scope>> getAnalysisScopes() {
+ return mAnalysisScopes;
+ }
+
+ /**
+ * Sets the collection of scopes that are allowed to be analyzed independently.
+ * See the {@link #getAnalysisScopes()} method for a full explanation.
+ * Note that you usually want to just call {@link #addAnalysisScope(EnumSet)}
+ * instead of constructing a list up front and passing it in here. This
+ * method exists primarily such that commonly used share sets of analysis
+ * scopes can be reused and set directly.
+ *
+ * @param required the collection of scopes
+ * @return this, for constructor chaining
+ */
+ public Issue setAnalysisScopes(@Nullable List<EnumSet<Scope>> required) {
+ mAnalysisScopes = required;
+
+ return this;
+ }
+
+ /**
+ * Returns true if the given scope is adequate for analyzing this issue.
+ * This looks through the analysis scopes (see
+ * {@link #addAnalysisScope(EnumSet)}) and if the scope passed in fully
+ * covers at least one of them, or if it covers the scope of the issue
+ * itself (see {@link #getScope()}, which should be a superset of all the
+ * analysis scopes) returns true.
+ * <p>
+ * The scope set returned by {@link Issue#getScope()} lists all the various
+ * scopes that are <b>affected</b> by this issue, meaning the detector
+ * should consider it. Frequently, the detector must analyze all these
+ * scopes in order to properly decide whether an issue is found. For
+ * example, the unused resource detector needs to consider both the XML
+ * resource files and the Java source files in order to decide if a resource
+ * is unused. If it analyzes just the Java files for example, it might
+ * incorrectly conclude that a resource is unused because it did not
+ * discover a resource reference in an XML file.
+ * <p>
+ * However, there are other issues where the issue can occur in a variety of
+ * files, but the detector can consider each in isolation. For example, the
+ * API checker is affected by both XML files and Java class files (detecting
+ * both layout constructor references in XML layout files as well as code
+ * references in .class files). It doesn't have to analyze both; it is
+ * capable of incrementally analyzing just an XML file, or just a class
+ * file, without considering the other.
+ * <p>
+ * An issue can register additional scope sets that can are adequate
+ * for analyzing the issue, by calling {@link #addAnalysisScope(EnumSet)}.
+ * This method returns true if the given scope matches one or more analysis
+ * scope, or the overall scope.
+ *
+ * @param scope the scope available for analysis
+ * @return true if this issue can be analyzed with the given available scope
+ */
+ public boolean isAdequate(@Nullable EnumSet<Scope> scope) {
+ if (scope.containsAll(mScope)) {
+ return true;
+ }
+
+ if (mAnalysisScopes != null) {
+ for (EnumSet<Scope> analysisScope : mAnalysisScopes) {
+ if (mScope.containsAll(analysisScope)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Adds a scope set that can be analyzed independently to uncover this issue.
+ * See the {@link #getAnalysisScopes()} method for a full explanation.
+ * Note that the {@link #getScope()} does not have to be added here; it is
+ * always considered an analysis scope.
+ *
+ * @param scope the additional scope which can analyze this issue independently
+ * @return this, for constructor chaining
+ */
+ public Issue addAnalysisScope(@Nullable EnumSet<Scope> scope) {
+ if (mAnalysisScopes == null) {
+ mAnalysisScopes = new ArrayList<EnumSet<Scope>>(2);
+ }
+ mAnalysisScopes.add(scope);
+
+ return this;
+ }
+
+ /**
* Returns the class of the detector to use to find this issue
*
* @return the class of the detector to use to find this issue
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
index 4b9da60..a917c11 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
@@ -90,12 +90,19 @@
* @return true if the scope set references a single file
*/
public static boolean checkSingleFile(@NonNull EnumSet<Scope> scopes) {
- return scopes.size() == 1 &&
+ int size = scopes.size();
+ if (size == 2) {
+ // When single checking a Java source file, we check both its Java source
+ // and the associated class files
+ return scopes.contains(JAVA_FILE) && scopes.contains(CLASS_FILE);
+ } else {
+ return size == 1 &&
(scopes.contains(JAVA_FILE)
|| scopes.contains(CLASS_FILE)
|| scopes.contains(RESOURCE_FILE)
|| scopes.contains(PROGUARD_FILE)
|| scopes.contains(MANIFEST));
+ }
}
/**
@@ -123,4 +130,8 @@
public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES);
/** Scope-set used for detectors which are affected by a single Java source file */
public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE);
+ /** Scope-set used for detectors which are affected by a single Java class file */
+ public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE);
+ /** Scope-set used for detectors which are affected by the manifest only */
+ public static final EnumSet<Scope> MANIFEST_SCOPE = EnumSet.of(MANIFEST);
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
index 43ea4ef..43bd7a8 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
@@ -81,7 +81,9 @@
6,
Severity.ERROR,
ApiDetector.class,
- EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE));
+ EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE))
+ .addAnalysisScope(Scope.RESOURCE_FILE_SCOPE)
+ .addAnalysisScope(Scope.CLASS_FILE_SCOPE);
private ApiLookup mApiDatabase;
private int mMinApi = -1;
@@ -295,7 +297,7 @@
"Class requires API level %1$d (current min is %2$d): %3$s",
api, minSdk, fqcn);
AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
- report(context, message, first, method, null, null);
+ report(context, message, first, method, method.name, null);
}
}
}
@@ -458,6 +460,15 @@
private void report(final ClassContext context, String message, AbstractInsnNode node,
MethodNode method, String patternStart, String patternEnd) {
int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
+
+ // If looking for a constructor, the string we'll see in the source is not the
+ // method name (<init>) but the class name
+ if (patternStart != null && patternStart.equals("<init>") //$NON-NLS-1$
+ && node instanceof MethodInsnNode) {
+ String owner = ((MethodInsnNode) node).owner;
+ patternStart = owner.substring(owner.lastIndexOf('/') + 1);
+ }
+
Location location = context.getLocationForLine(lineNumber, patternStart, patternEnd);
context.report(UNSUPPORTED, method, location, message, null);
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index b62f34a..a367cd6 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -53,7 +53,7 @@
private static final List<Issue> sIssues;
static {
- final int initialCapacity = 88;
+ final int initialCapacity = 90;
List<Issue> issues = new ArrayList<Issue>(initialCapacity);
issues.add(AccessibilityDetector.ISSUE);
@@ -85,6 +85,7 @@
issues.add(GridLayoutDetector.ISSUE);
issues.add(OnClickDetector.ISSUE);
issues.add(RegistrationDetector.ISSUE);
+ issues.add(HandlerDetector.ISSUE);
issues.add(TranslationDetector.EXTRA);
issues.add(TranslationDetector.MISSING);
issues.add(HardcodedValuesDetector.ISSUE);
@@ -144,6 +145,7 @@
issues.add(JavaPerformanceDetector.USE_SPARSEARRAY);
issues.add(SetJavaScriptEnabledDetector.ISSUE);
issues.add(ToastDetector.ISSUE);
+ issues.add(SharedPrefsDetector.ISSUE);
assert initialCapacity >= issues.size() : issues.size();
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
index 8bbc749..656c226 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
@@ -38,7 +38,6 @@
import java.io.File;
import java.util.ArrayList;
-import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -63,7 +62,7 @@
4,
Severity.WARNING,
FieldGetterDetector.class,
- EnumSet.of(Scope.CLASS_FILE)).
+ Scope.CLASS_FILE_SCOPE).
// This is a micro-optimization: not enabled by default
setEnabledByDefault(false).setMoreInfo(
"http://developer.android.com/guide/practices/design/performance.html#internal_get_set"); //$NON-NLS-1$
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HandlerDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HandlerDetector.java
new file mode 100644
index 0000000..03a611f
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HandlerDetector.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2012 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.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * Checks that Handler implementations are top level classes or static.
+ * See the corresponding check in the android.os.Handler source code.
+ */
+public class HandlerDetector extends Detector implements ClassScanner {
+
+ /** Potentially leaking handlers */
+ public static final Issue ISSUE = Issue.create(
+ "HandlerLeak", //$NON-NLS-1$
+ "Ensures that Handler classes do not hold on to a reference to an outer class",
+
+ "In Android, Handler classes should be static or leaks might occur. " +
+ "Messages enqueued on the application thread's MessageQueue also retain their " +
+ "target Handler. If the Handler is an inner class, its outer class will be " +
+ "retained as well. To avoid leaking the outer class, declare the Handler as a " +
+ "static nested class with a WeakReference to its outer class.",
+
+ Category.PERFORMANCE,
+ 4,
+ Severity.WARNING,
+ HandlerDetector.class,
+ EnumSet.of(Scope.CLASS_FILE));
+
+ /** Constructs a new {@link HandlerDetector} */
+ public HandlerDetector() {
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public boolean appliesTo(Context context, File file) {
+ return true;
+ }
+
+ // ---- Implements ClassScanner ----
+
+ @Override
+ public void checkClass(ClassContext context, ClassNode classNode) {
+ if (classNode.name.indexOf('$') == -1) {
+ return;
+ }
+
+ // Note: We can't just filter out static inner classes like this:
+ // (classNode.access & Opcodes.ACC_STATIC) != 0
+ // because the static flag only appears on methods and fields in the class
+ // file. Instead, look for the synthetic this pointer.
+
+ LintDriver driver = context.getDriver();
+ String name = classNode.name;
+ while (name != null) {
+ if (name.equals("android/os/Handler")) { //$NON-NLS-1$
+ if (isStaticInnerClass(classNode)) {
+ return;
+ }
+
+ Location location = context.getLocation(classNode);
+ context.report(ISSUE, location, String.format(
+ "This Handler class should be static or leaks might occur (%1$s)",
+ ClassContext.createSignature(classNode.name, null, null)),
+ null);
+ return;
+ }
+ name = driver.getSuperClass(name);
+ }
+ }
+
+ private boolean isStaticInnerClass(@NonNull ClassNode classNode) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List fieldList = classNode.fields;
+ for (Object f : fieldList) {
+ FieldNode field = (FieldNode) f;
+ if (field.name.startsWith("this$") && (field.access & Opcodes.ACC_SYNTHETIC) != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
index 306f546..df8c728 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
@@ -34,7 +34,6 @@
import java.io.File;
import java.util.Collection;
import java.util.Collections;
-import java.util.EnumSet;
/**
* Checks for hardcoded debug mode in manifest files
@@ -59,7 +58,7 @@
5,
Severity.WARNING,
HardcodedDebugModeDetector.class,
- EnumSet.of(Scope.MANIFEST));
+ Scope.MANIFEST_SCOPE);
/** Constructs a new {@link HardcodedDebugModeDetector} check */
public HardcodedDebugModeDetector() {
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
index 42ed1b8..c687d74 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
@@ -648,6 +648,9 @@
float factor = getMdpiScalingFactor(file.getParentFile().getName());
if (factor > 0) {
Dimension size = pixelSizes.get(file);
+ if (size == null) {
+ continue;
+ }
Dimension dip = new Dimension(
Math.round(size.width / factor),
Math.round(size.height / factor));
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
index 04cb9b6..84216d1 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
@@ -40,7 +40,7 @@
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -93,7 +93,7 @@
* Map from element to whether that element has a non-zero linear layout
* weight or has an ancestor which does
*/
- private Map<Node, Boolean> mInsideWeight = new HashMap<Node, Boolean>();
+ private Map<Node, Boolean> mInsideWeight = new IdentityHashMap<Node, Boolean>();
/** Constructs a new {@link InefficientWeightDetector} */
public InefficientWeightDetector() {
@@ -116,7 +116,6 @@
boolean multipleWeights = false;
Element weightChild = null;
boolean checkNesting = context.isEnabled(NESTED_WEIGHTS);
- Node parent = element.getParentNode();
for (Element child : children) {
if (child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) {
if (weightChild != null) {
@@ -127,11 +126,11 @@
}
if (checkNesting) {
- mInsideWeight.put(element, Boolean.TRUE);
+ mInsideWeight.put(child, Boolean.TRUE);
- Boolean inside = mInsideWeight.get(parent);
+ Boolean inside = mInsideWeight.get(element);
if (inside == null) {
- mInsideWeight.put(parent, Boolean.FALSE);
+ mInsideWeight.put(element, Boolean.FALSE);
} else if (inside) {
Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT);
context.report(NESTED_WEIGHTS, sizeNode,
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
index 856bd26..ccf6e98 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
@@ -68,7 +68,7 @@
5,
Severity.WARNING,
ManifestOrderDetector.class,
- EnumSet.of(Scope.MANIFEST));
+ Scope.MANIFEST_SCOPE);
/** Missing a {@code <uses-sdk>} element */
public static final Issue USES_SDK = Issue.create(
@@ -84,7 +84,7 @@
2,
Severity.WARNING,
ManifestOrderDetector.class,
- EnumSet.of(Scope.MANIFEST)).setMoreInfo(
+ Scope.MANIFEST_SCOPE).setMoreInfo(
"http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
/** Missing a {@code <uses-sdk>} element */
@@ -101,7 +101,7 @@
6,
Severity.FATAL,
ManifestOrderDetector.class,
- EnumSet.of(Scope.MANIFEST)).setMoreInfo(
+ Scope.MANIFEST_SCOPE).setMoreInfo(
"http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
/** Missing a {@code <uses-sdk>} element */
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
index 91d2b5a..06258dd 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
@@ -34,7 +34,6 @@
import org.objectweb.asm.tree.MethodNode;
import java.io.File;
-import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -60,7 +59,7 @@
3,
Severity.WARNING,
MathDetector.class,
- EnumSet.of(Scope.CLASS_FILE)).setMoreInfo(
+ Scope.CLASS_FILE_SCOPE).setMoreInfo(
//"http://developer.android.com/reference/android/util/FloatMath.html"); //$NON-NLS-1$
"http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java
index 13b5c83..807d515 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java
@@ -52,7 +52,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
@@ -81,7 +80,7 @@
5,
Severity.WARNING,
SecurityDetector.class,
- EnumSet.of(Scope.MANIFEST));
+ Scope.MANIFEST_SCOPE);
/** Exported content providers */
public static final Issue EXPORTED_PROVIDER = Issue.create(
@@ -96,7 +95,7 @@
5,
Severity.WARNING,
SecurityDetector.class,
- EnumSet.of(Scope.MANIFEST));
+ Scope.MANIFEST_SCOPE);
/** Content provides which grant all URIs access */
public static final Issue OPEN_PROVIDER = Issue.create(
@@ -109,7 +108,7 @@
7,
Severity.WARNING,
SecurityDetector.class,
- EnumSet.of(Scope.MANIFEST));
+ Scope.MANIFEST_SCOPE);
/** Using the world-writable flag */
public static final Issue WORLD_WRITEABLE = Issue.create(
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SharedPrefsDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SharedPrefsDetector.java
new file mode 100644
index 0000000..525ab6d
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SharedPrefsDetector.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Return;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+
+/**
+ * Detector looking for SharedPreferences.edit() calls without a corresponding
+ * commit() or apply() call
+ */
+public class SharedPrefsDetector extends Detector implements Detector.JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "CommitPrefEdits", //$NON-NLS-1$
+ "Looks for code editing a SharedPreference but forgetting to call commit() on it",
+
+ "After calling edit() on a SharedPreference, you must call commit() or apply() on " +
+ "the editor to save the results.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ SharedPrefsDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Constructs a new {@link SharedPrefsDetector} check */
+ public SharedPrefsDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(Context context, File file) {
+ return true;
+ }
+
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList("edit"); //$NON-NLS-1$
+ }
+
+ private MethodDeclaration findSurroundingMethod(Node scope) {
+ while (scope != null) {
+ Class<? extends Node> type = scope.getClass();
+ // The Lombok AST uses a flat hierarchy of node type implementation classes
+ // so no need to do instanceof stuff here.
+ if (type == MethodDeclaration.class) {
+ return (MethodDeclaration) scope;
+ }
+
+ scope = scope.getParent();
+ }
+
+ return null;
+ }
+
+ @Override
+ public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) {
+ assert node.astName().astValue().equals("edit");
+ if (node.astOperand() == null) {
+ return;
+ }
+
+ // Looking for the specific pattern where you assign the edit() result
+ // to a local variable; this means we won't recognize some other usages
+ // of the API (e.g. assigning it to a previously declared variable) but
+ // is needed until we have type attribution in the AST itself.
+ if (!(node.getParent() instanceof VariableDefinitionEntry &&
+ node.getParent().getParent() instanceof VariableDefinition)) {
+ return;
+ }
+ VariableDefinition definition = (VariableDefinition) node.getParent().getParent();
+ String type = definition.astTypeReference().toString();
+ if (!type.endsWith("SharedPreferences.Editor")) { //$NON-NLS-1$
+ return;
+ }
+
+ MethodDeclaration method = findSurroundingMethod(node.getParent());
+ if (method == null) {
+ return;
+ }
+
+ CommitFinder finder = new CommitFinder(node);
+ method.accept(finder);
+ if (!finder.isCommitCalled()) {
+ context.report(ISSUE, method, context.getLocation(node),
+ "SharedPreferences.edit() without a corresponding commit() or apply() call",
+ null);
+ }
+ }
+
+ private class CommitFinder extends ForwardingAstVisitor {
+ /** Whether we've found one of the commit/cancel methods */
+ private boolean mFound;
+ /** The target edit call */
+ private MethodInvocation mTarget;
+ /** Whether we've seen the target edit node yet */
+ private boolean mSeenTarget;
+
+ private CommitFinder(MethodInvocation target) {
+ mTarget = target;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (node == mTarget) {
+ mSeenTarget = true;
+ } else if (mSeenTarget || node.astOperand() == mTarget) {
+ String name = node.astName().astValue();
+ if ("commit".equals(name) || "apply".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
+ // TODO: Do more flow analysis to see whether we're really calling commit/apply
+ // on the right type of object?
+ mFound = true;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean visitReturn(Return node) {
+ if (node.astValue() == mTarget) {
+ // If you just do "return editor.commit() don't warn
+ mFound = true;
+ }
+ return super.visitReturn(node);
+ }
+
+ boolean isCommitCalled() {
+ return mFound;
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ToastDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ToastDetector.java
index c97027f..f199438 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ToastDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ToastDetector.java
@@ -54,7 +54,8 @@
ToastDetector.class,
Scope.JAVA_FILE_SCOPE);
- /** Constructs a new {@link FieldGetterDetector} check */
+
+ /** Constructs a new {@link ToastDetector} check */
public ToastDetector() {
}
@@ -94,6 +95,10 @@
@Override
public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) {
assert node.astName().astValue().equals("makeText");
+ if (node.astOperand() == null) {
+ // "makeText()" in the code with no operand
+ return;
+ }
String operand = node.astOperand().toString();
if (!(operand.equals("Toast") || operand.endsWith(".Toast"))) {
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java
index e5d1e7e..6c3b42a 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java
@@ -31,7 +31,6 @@
import org.objectweb.asm.tree.MethodNode;
import java.io.File;
-import java.util.EnumSet;
import java.util.List;
/**
@@ -65,7 +64,7 @@
3,
Severity.WARNING,
ViewConstructorDetector.class,
- EnumSet.of(Scope.CLASS_FILE));
+ Scope.CLASS_FILE_SCOPE);
/** Constructs a new {@link ViewConstructorDetector} check */
public ViewConstructorDetector() {
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HandlerDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HandlerDetectorTest.java
new file mode 100644
index 0000000..1631a23
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HandlerDetectorTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 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.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class HandlerDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new HandlerDetector();
+ }
+
+ public void testRegistered() throws Exception {
+ assertEquals(
+ "HandlerTest$1.class: Warning: This Handler class should be static or leaks might occur (test.pkg.HandlerTest.1)\n" +
+ "HandlerTest$Inner.class: Warning: This Handler class should be static or leaks might occur (test.pkg.HandlerTest.Inner)",
+
+ lintProject(
+ "bytecode/HandlerTest.java.txt=>src/test/pkg/HandlerTest.java",
+ "bytecode/HandlerTest.class.data=>bin/classes/test/pkg/HandlerTest.class",
+ "bytecode/HandlerTest$Inner.class.data=>bin/classes/test/pkg/HandlerTest$Inner.class",
+ "bytecode/HandlerTest$StaticInner.class.data=>bin/classes/test/pkg/HandlerTest$StaticInner.class",
+ "bytecode/HandlerTest$1.class.data=>bin/classes/test/pkg/HandlerTest$1.class"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
index ed820c6..c48658f 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
@@ -28,16 +28,13 @@
public void testWeights() throws Exception {
assertEquals(
"inefficient_weight.xml:10: Warning: Use a layout_width of 0dip instead of match_parent for better performance\n" +
- "inefficient_weight.xml:24: Warning: Use a layout_height of 0dip instead of wrap_content for better performance\n" +
- "inefficient_weight.xml:25: Warning: Nested weights are bad for performance\n" +
- "inefficient_weight.xml:40: Warning: Nested weights are bad for performance",
+ "inefficient_weight.xml:24: Warning: Use a layout_height of 0dip instead of wrap_content for better performance",
lintFiles("res/layout/inefficient_weight.xml"));
}
public void testWeights2() throws Exception {
assertEquals(
- "nested_weights.xml:22: Warning: Nested weights are bad for performance\n" +
- "nested_weights.xml:28: Warning: Use a layout_width of 0dip instead of match_parent for better performance",
+ "nested_weights.xml:23: Warning: Nested weights are bad for performance",
lintFiles("res/layout/nested_weights.xml"));
}
@@ -68,5 +65,12 @@
lintFiles("res/layout/inefficient_weight2.xml"));
}
+ public void testNestedWeights() throws Exception {
+ // Regression test for http://code.google.com/p/android/issues/detail?id=22889
+ // (Comment 8)
+ assertEquals(
+ "No warnings.",
+ lintFiles("res/layout/nested_weights2.xml"));
+ }
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SharedPrefsDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
new file mode 100644
index 0000000..b13b344
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class SharedPrefsDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new SharedPrefsDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "SharedPrefsTest.java:54: Warning: SharedPreferences.edit() without a " +
+ "corresponding commit() or apply() call",
+
+ lintProject("src/test/pkg/SharedPrefsTest.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest.java"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data
new file mode 100644
index 0000000..ae65532
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data
new file mode 100644
index 0000000..3975896
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data
new file mode 100644
index 0000000..690ee89
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
new file mode 100644
index 0000000..93f9999
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
new file mode 100644
index 0000000..7622c98
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
@@ -0,0 +1,24 @@
+package test.pkg;
+
+import android.os.Handler;
+import android.os.Message;
+
+public class HandlerTest extends Handler { // OK
+ public static class StaticInner extends Handler { // OK
+ public void dispatchMessage(Message msg) {
+ super.dispatchMessage(msg);
+ };
+ }
+ public class Inner extends Handler { // ERROR
+ public void dispatchMessage(Message msg) {
+ super.dispatchMessage(msg);
+ };
+ }
+ void method() {
+ Handler anonymous = new Handler() { // ERROR
+ public void dispatchMessage(Message msg) {
+ super.dispatchMessage(msg);
+ };
+ };
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
index fda1e3e..a375e06 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
@@ -12,6 +12,7 @@
<LinearLayout
android:id="@+id/linearLayout1"
+ android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent" >
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml
new file mode 100644
index 0000000..7002b89
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <ImageView
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/launcher_icon" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:text="test" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_weight="1"
+ android:layout_height="0dp"
+ android:orientation="vertical" >
+ </LinearLayout>
+ </LinearLayout>
+
+</FrameLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt
new file mode 100644
index 0000000..557fc51
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt
@@ -0,0 +1,59 @@
+package foo.bar;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.widget.Toast;
+
+public class SharedPrefsText {
+ // OK 1
+ public void onCreate1(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString("foo", "bar");
+ editor.putInt("bar", 42);
+ editor.commit();
+ }
+
+ // OK 2
+ public void onCreate2(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString("foo", "bar");
+ editor.putInt("bar", 42);
+ if (apply) {
+ editor.apply();
+ }
+ }
+
+ // OK 3
+ public boolean test1(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString("foo", "bar");
+ editor.putInt("bar", 42);
+ return editor.apply();
+ }
+
+ // Not a bug
+ public void test(Foo foo) {
+ Bar bar1 = foo.edit();
+ Bar bar2 = Foo.edit();
+ Bar bar3 = edit();
+ SharedPreferences.Editor editor = preferences.edit(42);
+ apply();
+ }
+
+ // Bug
+ public void bug1(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString("foo", "bar");
+ editor.putInt("bar", 42);
+ }
+ }
+
diff --git a/monitor/Android.mk b/monitor/Android.mk
index a0926a7..2661429 100644
--- a/monitor/Android.mk
+++ b/monitor/Android.mk
@@ -1,7 +1,7 @@
# Copyright 2012 The Android Open Source Project
# Expose the Monitor RCP only for the SDK builds.
-ifneq (,$(is_sdk_build)$(filter sdk,$(TARGET_PRODUCT)))
+ifneq (,$(is_sdk_build)$(filter sdk sdk_x86,$(TARGET_PRODUCT)))
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
diff --git a/monitor/build.xml b/monitor/build.xml
index fabd3ba..d3c2d94 100644
--- a/monitor/build.xml
+++ b/monitor/build.xml
@@ -53,10 +53,11 @@
<!-- create the build directory, copy plugins and features into it -->
<target name="copy_srcs">
<mkdir dir="${buildDir}" />
- <copy todir="${buildDir}">
+ <copy todir="${buildDir}" preservelastmodified="true">
<fileset dir="${srcDir}/">
<include name="plugins/**" />
<include name="features/**" />
+ <exclude name="plugins/*/bin/**" />
</fileset>
</copy>
</target>
diff --git a/sdkmanager/libs/sdklib/.classpath b/sdkmanager/libs/sdklib/.classpath
index 3e6a435..ce6f3f4 100644
--- a/sdkmanager/libs/sdklib/.classpath
+++ b/sdkmanager/libs/sdklib/.classpath
@@ -12,6 +12,6 @@
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpclient-4.1.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-client-4.1.1-src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpcore-4.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-core-4.1-src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpmime-4.1.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-client-4.1.1-src.zip"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/mkidentity/mkidentity-prebuilt.jar"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/mkidentity/mkidentity-prebuilt.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
index 996aee4..38d3bbb 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
@@ -16,6 +16,7 @@
package com.android.sdklib;
+import com.android.annotations.Nullable;
import com.android.sdklib.repository.PkgProps;
import java.util.Properties;
@@ -61,7 +62,7 @@
*/
public AndroidVersion(int apiLevel, String codename) {
mApiLevel = apiLevel;
- mCodename = codename;
+ mCodename = sanitizeCodename(codename);
}
/**
@@ -73,11 +74,12 @@
public AndroidVersion(Properties properties, int defaultApiLevel, String defaultCodeName) {
if (properties == null) {
mApiLevel = defaultApiLevel;
- mCodename = defaultCodeName;
+ mCodename = sanitizeCodename(defaultCodeName);
} else {
mApiLevel = Integer.parseInt(properties.getProperty(PkgProps.VERSION_API_LEVEL,
- Integer.toString(defaultApiLevel)));
- mCodename = properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName);
+ Integer.toString(defaultApiLevel)));
+ mCodename = sanitizeCodename(
+ properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName));
}
}
@@ -95,7 +97,8 @@
if (apiLevel != null) {
try {
mApiLevel = Integer.parseInt(apiLevel);
- mCodename = properties.getProperty(PkgProps.VERSION_CODENAME, null/*defaultValue*/);
+ mCodename = sanitizeCodename(properties.getProperty(PkgProps.VERSION_CODENAME,
+ null/*defaultValue*/));
return;
} catch (NumberFormatException e) {
error = e;
@@ -298,4 +301,25 @@
public boolean isGreaterOrEqualThan(int api) {
return compareTo(api, null /*codename*/) >= 0;
}
+
+ /**
+ * Sanitizes the codename string according to the following rules:
+ * - A codename should be {@code null} for a release version or it should be a non-empty
+ * string for an actual preview.
+ * - In input, spacing is trimmed since it is irrelevant.
+ * - An empty string or the special codename "REL" means a release version
+ * and is converted to {@code null}.
+ *
+ * @param codename A possible-null codename.
+ * @return Null for a release version or a non-empty codename.
+ */
+ private @Nullable String sanitizeCodename(@Nullable String codename) {
+ if (codename != null) {
+ codename = codename.trim();
+ if (codename.length() == 0 || SdkConstants.CODENAME_RELEASE.equals(codename)) {
+ codename = null;
+ }
+ }
+ return codename;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
index 02688c0..ac8de23 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
@@ -53,8 +53,7 @@
*
* @param sdkOsPath the root folder of the SDK
* @param platformOSPath the root folder of the platform component
- * @param apiLevel the API Level
- * @param codeName the codename. can be null.
+ * @param apiVersion the API Level + codename.
* @param versionName the version name of the platform.
* @param revision the revision of the platform component.
* @param layoutlibVersion The {@link LayoutlibVersion}. May be null.
@@ -65,8 +64,7 @@
PlatformTarget(
String sdkOsPath,
String platformOSPath,
- int apiLevel,
- String codeName,
+ AndroidVersion apiVersion,
String versionName,
int revision,
LayoutlibVersion layoutlibVersion,
@@ -77,7 +75,7 @@
}
mRootFolderOsPath = platformOSPath;
mProperties = Collections.unmodifiableMap(properties);
- mVersion = new AndroidVersion(apiLevel, codeName);
+ mVersion = apiVersion;
mVersionName = versionName;
mRevision = revision;
mLayoutlibVersion = layoutlibVersion;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
index 6e6c657..b5ff9da 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
@@ -263,6 +263,9 @@
/** Name of the cache folder in the $HOME/.android. */
public final static String FD_CACHE = "cache"; //$NON-NLS-1$
+ /** API codename of a release (non preview) system image or platform. **/
+ public final static String CODENAME_RELEASE = "REL"; //$NON-NLS-1$
+
/** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
public final static String NS_RESOURCES =
"http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
@@ -377,7 +380,6 @@
FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE_CLANG;
/* Folder paths relative to a addon folder */
-
/** Path of the images directory relative to a folder folder.
* This is an OS path, ending with a separator. */
public final static String OS_ADDON_LIBS_FOLDER = FD_ADDON_LIBS + File.separator;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
index 8284054..5c570ce 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
@@ -437,12 +437,10 @@
}
}
- // codename (optional)
- String apiCodename = platformProp.get(PROP_VERSION_CODENAME);
- if (apiCodename != null && apiCodename.equals("REL")) {
- apiCodename = null; // REL means it's a release version and therefore the
- // codename is irrelevant at this point.
- }
+ // Codename must be either null or a platform codename.
+ // REL means it's a release version and therefore the codename should be null.
+ AndroidVersion apiVersion =
+ new AndroidVersion(apiNumber, platformProp.get(PROP_VERSION_CODENAME));
// version string
String apiName = platformProp.get(PkgProps.PLATFORM_VERSION);
@@ -489,14 +487,13 @@
}
ISystemImage[] systemImages =
- getPlatformSystemImages(sdkOsPath, platformFolder, apiNumber, apiCodename);
+ getPlatformSystemImages(sdkOsPath, platformFolder, apiVersion);
// create the target.
PlatformTarget target = new PlatformTarget(
sdkOsPath,
platformFolder.getAbsolutePath(),
- apiNumber,
- apiCodename,
+ apiVersion,
apiName,
revision,
layoutlibVersion,
@@ -574,16 +571,14 @@
*
* @param sdkOsPath The path to the SDK.
* @param root Root of the platform target being loaded.
- * @param apiNumber API level of platform being loaded
- * @param apiCodename Optional codename of platform being loaded
+ * @param version API level + codename of platform being loaded.
* @return an array of ISystemImage containing all the system images for the target.
* The list can be empty.
*/
private static ISystemImage[] getPlatformSystemImages(
String sdkOsPath,
File root,
- int apiNumber,
- String apiCodename) {
+ AndroidVersion version) {
Set<ISystemImage> found = new TreeSet<ISystemImage>();
Set<String> abiFound = new HashSet<String>();
@@ -592,8 +587,6 @@
// The actual directory names are irrelevant.
// If we find multiple occurrences of the same platform/abi, the first one read wins.
- AndroidVersion version = new AndroidVersion(apiNumber, apiCodename);
-
File[] firstLevelFiles = new File(sdkOsPath, SdkConstants.FD_SYSTEM_IMAGES).listFiles();
if (firstLevelFiles != null) {
for (File firstLevel : firstLevelFiles) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
index 039e165..591e447 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
@@ -71,7 +71,7 @@
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
*/
- private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG_CACHE") != null;
+ private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG_CACHE") != null; //$NON-NLS-1$
/** Key for the Status-Code in the info properties. */
private static final String KEY_STATUS_CODE = "Status-Code"; //$NON-NLS-1$
@@ -134,6 +134,12 @@
public enum Strategy {
/**
+ * Exclusively serves data from the cache. If files are available in the
+ * cache, serve them as is (without trying to refresh them). If files are
+ * not available, they are <em>not</em> fetched at all.
+ */
+ ONLY_CACHE,
+ /**
* If the files are available in the cache, serve them as-is, otherwise
* download them and return the cached version. No expiration or refresh
* is attempted if a file is in the cache.
@@ -156,6 +162,12 @@
/** Creates a default instance of the URL cache */
public DownloadCache(Strategy strategy) {
mCacheRoot = initCacheRoot();
+
+ // If this is defined in the environment, never use the cache. Useful for testing.
+ if (System.getenv("SDKMAN_DISABLE_CACHE") != null) { //$NON-NLS-1$
+ strategy = Strategy.DIRECT;
+ }
+
mStrategy = mCacheRoot == null ? Strategy.DIRECT : strategy;
}
@@ -231,6 +243,7 @@
* @param monitor {@link ITaskMonitor} which is related to this URL
* fetching.
* @return Returns an {@link InputStream} holding the URL content.
+ * Returns null if the document is not cached and strategy is {@link Strategy#ONLY_CACHE}.
* @throws IOException Exception thrown when there are problems retrieving
* the URL or its content.
* @throws CanceledByUserException Exception thrown if the user cancels the
@@ -408,6 +421,14 @@
} catch (IOException ignore) {}
}
+ if (!useCached && mStrategy == Strategy.ONLY_CACHE) {
+ // We don't have a document to serve from the cache.
+ if (DEBUG) {
+ System.out.println(String.format("%s : file not in cache", urlString)); //$NON-NLS-1$
+ }
+ return null;
+ }
+
// If we're not using the cache, try to remove the cache and download again.
try {
cached.delete();
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
index fb59b42..dfd197d 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
@@ -16,6 +16,7 @@
package com.android.sdklib.internal.repository;
+
/**
* A factory that can start and run new {@link ITask}s.
*/
@@ -23,6 +24,12 @@
/**
* Starts a new task with a new {@link ITaskMonitor}.
+ * <p/>
+ * The task will execute in a thread and runs it own UI loop.
+ * This means the task can perform UI operations using
+ * {@code Display#asyncExec(Runnable)}.
+ * <p/>
+ * In either case, the method only returns when the task has finished.
*
* @param title The title of the task, displayed in the monitor if any.
* @param task The task to run.
@@ -36,6 +43,13 @@
* and give the sub-monitor to the new task with the number of work units you want
* it to fill. The {@link #start} method will make sure to <em>fill</em> the progress
* when the task is completed, in case the actual task did not.
+ * <p/>
+ * When a task is started from within a monitor, it reuses the thread
+ * from the parent. Otherwise it starts a new thread and runs it own
+ * UI loop. This means the task can perform UI operations using
+ * {@code Display#asyncExec(Runnable)}.
+ * <p/>
+ * In either case, the method only returns when the task has finished.
*
* @param title The title of the task, displayed in the monitor if any.
* @param parentMonitor The parent monitor. Can be null.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java
deleted file mode 100755
index e4f2419..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.sdklib.internal.repository;
-
-import org.w3c.dom.Node;
-
-/**
- * Misc utilities to help extracting elements and attributes out of an XML document.
- */
-public class XmlParserUtils {
-
- /**
- * Returns the first child element with the given XML local name.
- * If xmlLocalName is null, returns the very first child element.
- */
- public static Node getFirstChild(Node node, String xmlLocalName) {
-
- String nsUri = node.getNamespaceURI();
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {
- return child;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Retrieves the value of that XML element as a string.
- * Returns an empty string whether the element is missing or empty,
- * so you can't tell the difference.
- * <p/>
- * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the
- * element is missing versus empty.
- *
- * @param node The XML <em>parent</em> node to parse.
- * @param xmlLocalName The XML local name to find in the parent node.
- * @return The text content of the element. Returns an empty string whether the element
- * is missing or empty, so you can't tell the difference.
- */
- public static String getXmlString(Node node, String xmlLocalName) {
- Node child = getFirstChild(node, xmlLocalName);
-
- return child == null ? "" : child.getTextContent(); //$NON-NLS-1$
- }
-
- /**
- * Retrieves the value of that XML element as a string.
- * Returns null when the element is missing, so you can tell between a missing element
- * and an empty one.
- * <p/>
- * Note: use {@link #getXmlString(Node, String)} if you don't need to know when the
- * element is missing versus empty.
- *
- * @param node The XML <em>parent</em> node to parse.
- * @param xmlLocalName The XML local name to find in the parent node.
- * @return The text content of the element. Returns null when the element is missing.
- * Returns an empty string whether the element is present but empty.
- */
- public static String getOptionalXmlString(Node node, String xmlLocalName) {
- Node child = getFirstChild(node, xmlLocalName);
-
- return child == null ? null : child.getTextContent(); //$NON-NLS-1$
- }
-
- /**
- * Retrieves the value of that XML element as an integer.
- * Returns the default value when the element is missing or is not an integer.
- */
- public static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {
- String s = getXmlString(node, xmlLocalName);
- try {
- return Integer.parseInt(s);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- /**
- * Retrieves the value of that XML element as a long.
- * Returns the default value when the element is missing or is not an integer.
- */
- public static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {
- String s = getXmlString(node, xmlLocalName);
- try {
- return Long.parseLong(s);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- /**
- * Retrieve an attribute which value must match one of the given enums using a
- * case-insensitive name match.
- *
- * Returns defaultValue if the attribute does not exist or its value does not match
- * the given enum values.
- */
- public static Object getEnumAttribute(
- Node archiveNode,
- String attrName,
- Object[] values,
- Object defaultValue) {
-
- Node attr = archiveNode.getAttributes().getNamedItem(attrName);
- if (attr != null) {
- String found = attr.getNodeValue();
- for (Object value : values) {
- if (value.toString().equalsIgnoreCase(found)) {
- return value;
- }
- }
- }
-
- return defaultValue;
- }
-
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
index cc35e54..fff45b9 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
@@ -25,7 +25,6 @@
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
@@ -46,8 +45,9 @@
/**
* Represents an add-on XML node in an SDK repository.
*/
-public class AddonPackage extends Package
- implements IPackageVersion, IPlatformDependency, IExactApiLevelDependency, ILayoutlibVersion {
+public class AddonPackage extends MajorRevisionPackage
+ implements IAndroidVersionProvider, IPlatformDependency,
+ IExactApiLevelDependency, ILayoutlibVersion {
private final String mVendorId;
private final String mVendorDisplay;
@@ -142,12 +142,12 @@
// that only provide name and vendor. If the addon provides neither set of fields,
// it will simply not work as expected.
- String nameId = XmlParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME_ID);
- String nameDisp = XmlParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME_DISPLAY);
- String name = XmlParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME);
+ String nameId = PackageParserUtils.getXmlString(packageNode,
+ SdkRepoConstants.NODE_NAME_ID);
+ String nameDisp = PackageParserUtils.getXmlString(packageNode,
+ SdkRepoConstants.NODE_NAME_DISPLAY);
+ String name = PackageParserUtils.getXmlString(packageNode,
+ SdkRepoConstants.NODE_NAME);
// The old <name> is equivalent to the new <name-display>
if (nameDisp.length() == 0) {
@@ -168,12 +168,12 @@
// --- vendor id/display ---
// Same processing for vendor id vs display
- String vendorId = XmlParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR_ID);
- String vendorDisp = XmlParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR_DISPLAY);
- String vendor = XmlParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR);
+ String vendorId = PackageParserUtils.getXmlString(packageNode,
+ SdkAddonConstants.NODE_VENDOR_ID);
+ String vendorDisp = PackageParserUtils.getXmlString(packageNode,
+ SdkAddonConstants.NODE_VENDOR_DISPLAY);
+ String vendor = PackageParserUtils.getXmlString(packageNode,
+ SdkAddonConstants.NODE_VENDOR);
// The old <vendor> is equivalent to the new <vendor-display>
if (vendorDisp.length() == 0) {
@@ -194,10 +194,12 @@
// --- other attributes
- int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkAddonConstants.NODE_API_LEVEL, 0);
+ int apiLevel =
+ PackageParserUtils.getXmlInt(packageNode, SdkAddonConstants.NODE_API_LEVEL, 0);
mVersion = new AndroidVersion(apiLevel, null /*codeName*/);
- mLibs = parseLibs(XmlParserUtils.getFirstChild(packageNode, SdkAddonConstants.NODE_LIBS));
+ mLibs = parseLibs(
+ PackageParserUtils.findChildElement(packageNode, SdkAddonConstants.NODE_LIBS));
mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
}
@@ -399,8 +401,8 @@
* Parses a <lib> element from a <libs> container.
*/
private Lib parseLib(Node libNode) {
- return new Lib(XmlParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_NAME),
- XmlParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_DESCRIPTION));
+ return new Lib(PackageParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_NAME),
+ PackageParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_DESCRIPTION));
}
/** Returns the vendor id, a string, for add-on packages. */
@@ -428,8 +430,8 @@
* <p/>
* An add-on has the same {@link AndroidVersion} as the platform it depends on.
*/
- @Override
- public @NonNull AndroidVersion getVersion() {
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
return mVersion;
}
@@ -601,7 +603,7 @@
// check they are the same add-on.
if (getNameId().equals(newPkg.getNameId()) &&
- getVersion().equals(newPkg.getVersion())) {
+ getAndroidVersion().equals(newPkg.getAndroidVersion())) {
// Check the vendor-id field.
if (getVendorId().equals(newPkg.getVendorId())) {
return true;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
index 6a7e9c6..c84c259 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
@@ -30,7 +30,7 @@
* Represents an SDK repository package that is incomplete.
* It has a distinct icon and a specific error that is supposed to help the user on how to fix it.
*/
-public class BrokenPackage extends Package
+public class BrokenPackage extends MajorRevisionPackage
implements IExactApiLevelDependency, IMinApiLevelDependency {
/**
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
index 54dfc5e..196f351 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
@@ -16,11 +16,11 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
@@ -35,11 +35,11 @@
/**
* Represents a doc XML node in an SDK repository.
* <p/>
- * Note that a doc package has a version and thus implements {@link IPackageVersion}.
+ * Note that a doc package has a version and thus implements {@link IAndroidVersionProvider}.
* However there is no mandatory dependency that limits installation so this does not
* implement {@link IPlatformDependency}.
*/
-public class DocPackage extends Package implements IPackageVersion {
+public class DocPackage extends MajorRevisionPackage implements IAndroidVersionProvider {
private final AndroidVersion mVersion;
@@ -53,11 +53,16 @@
* parameters that vary according to the originating XML schema.
* @param licenses The licenses loaded from the XML originating document.
*/
- public DocPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
+ public DocPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- int apiLevel = XmlParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ int apiLevel =
+ PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
if (codeName.length() == 0) {
codeName = null;
}
@@ -124,8 +129,8 @@
* Returns the version, for platform, add-on and doc packages.
* Can be 0 if this is a local package of unknown api-level.
*/
- @Override
- public AndroidVersion getVersion() {
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
return mVersion;
}
@@ -166,12 +171,12 @@
if (mVersion.isPreview()) {
return String.format("Documentation for Android '%1$s' Preview SDK, revision %2$s%3$s",
mVersion.getCodename(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
} else {
return String.format("Documentation for Android SDK, API %1$d, revision %2$s%3$s",
mVersion.getApiLevel(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
}
@@ -190,8 +195,8 @@
}
if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -220,8 +225,8 @@
@Override
public boolean sameItemAs(Package pkg) {
if (pkg instanceof DocPackage) {
- AndroidVersion rev2 = ((DocPackage) pkg).getVersion();
- return this.getVersion().equals(rev2);
+ AndroidVersion rev2 = ((DocPackage) pkg).getAndroidVersion();
+ return this.getAndroidVersion().equals(rev2);
}
return false;
@@ -248,12 +253,12 @@
DocPackage replacementDoc = (DocPackage)replacementPackage;
- AndroidVersion replacementVersion = replacementDoc.getVersion();
+ AndroidVersion replacementVersion = replacementDoc.getAndroidVersion();
// Check if they're the same exact (api and codename)
if (replacementVersion.equals(mVersion)) {
// exact same version, so check the revision level
- if (replacementPackage.getRevision() > this.getRevision()) {
+ if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
return UpdateInfo.UPDATE;
}
} else {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
index cc27853..387ef98 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
@@ -25,7 +25,6 @@
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.LocalSdkParser;
import com.android.sdklib.internal.repository.NullTaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
@@ -103,22 +102,25 @@
Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- mPath = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_PATH);
+ mPath = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_PATH);
// Read name-display, vendor-display and vendor-id, introduced in addon-4.xsd.
// These are not optional, they are mandatory in addon-4 but we still treat them
// as optional so that we can fallback on using <vendor> which was the only one
// defined in addon-3.xsd.
- String name = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_NAME_DISPLAY);
- String vname = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_DISPLAY);
- String vid = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_ID);
+ String name =
+ PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_NAME_DISPLAY);
+ String vname =
+ PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_DISPLAY);
+ String vid =
+ PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_ID);
if (vid.length() == 0) {
// If vid is missing, use the old <vendor> attribute.
// Note that in a valid XML, vendor-id cannot be an empty string.
// The only reason vid can be empty is when <vendor-id> is missing, which
// happens in an addon-3 schema, in which case the old <vendor> needs to be used.
- String vendor = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR);
+ String vendor = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR);
vid = sanitizeLegacyVendor(vendor);
if (vname.length() == 0) {
vname = vendor;
@@ -137,13 +139,13 @@
}
mDisplayName = name.trim();
- mMinApiLevel = XmlParserUtils.getXmlInt(
+ mMinApiLevel = PackageParserUtils.getXmlInt(
packageNode, RepoConstants.NODE_MIN_API_LEVEL, MIN_API_LEVEL_NOT_SPECIFIED);
mProjectFiles = parseProjectFiles(
- XmlParserUtils.getFirstChild(packageNode, RepoConstants.NODE_PROJECT_FILES));
+ PackageParserUtils.findChildElement(packageNode, RepoConstants.NODE_PROJECT_FILES));
- mOldPaths = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_OLD_PATHS);
+ mOldPaths = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_OLD_PATHS);
}
private String[] parseProjectFiles(Node projectFilesNode) {
@@ -254,10 +256,8 @@
mOldPaths = getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);
- mMinApiLevel = Integer.parseInt(
- getProperty(props,
- PkgProps.EXTRA_MIN_API_LEVEL,
- Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));
+ mMinApiLevel = getPropertyInt(props, PkgProps.EXTRA_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
String projectFiles = getProperty(props, PkgProps.EXTRA_PROJECT_FILES, null);
ArrayList<String> filePaths = new ArrayList<String>();
@@ -490,9 +490,9 @@
*/
@Override
public String getShortDescription() {
- String s = String.format("%1$s, revision %2$d%3$s",
+ String s = String.format("%1$s, revision %2$s%3$s",
getDisplayName(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
return s;
@@ -506,9 +506,9 @@
*/
@Override
public String getLongDescription() {
- String s = String.format("%1$s, revision %2$d%3$s\nBy %4$s",
+ String s = String.format("%1$s, revision %2$s%3$s\nBy %4$s",
getDisplayName(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "", //$NON-NLS-2$
getVendorDisplay());
@@ -517,8 +517,9 @@
s += '\n' + d;
}
- if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
- s += String.format("\nRequires tools revision %1$d", getMinToolsRevision());
+ if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {
+ s += String.format("\nRequires tools revision %1$s",
+ getMinToolsRevision().toShortString());
}
if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java
new file mode 100755
index 0000000..40235c2
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2012 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.sdklib.internal.repository.packages;
+
+import com.android.annotations.NonNull;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Package multi-part revision number composed of a tuple
+ * (major.minor.micro) and an optional preview revision
+ * (the lack of a preview number indicates it's not a preview
+ * but a final package.)
+ *
+ * @see MajorRevision
+ */
+public class FullRevision implements Comparable<FullRevision> {
+
+ public static final int MISSING_MAJOR_REV = 0;
+ public static final int IMPLICIT_MINOR_REV = 0;
+ public static final int IMPLICIT_MICRO_REV = 0;
+ public static final int NOT_A_PREVIEW = 0;
+
+ private final static Pattern FULL_REVISION_PATTERN =
+ // 1=major 2=minor 3=micro 4=preview
+ Pattern.compile("\\s*([0-9]+)(?:\\.([0-9]+)(?:\\.([0-9]+))?)?\\s*(?:rc([0-9]+))?\\s*");
+
+ private final int mMajor;
+ private final int mMinor;
+ private final int mMicro;
+ private final int mPreview;
+
+ public FullRevision(int major) {
+ this(major, 0, 0);
+ }
+
+ public FullRevision(int major, int minor, int micro) {
+ this(major, minor, micro, NOT_A_PREVIEW);
+ }
+
+ public FullRevision(int major, int minor, int micro, int preview) {
+ mMajor = major;
+ mMinor = minor;
+ mMicro = micro;
+ mPreview = preview;
+ }
+
+ public int getMajor() {
+ return mMajor;
+ }
+
+ public int getMinor() {
+ return mMinor;
+ }
+
+ public int getMicro() {
+ return mMicro;
+ }
+
+ public boolean isPreview() {
+ return mPreview > NOT_A_PREVIEW;
+ }
+
+ public int getPreview() {
+ return mPreview;
+ }
+
+ /**
+ * Parses a string of format "major.minor.micro rcPreview" and returns
+ * a new {@link FullRevision} for it. All the fields except major are
+ * optional.
+ * <p/>
+ * The parsing is equivalent to the pseudo-BNF/regexp:
+ * <pre>
+ * Major/Minor/Micro/Preview := [0-9]+
+ * Revision := Major ('.' Minor ('.' Micro)? )? \s* ('rc'Preview)?
+ * </pre>
+ *
+ * @param revision A non-null revision to parse.
+ * @return A new non-null {@link FullRevision}.
+ * @throws NumberFormatException if the parsing failed.
+ */
+ public static @NonNull FullRevision parseRevision(@NonNull String revision)
+ throws NumberFormatException {
+
+ if (revision == null) {
+ throw new NumberFormatException("revision is <null>"); //$NON-NLS-1$
+ }
+
+ Throwable cause = null;
+ try {
+ Matcher m = FULL_REVISION_PATTERN.matcher(revision);
+ if (m != null && m.matches()) {
+ int major = Integer.parseInt(m.group(1));
+ String s = m.group(2);
+ int minor = s == null ? IMPLICIT_MINOR_REV : Integer.parseInt(s);
+ s = m.group(3);
+ int micro = s == null ? IMPLICIT_MICRO_REV : Integer.parseInt(s);
+ s = m.group(4);
+ int preview = s == null ? NOT_A_PREVIEW : Integer.parseInt(s);
+
+ return new FullRevision(major, minor, micro, preview);
+ }
+ } catch (Throwable t) {
+ cause = t;
+ }
+
+ NumberFormatException n = new NumberFormatException(
+ "Invalid full revision: " + revision); //$NON-NLS-1$
+ n.initCause(cause);
+ throw n;
+ }
+
+ /**
+ * Returns the version in a fixed format major.minor.micro
+ * with an optional "rc preview#". For example it would
+ * return "18.0.0", "18.1.0" or "18.1.2 rc5".
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mMajor)
+ .append('.').append(mMinor)
+ .append('.').append(mMicro);
+
+ if (mPreview != NOT_A_PREVIEW) {
+ sb.append(" rc").append(mPreview);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the version in a dynamic format "major.minor.micro rc#".
+ * This is similar to {@link #toString()} except it omits minor, micro
+ * or preview versions when they are zero.
+ * For example it would return "18 rc1" instead of "18.0.0 rc1",
+ * or "18.1 rc2" instead of "18.1.0 rc2".
+ */
+ public String toShortString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mMajor);
+ if (mMinor > 0 || mMicro > 0) {
+ sb.append('.').append(mMinor);
+ }
+ if (mMicro > 0) {
+ sb.append('.').append(mMicro);
+ }
+ if (mPreview != NOT_A_PREVIEW) {
+ sb.append(" rc").append(mPreview);
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mMajor;
+ result = prime * result + mMinor;
+ result = prime * result + mMicro;
+ result = prime * result + mPreview;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) {
+ return true;
+ }
+ if (rhs == null) {
+ return false;
+ }
+ if (!(rhs instanceof FullRevision)) {
+ return false;
+ }
+ FullRevision other = (FullRevision) rhs;
+ if (mMajor != other.mMajor) {
+ return false;
+ }
+ if (mMinor != other.mMinor) {
+ return false;
+ }
+ if (mMicro != other.mMicro) {
+ return false;
+ }
+ if (mPreview != other.mPreview) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Trivial comparison of a version, e.g 17.1.2 < 18.0.0.
+ *
+ * Note that preview/release candidate are released before their final version,
+ * so "18.0.0 rc1" comes below "18.0.0". The best way to think of it as if the
+ * lack of preview number was "+inf":
+ * "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0"
+ * and more than "18.1.2.4"
+ */
+ @Override
+ public int compareTo(FullRevision rhs) {
+ int delta = mMajor - rhs.mMajor;
+ if (delta != 0) {
+ return delta;
+ }
+
+ delta = mMinor - rhs.mMinor;
+ if (delta != 0) {
+ return delta;
+ }
+
+ delta = mMicro - rhs.mMicro;
+ if (delta != 0) {
+ return delta;
+ }
+
+ int p1 = mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : mPreview;
+ int p2 = rhs.mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : rhs.mPreview;
+ delta = p1 - p2;
+ return delta;
+ }
+
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
new file mode 100755
index 0000000..d7b5429
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2012 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.sdklib.internal.repository.packages;
+
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a package in an SDK repository that has a {@link FullRevision},
+ * which is a multi-part revision number (major.minor.micro) and an optional preview revision.
+ */
+public abstract class FullRevisionPackage extends Package
+ implements IFullRevisionProvider {
+
+ private final FullRevision mPreviewVersion;
+
+ /**
+ * Creates a new package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ FullRevisionPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mPreviewVersion = PackageParserUtils.parseFullRevisionElement(
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_REVISION));
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * Properties from props are used first when possible, e.g. if props is non null.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public FullRevisionPackage(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source, props, revision, license, description, descUrl,
+ archiveOs, archiveArch, archiveOsPath);
+
+ String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+
+ FullRevision rev = null;
+ if (revStr != null) {
+ try {
+ rev = FullRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+ if (rev == null) {
+ rev = new FullRevision(revision);
+ }
+
+ mPreviewVersion = rev;
+ }
+
+ @Override
+ public FullRevision getRevision() {
+ return mPreviewVersion;
+ }
+
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+ props.setProperty(PkgProps.PKG_REVISION, mPreviewVersion.toShortString());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mPreviewVersion == null) ? 0 : mPreviewVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof FullRevisionPackage)) {
+ return false;
+ }
+ FullRevisionPackage other = (FullRevisionPackage) obj;
+ if (mPreviewVersion == null) {
+ if (other.mPreviewVersion != null) {
+ return false;
+ }
+ } else if (!mPreviewVersion.equals(other.mPreviewVersion)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Computes whether the given package is a suitable update for the current package.
+ * <p/>
+ * A specific case here is that a release package can update a preview, whereas
+ * a preview can only update another preview.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
+ if (replacementPackage == null) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check they are the same item, ignoring the preview bit.
+ if (!sameItemAs(replacementPackage, true /*ignorePreviews*/)) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // a preview cannot update a non-preview
+ if (!getRevision().isPreview() && replacementPackage.getRevision().isPreview()) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check revision number
+ if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
+ return UpdateInfo.UPDATE;
+ }
+
+ // not an upgrade but not incompatible either.
+ return UpdateInfo.NOT_UPDATE;
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
similarity index 84%
rename from sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java
rename to sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
index 77a6a1d..e3ab5bd 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
@@ -16,6 +16,7 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
import com.android.sdklib.AndroidVersion;
/**
@@ -26,11 +27,11 @@
* requested platform is present, whereas this interface denotes that the given package simply
* has a version, which is not necessarily a dependency.
*/
-public interface IPackageVersion {
+public interface IAndroidVersionProvider {
/**
- * Returns the version, for platform, add-on and doc packages.
+ * Returns the android version, for platform, add-on and doc packages.
* Can be 0 if this is a local package of unknown api-level.
*/
- public abstract AndroidVersion getVersion();
+ public abstract @NonNull AndroidVersion getAndroidVersion();
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java
new file mode 100755
index 0000000..497b2f8
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.sdklib.internal.repository.packages;
+
+
+
+/**
+ * Interface for packages that provide a {@link FullRevision},
+ * which is a multi-part revision number (major.minor.micro) and an optional preview revision.
+ * <p/>
+ * This interface is a tag. It indicates that {@link Package#getRevision()} returns a
+ * {@link FullRevision} instead of a limited {@link MajorRevision}. <br/>
+ * The preview version number is available via {@link Package#getRevision()}.
+ */
+public interface IFullRevisionProvider {
+
+ /**
+ * Returns whether the give package represents the same item as the current package.
+ * <p/>
+ * Two packages are considered the same if they represent the same thing, except for the
+ * revision number.
+ * @param pkg the package to compare
+ * @param ignorePreviews true if 2 packages should be considered the same even if one
+ * is a preview and the other one is not.
+ * @return true if the item are the same.
+ */
+ public abstract boolean sameItemAs(Package pkg, boolean ignorePreviews);
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
index 32468a4..d17b800 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
@@ -34,7 +34,8 @@
* Since this is a required attribute in the XML schema, it can only happen when dealing
* with an invalid repository XML.
*/
- public static final int MIN_PLATFORM_TOOLS_REV_INVALID = 0;
+ public static final FullRevision MIN_PLATFORM_TOOLS_REV_INVALID =
+ new FullRevision(FullRevision.MISSING_MAJOR_REV);
/**
* The minimal revision of the tools package required by this package if > 0,
@@ -43,6 +44,6 @@
* This attribute is mandatory and should not be normally missing.
* It can only happen when dealing with an invalid repository XML.
*/
- public abstract int getMinPlatformToolsRevision();
+ public abstract FullRevision getMinPlatformToolsRevision();
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
index 76cdd66..064f1d3 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
@@ -31,12 +31,12 @@
* The value of {@link #getMinToolsRevision()} when the
* {@link SdkRepoConstants#NODE_MIN_TOOLS_REV} was not specified in the XML source.
*/
- public static final int MIN_TOOLS_REV_NOT_SPECIFIED = 0;
+ public static final FullRevision MIN_TOOLS_REV_NOT_SPECIFIED =
+ new FullRevision(FullRevision.MISSING_MAJOR_REV);
/**
* The minimal revision of the tools package required by this extra package if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
- public abstract int getMinToolsRevision();
-
+ public abstract FullRevision getMinToolsRevision();
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
index a61fbea..9665528 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
@@ -25,13 +25,13 @@
* A package that has this dependency can only be installed if a platform with at least the
* requested API level is present or installed at the same time.
* <p/>
- * Note that although this interface looks like {@link IPackageVersion}, it does not convey
- * the same semantic, that is {@link IPackageVersion} does <em>not</em> imply any dependency being
- * a limiting factor as far as installation is concerned.
+ * Note that although this interface looks like {@link IAndroidVersionProvider}, it does
+ * not convey the same semantic since {@link IAndroidVersionProvider} does <em>not</em>
+ * imply any dependency being a limiting factor as far as installation is concerned.
*/
public interface IPlatformDependency {
/** Returns the version of the platform dependency of this package. */
- AndroidVersion getVersion();
+ AndroidVersion getAndroidVersion();
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
index 5b582c1..2713fe7 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
@@ -16,7 +16,6 @@
package com.android.sdklib.internal.repository.packages;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.RepoConstants;
import com.android.util.Pair;
@@ -51,11 +50,12 @@
int api = LAYOUTLIB_API_NOT_SPECIFIED;
int rev = LAYOUTLIB_REV_NOT_SPECIFIED;
- Node layoutlibNode = XmlParserUtils.getFirstChild(pkgNode, RepoConstants.NODE_LAYOUT_LIB);
+ Node layoutlibNode =
+ PackageParserUtils.findChildElement(pkgNode, RepoConstants.NODE_LAYOUT_LIB);
if (layoutlibNode != null) {
- api = XmlParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_API, 0);
- rev = XmlParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_REVISION, 0);
+ api = PackageParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_API, 0);
+ rev = PackageParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_REVISION, 0);
}
mLayoutlibVersion = Pair.of(api, rev);
@@ -65,12 +65,10 @@
* Parses the layoutlib version optionally available in the given {@link Properties}.
*/
public LayoutlibVersionMixin(Properties props) {
- int layoutlibApi = Integer.parseInt(
- Package.getProperty(props, PkgProps.LAYOUTLIB_API,
- Integer.toString(LAYOUTLIB_API_NOT_SPECIFIED)));
- int layoutlibRev = Integer.parseInt(
- Package.getProperty(props, PkgProps.LAYOUTLIB_REV,
- Integer.toString(LAYOUTLIB_REV_NOT_SPECIFIED)));
+ int layoutlibApi = Package.getPropertyInt(props, PkgProps.LAYOUTLIB_API,
+ LAYOUTLIB_API_NOT_SPECIFIED);
+ int layoutlibRev = Package.getPropertyInt(props, PkgProps.LAYOUTLIB_REV,
+ LAYOUTLIB_REV_NOT_SPECIFIED);
mLayoutlibVersion = Pair.of(layoutlibApi, layoutlibRev);
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java
new file mode 100755
index 0000000..9ca9e22
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 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.sdklib.internal.repository.packages;
+
+import com.android.annotations.NonNull;
+
+
+/**
+ * Package revision number composed of a <em>single</em> major revision.
+ * <p/>
+ * Contrary to a {@link FullRevision}, a {@link MajorRevision} does not
+ * provide minor, micro and preview revision numbers -- these are all
+ * set to zero.
+ */
+public class MajorRevision extends FullRevision {
+
+ public MajorRevision(int major) {
+ super(major, 0, 0);
+ }
+
+ @Override
+ public String toString() {
+ return super.toShortString();
+ }
+
+ /**
+ * Parses a single-integer string and returns a new {@link MajorRevision} for it.
+ *
+ * @param revision A non-null revision to parse.
+ * @return A new non-null {@link MajorRevision}.
+ * @throws NumberFormatException if the parsing failed.
+ */
+ public static @NonNull MajorRevision parseRevision(@NonNull String revision)
+ throws NumberFormatException {
+
+ if (revision == null) {
+ throw new NumberFormatException("revision is <null>"); //$NON-NLS-1$
+ }
+
+ return new MajorRevision(Integer.parseInt(revision.trim()));
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
new file mode 100755
index 0000000..1348bb9
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012 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.sdklib.internal.repository.packages;
+
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a package in an SDK repository that has a {@link MajorRevision},
+ * which is a single major revision number (not minor, micro or previews).
+ */
+public abstract class MajorRevisionPackage extends Package {
+
+ private final MajorRevision mRevision;
+
+ /**
+ * Creates a new package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ MajorRevisionPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mRevision = new MajorRevision(
+ PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_REVISION, 0));
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * Properties from props are used first when possible, e.g. if props is non null.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MajorRevisionPackage(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source, props, revision, license, description, descUrl,
+ archiveOs, archiveArch, archiveOsPath);
+
+ String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+
+ MajorRevision rev = null;
+ if (revStr != null) {
+ try {
+ rev = MajorRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+ if (rev == null) {
+ rev = new MajorRevision(revision);
+ }
+
+ mRevision = rev;
+ }
+
+ /**
+ * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
+ * Can be 0 if this is a local package of unknown revision.
+ */
+ @Override
+ public FullRevision getRevision() {
+ return mRevision;
+ }
+
+
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+ props.setProperty(PkgProps.PKG_REVISION, mRevision.toString());
+ }
+
+ @Override
+ public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
+ if (replacementPackage == null) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check they are the same item.
+ if (!sameItemAs(replacementPackage)) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check revision number
+ if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
+ return UpdateInfo.UPDATE;
+ }
+
+ // not an upgrade but not incompatible either.
+ return UpdateInfo.NOT_UPDATE;
+ }
+
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
index 99602c8..12c423e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
@@ -16,7 +16,6 @@
package com.android.sdklib.internal.repository.packages;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
@@ -31,13 +30,13 @@
/**
* Represents an XML node in an SDK repository that has a min-tools-rev requirement.
*/
-public abstract class MinToolsPackage extends Package implements IMinToolsDependency {
+public abstract class MinToolsPackage extends MajorRevisionPackage implements IMinToolsDependency {
/**
* The minimal revision of the tools package required by this extra package, if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
- private final int mMinToolsRevision;
+ private final FullRevision mMinToolsRevision;
/**
* Creates a new package from the attributes and elements of the given XML node.
@@ -52,9 +51,8 @@
MinToolsPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- mMinToolsRevision = XmlParserUtils.getXmlInt(packageNode,
- SdkRepoConstants.NODE_MIN_TOOLS_REV,
- MIN_TOOLS_REV_NOT_SPECIFIED);
+ mMinToolsRevision = PackageParserUtils.parseFullRevisionElement(
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_MIN_TOOLS_REV));
}
/**
@@ -79,10 +77,16 @@
super(source, props, revision, license, description, descUrl,
archiveOs, archiveArch, archiveOsPath);
- mMinToolsRevision = Integer.parseInt(
- getProperty(props,
- PkgProps.MIN_TOOLS_REV,
- Integer.toString(MIN_TOOLS_REV_NOT_SPECIFIED)));
+ String revStr = getProperty(props, PkgProps.MIN_TOOLS_REV, null);
+
+ FullRevision rev = MIN_TOOLS_REV_NOT_SPECIFIED;
+ if (revStr != null) {
+ try {
+ rev = FullRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ mMinToolsRevision = rev;
}
/**
@@ -90,7 +94,7 @@
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
@Override
- public int getMinToolsRevision() {
+ public FullRevision getMinToolsRevision() {
return mMinToolsRevision;
}
@@ -98,9 +102,8 @@
public void saveProperties(Properties props) {
super.saveProperties(props);
- if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
- props.setProperty(PkgProps.MIN_TOOLS_REV,
- Integer.toString(getMinToolsRevision()));
+ if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {
+ props.setProperty(PkgProps.MIN_TOOLS_REV, getMinToolsRevision().toShortString());
}
}
@@ -108,7 +111,7 @@
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
- result = prime * result + mMinToolsRevision;
+ result = prime * result + ((mMinToolsRevision == null) ? 0 : mMinToolsRevision.hashCode());
return result;
}
@@ -124,7 +127,11 @@
return false;
}
MinToolsPackage other = (MinToolsPackage) obj;
- if (mMinToolsRevision != other.mMinToolsRevision) {
+ if (mMinToolsRevision == null) {
+ if (other.mMinToolsRevision != null) {
+ return false;
+ }
+ } else if (!mMinToolsRevision.equals(other.mMinToolsRevision)) {
return false;
}
return true;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
index 0e2b615..9c6bd44 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
@@ -16,6 +16,8 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.AndroidVersion;
@@ -23,7 +25,6 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
@@ -58,7 +59,6 @@
*/
public abstract class Package implements IDescription, Comparable<Package> {
- private final int mRevision;
private final String mObsolete;
private final String mLicense;
private final String mDescription;
@@ -104,17 +104,20 @@
*/
Package(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
mSource = source;
- mRevision = XmlParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_REVISION, 0);
- mDescription = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);
- mDescUrl = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESC_URL);
- mReleaseNote = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_NOTE);
- mReleaseUrl = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_URL);
- mObsolete = XmlParserUtils.getOptionalXmlString(
- packageNode, SdkRepoConstants.NODE_OBSOLETE);
+ mDescription =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);
+ mDescUrl =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESC_URL);
+ mReleaseNote =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_NOTE);
+ mReleaseUrl =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_URL);
+ mObsolete =
+ PackageParserUtils.getOptionalXmlString(packageNode, SdkRepoConstants.NODE_OBSOLETE);
mLicense = parseLicense(packageNode, licenses);
- mArchives = parseArchives(XmlParserUtils.getFirstChild(
- packageNode, SdkRepoConstants.NODE_ARCHIVES));
+ mArchives = parseArchives(
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_ARCHIVES));
}
/**
@@ -144,8 +147,6 @@
descUrl = "";
}
- mRevision = Integer.parseInt(
- getProperty(props, PkgProps.PKG_REVISION, Integer.toString(revision)));
mLicense = getProperty(props, PkgProps.PKG_LICENSE, license);
mDescription = getProperty(props, PkgProps.PKG_DESC, description);
mDescUrl = getProperty(props, PkgProps.PKG_DESC_URL, descUrl);
@@ -210,7 +211,11 @@
* @return The string value of the given key in the properties, or null if the key
* isn't found or if {@code props} is null.
*/
- static String getProperty(Properties props, String propKey, String defaultValue) {
+ @Nullable
+ static String getProperty(
+ @Nullable Properties props,
+ @NonNull String propKey,
+ @Nullable String defaultValue) {
if (props == null) {
return defaultValue;
}
@@ -218,15 +223,38 @@
}
/**
+ * Utility method that returns an integer property from a {@link Properties} object.
+ * Returns the default value if props is null or if the property is not defined or
+ * cannot be parsed to an integer.
+ *
+ * @param props The {@link Properties} to search into.
+ * If null, the default value is returned.
+ * @param propKey The name of the property. Must not be null.
+ * @param defaultValue The default value to return if {@code props} is null or if the
+ * key is not found. Can be null.
+ * @return The integer value of the given key in the properties, or the {@code defaultValue}.
+ */
+ static int getPropertyInt(
+ @Nullable Properties props,
+ @NonNull String propKey,
+ int defaultValue) {
+ String s = props != null ? props.getProperty(propKey, null) : null;
+ if (s != null) {
+ try {
+ return Integer.parseInt(s);
+ } catch (Exception ignore) {}
+ }
+ return defaultValue;
+ }
+
+ /**
* Save the properties of the current packages in the given {@link Properties} object.
* These properties will later be give the constructor that takes a {@link Properties} object.
*/
public void saveProperties(Properties props) {
- props.setProperty(PkgProps.PKG_REVISION, Integer.toString(mRevision));
if (mLicense != null && mLicense.length() > 0) {
props.setProperty(PkgProps.PKG_LICENSE, mLicense);
}
-
if (mDescription != null && mDescription.length() > 0) {
props.setProperty(PkgProps.PKG_DESC, mDescription);
}
@@ -243,7 +271,6 @@
if (mObsolete != null) {
props.setProperty(PkgProps.PKG_OBSOLETE, mObsolete);
}
-
if (mSource != null) {
props.setProperty(PkgProps.PKG_SOURCE_URL, mSource.getUrl());
}
@@ -255,8 +282,8 @@
* license of this name defined.
*/
private String parseLicense(Node packageNode, Map<String, String> licenses) {
- Node usesLicense = XmlParserUtils.getFirstChild(
- packageNode, SdkRepoConstants.NODE_USES_LICENSE);
+ Node usesLicense =
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_USES_LICENSE);
if (usesLicense != null) {
Node ref = usesLicense.getAttributes().getNamedItem(SdkRepoConstants.ATTR_REF);
if (ref != null) {
@@ -297,13 +324,13 @@
private Archive parseArchive(Node archiveNode) {
Archive a = new Archive(
this,
- (Os) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepoConstants.ATTR_OS,
- Os.values(), null),
- (Arch) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepoConstants.ATTR_ARCH,
- Arch.values(), Arch.ANY),
- XmlParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),
- XmlParserUtils.getXmlLong (archiveNode, SdkRepoConstants.NODE_SIZE, 0),
- XmlParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)
+ (Os) PackageParserUtils.getEnumAttribute(
+ archiveNode, SdkRepoConstants.ATTR_OS, Os.values(), null),
+ (Arch) PackageParserUtils.getEnumAttribute(
+ archiveNode, SdkRepoConstants.ATTR_ARCH, Arch.values(), Arch.ANY),
+ PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),
+ PackageParserUtils.getXmlLong (archiveNode, SdkRepoConstants.NODE_SIZE, 0),
+ PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)
);
return a;
@@ -328,9 +355,7 @@
* Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
* Can be 0 if this is a local package of unknown revision.
*/
- public int getRevision() {
- return mRevision;
- }
+ public abstract FullRevision getRevision();
/**
* Returns the optional description for all packages (platform, add-on, tool, doc) or
@@ -484,8 +509,8 @@
sb.append("\n");
}
- sb.append(String.format("Revision %1$d%2$s",
- getRevision(),
+ sb.append(String.format("Revision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : ""));
s = getDescUrl();
@@ -615,8 +640,8 @@
* <p/>
* Two packages are considered the same if they represent the same thing, except for the
* revision number.
- * @param pkg the package to compare
- * @return true if the item
+ * @param pkg the package to compare.
+ * @return true if the item as equivalent.
*/
public abstract boolean sameItemAs(Package pkg);
@@ -632,27 +657,10 @@
*
* @see #sameItemAs(Package)
*/
- public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
- if (replacementPackage == null) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check they are the same item.
- if (sameItemAs(replacementPackage) == false) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check revision number
- if (replacementPackage.getRevision() > this.getRevision()) {
- return UpdateInfo.UPDATE;
- }
-
- // not an upgrade but not incompatible either.
- return UpdateInfo.NOT_UPDATE;
- }
+ public abstract UpdateInfo canBeUpdatedBy(Package replacementPackage);
/**
- * Returns an ordering like this: <br/>
+ * Returns an ordering <b>suitable for display</b> like this: <br/>
* - Tools <br/>
* - Platform-Tools <br/>
* - Docs. <br/>
@@ -668,6 +676,10 @@
* Important: this must NOT be used to compare if two packages are the same thing.
* This is achieved by {@link #sameItemAs(Package)} or {@link #canBeUpdatedBy(Package)}.
* <p/>
+ * The order done here is suitable for display, and this may not be the appropriate
+ * order when comparing whether packages are equal or of greater revision -- if you need
+ * to compare revisions, then use {@link #getRevision()}{@code .compareTo(rev)} directly.
+ * <p/>
* This {@link #compareTo(Package)} method is purely an implementation detail to
* perform the right ordering of the packages in the list of available or installed packages.
* <p/>
@@ -679,7 +691,8 @@
String s1 = this.comparisonKey();
String s2 = other.comparisonKey();
- return s1.compareTo(s2);
+ int r = s1.compareTo(s2);
+ return r;
}
/**
@@ -728,12 +741,13 @@
// We insert the package version here because it is more important
- // than the revision number. We want package version to be sorted
+ // than the revision number.
+ // In the list display, we want package version to be sorted
// top-down, so we'll use 10k-api as the sorting key. The day we
- // get reach 10k APIs, we'll need to revisit this.
+ // reach 10k APIs, we'll need to revisit this.
sb.append("|v:"); //$NON-NLS-1$
- if (this instanceof IPackageVersion) {
- AndroidVersion v = ((IPackageVersion) this).getVersion();
+ if (this instanceof IAndroidVersionProvider) {
+ AndroidVersion v = ((IAndroidVersionProvider) this).getAndroidVersion();
sb.append(String.format("%1$04d.%2$d", //$NON-NLS-1$
10000 - v.getApiLevel(),
@@ -743,7 +757,19 @@
// Append revision number
sb.append("|r:"); //$NON-NLS-1$
- sb.append(String.format("%1$04d", getRevision())); //$NON-NLS-1$
+ FullRevision rev = getRevision();
+ sb.append(rev.getMajor()).append('.')
+ .append(rev.getMinor()).append('.')
+ .append(rev.getMicro()).append('.');
+ // Hack: When comparing packages for installation purposes, we want to treat
+ // "final releases" packages as more important than rc/preview packages.
+ // However like for the API level above, when sorting for list display purposes
+ // we want the final release package listed before its rc/preview packages.
+ if (rev.isPreview()) {
+ sb.append(rev.getPreview());
+ } else {
+ sb.append('0'); // 0=Final (!preview), to make "18.0" < "18.1" (18 Final < 18 RC1)
+ }
sb.append('|');
return sb.toString();
@@ -755,7 +781,7 @@
int result = 1;
result = prime * result + Arrays.hashCode(mArchives);
result = prime * result + ((mObsolete == null) ? 0 : mObsolete.hashCode());
- result = prime * result + mRevision;
+ result = prime * result + getRevision().hashCode();
result = prime * result + ((mSource == null) ? 0 : mSource.hashCode());
return result;
}
@@ -782,7 +808,7 @@
} else if (!mObsolete.equals(other.mObsolete)) {
return false;
}
- if (mRevision != other.mRevision) {
+ if (!getRevision().equals(other.getRevision())) {
return false;
}
if (mSource == null) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
new file mode 100755
index 0000000..8bde3d3
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
@@ -0,0 +1,180 @@
+/*
+ * 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.sdklib.internal.repository.packages;
+
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+/**
+ * Misc utilities to help extracting elements and attributes out of an XML document.
+ */
+public class PackageParserUtils {
+
+ /**
+ * Parses a full revision element such as <revision> or <min-tools-rev>.
+ * This supports both the single-integer format as well as the full revision
+ * format with major/minor/micro/preview sub-elements.
+ *
+ * @param revisionNode The node to parse.
+ * @return A new {@link FullRevision}. If parsing failed, major is set to
+ * {@link FullRevision#MISSING_MAJOR_REV}.
+ */
+ public static FullRevision parseFullRevisionElement(Node revisionNode) {
+ // This needs to support two modes:
+ // - For repository XSD >= 7, <revision> contains sub-elements such as <major> or <minor>.
+ // - Otherwise for repository XSD < 7, <revision> contains an integer.
+ // The <major> element is mandatory, so it's easy to distinguish between both cases.
+ int major = FullRevision.MISSING_MAJOR_REV,
+ minor = FullRevision.IMPLICIT_MINOR_REV,
+ micro = FullRevision.IMPLICIT_MICRO_REV,
+ preview = FullRevision.NOT_A_PREVIEW;
+
+ if (revisionNode != null) {
+ if (PackageParserUtils.findChildElement(revisionNode,
+ SdkRepoConstants.NODE_MAJOR_REV) != null) {
+ // <revision> has a <major> sub-element, so it's a repository XSD >= 7.
+ major = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MAJOR_REV, FullRevision.MISSING_MAJOR_REV);
+ minor = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MINOR_REV, FullRevision.IMPLICIT_MINOR_REV);
+ micro = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MICRO_REV, FullRevision.IMPLICIT_MICRO_REV);
+ preview = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_PREVIEW, FullRevision.NOT_A_PREVIEW);
+ } else {
+ try {
+ String majorStr = revisionNode.getTextContent().trim();
+ major = Integer.parseInt(majorStr);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ return new FullRevision(major, minor, micro, preview);
+ }
+
+ /**
+ * Returns the first child element with the given XML local name.
+ * If xmlLocalName is null, returns the very first child element.
+ */
+ public static Node findChildElement(Node node, String xmlLocalName) {
+ if (node != null) {
+ String nsUri = node.getNamespaceURI();
+ for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI())) {
+ if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {
+ return child;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the value of that XML element as a string.
+ * Returns an empty string whether the element is missing or empty,
+ * so you can't tell the difference.
+ * <p/>
+ * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the
+ * element is missing versus empty.
+ *
+ * @param node The XML <em>parent</em> node to parse.
+ * @param xmlLocalName The XML local name to find in the parent node.
+ * @return The text content of the element. Returns an empty string whether the element
+ * is missing or empty, so you can't tell the difference.
+ */
+ public static String getXmlString(Node node, String xmlLocalName) {
+ Node child = findChildElement(node, xmlLocalName);
+
+ return child == null ? "" : child.getTextContent(); //$NON-NLS-1$
+ }
+
+ /**
+ * Retrieves the value of that XML element as a string.
+ * Returns null when the element is missing, so you can tell between a missing element
+ * and an empty one.
+ * <p/>
+ * Note: use {@link #getXmlString(Node, String)} if you don't need to know when the
+ * element is missing versus empty.
+ *
+ * @param node The XML <em>parent</em> node to parse.
+ * @param xmlLocalName The XML local name to find in the parent node.
+ * @return The text content of the element. Returns null when the element is missing.
+ * Returns an empty string whether the element is present but empty.
+ */
+ public static String getOptionalXmlString(Node node, String xmlLocalName) {
+ Node child = findChildElement(node, xmlLocalName);
+
+ return child == null ? null : child.getTextContent(); //$NON-NLS-1$
+ }
+
+ /**
+ * Retrieves the value of that XML element as an integer.
+ * Returns the default value when the element is missing or is not an integer.
+ */
+ public static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {
+ String s = getXmlString(node, xmlLocalName);
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Retrieves the value of that XML element as a long.
+ * Returns the default value when the element is missing or is not an integer.
+ */
+ public static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {
+ String s = getXmlString(node, xmlLocalName);
+ try {
+ return Long.parseLong(s);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Retrieve an attribute which value must match one of the given enums using a
+ * case-insensitive name match.
+ *
+ * Returns defaultValue if the attribute does not exist or its value does not match
+ * the given enum values.
+ */
+ public static Object getEnumAttribute(
+ Node archiveNode,
+ String attrName,
+ Object[] values,
+ Object defaultValue) {
+
+ Node attr = archiveNode.getAttributes().getNamedItem(attrName);
+ if (attr != null) {
+ String found = attr.getNodeValue();
+ for (Object value : values) {
+ if (value.toString().equalsIgnoreCase(found)) {
+ return value;
+ }
+ }
+ }
+
+ return defaultValue;
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
index 391c32e..9aaee66 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
@@ -16,6 +16,7 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.AndroidVersion;
@@ -23,7 +24,6 @@
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
@@ -40,7 +40,8 @@
/**
* Represents a platform XML node in an SDK repository.
*/
-public class PlatformPackage extends MinToolsPackage implements IPackageVersion, ILayoutlibVersion {
+public class PlatformPackage extends MinToolsPackage
+ implements IAndroidVersionProvider, ILayoutlibVersion {
/** The package version, for platform, add-on and doc packages. */
private final AndroidVersion mVersion;
@@ -64,20 +65,27 @@
* parameters that vary according to the originating XML schema.
* @param licenses The licenses loaded from the XML originating document.
*/
- public PlatformPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
+ public PlatformPackage(
+ SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- mVersionName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_VERSION);
+ mVersionName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_VERSION);
- int apiLevel = XmlParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ int apiLevel =
+ PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
if (codeName.length() == 0) {
codeName = null;
}
mVersion = new AndroidVersion(apiLevel, codeName);
- mIncludedAbi = XmlParserUtils.getOptionalXmlString(
- packageNode, SdkRepoConstants.NODE_ABI_INCLUDED);
+ mIncludedAbi = PackageParserUtils.getOptionalXmlString(packageNode,
+ SdkRepoConstants.NODE_ABI_INCLUDED);
mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
}
@@ -145,8 +153,8 @@
}
/** Returns the package version, for platform, add-on and doc packages. */
- @Override
- public AndroidVersion getVersion() {
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
return mVersion;
}
@@ -220,13 +228,13 @@
if (mVersion.isPreview()) {
s = String.format("SDK Platform Android %1$s Preview, revision %2$s%3$s",
getVersionName(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
} else {
s = String.format("SDK Platform Android %1$s, API %2$d, revision %3$s%4$s",
getVersionName(),
mVersion.getApiLevel(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
}
@@ -247,8 +255,8 @@
}
if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -279,7 +287,7 @@
File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS);
File folder = new File(platforms,
- String.format("android-%s", getVersion().getApiString())); //$NON-NLS-1$
+ String.format("android-%s", getAndroidVersion().getApiString())); //$NON-NLS-1$
return folder;
}
@@ -290,7 +298,7 @@
PlatformPackage newPkg = (PlatformPackage)pkg;
// check they are the same version.
- return newPkg.getVersion().equals(this.getVersion());
+ return newPkg.getAndroidVersion().equals(this.getAndroidVersion());
}
return false;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
index 6070593..fdacfcc 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
@@ -39,10 +39,12 @@
/**
* Represents a platform-tool XML node in an SDK repository.
*/
-public class PlatformToolPackage extends Package {
+public class PlatformToolPackage extends FullRevisionPackage {
/** The value returned by {@link PlatformToolPackage#installId()}. */
public static final String INSTALL_ID = "platform-tools"; //$NON-NLS-1$
+ /** The value returned by {@link PlatformToolPackage#installId()}. */
+ public static final String INSTALL_ID_PREVIEW = "platform-tools-preview"; //$NON-NLS-1$
/**
* Creates a new platform-tool package from the attributes and elements of the given XML node.
@@ -153,13 +155,18 @@
/**
* Returns a string identifier to install this package from the command line.
- * For platform-tools, we use "platform-tools" since this package type is unique.
+ * For platform-tools, we use "platform-tools" or "platform-tools-preview" since
+ * this package type is unique.
* <p/>
* {@inheritDoc}
*/
@Override
public String installId() {
- return INSTALL_ID;
+ if (getRevision().isPreview()) {
+ return INSTALL_ID_PREVIEW;
+ } else {
+ return INSTALL_ID;
+ }
}
/**
@@ -178,8 +185,8 @@
*/
@Override
public String getShortDescription() {
- return String.format("Android SDK Platform-tools, revision %1$d%2$s",
- getRevision(),
+ return String.format("Android SDK Platform-tools, revision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -192,8 +199,8 @@
}
if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -215,10 +222,28 @@
return new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
}
+ /**
+ * Check whether 2 platform-tool packages are the same <em>and</em> have the
+ * same preview bit.
+ */
@Override
public boolean sameItemAs(Package pkg) {
+ return sameItemAs(pkg, false /*ignorePreviews*/);
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg, boolean ignorePreviews) {
// only one platform-tool package so any platform-tool package is the same item.
- return pkg instanceof PlatformToolPackage;
+ if (pkg instanceof PlatformToolPackage) {
+ if (ignorePreviews) {
+ return true;
+ } else {
+ // however previews can only match previews by default, unless we ignore that check.
+ return ((PlatformToolPackage) pkg).getRevision().isPreview() ==
+ getRevision().isPreview();
+ }
+ }
+ return false;
}
/**
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
index ed5eda5..dcaccf1 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
@@ -16,6 +16,7 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.AndroidVersion.AndroidVersionException;
import com.android.sdklib.IAndroidTarget;
@@ -23,7 +24,6 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
@@ -49,7 +49,7 @@
* Represents a sample XML node in an SDK repository.
*/
public class SamplePackage extends MinToolsPackage
- implements IPackageVersion, IMinApiLevelDependency {
+ implements IAndroidVersionProvider, IMinApiLevelDependency {
/** The matching platform version. */
private final AndroidVersion mVersion;
@@ -70,18 +70,24 @@
* parameters that vary according to the originating XML schema.
* @param licenses The licenses loaded from the XML originating document.
*/
- public SamplePackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
+ public SamplePackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- int apiLevel = XmlParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ int apiLevel =
+ PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
if (codeName.length() == 0) {
codeName = null;
}
mVersion = new AndroidVersion(apiLevel, codeName);
- mMinApiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_MIN_API_LEVEL,
- MIN_API_LEVEL_NOT_SPECIFIED);
+ mMinApiLevel = PackageParserUtils.getXmlInt(packageNode,
+ SdkRepoConstants.NODE_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
}
/**
@@ -115,10 +121,8 @@
mVersion = target.getVersion();
- mMinApiLevel = Integer.parseInt(
- getProperty(props,
- PkgProps.SAMPLE_MIN_API_LEVEL,
- Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));
+ mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
}
/**
@@ -133,7 +137,8 @@
* @throws AndroidVersionException if the {@link AndroidVersion} can't be restored
* from properties.
*/
- public static Package create(String archiveOsPath, Properties props) throws AndroidVersionException {
+ public static Package create(String archiveOsPath, Properties props)
+ throws AndroidVersionException {
return new SamplePackage(archiveOsPath, props);
}
@@ -151,10 +156,8 @@
mVersion = new AndroidVersion(props);
- mMinApiLevel = Integer.parseInt(
- getProperty(props,
- PkgProps.SAMPLE_MIN_API_LEVEL,
- Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));
+ mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
}
/**
@@ -182,8 +185,8 @@
}
/** Returns the matching platform version. */
- @Override
- public AndroidVersion getVersion() {
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
return mVersion;
}
@@ -217,10 +220,10 @@
*/
@Override
public String getShortDescription() {
- String s = String.format("Samples for SDK API %1$s%2$s, revision %3$d%4$s",
+ String s = String.format("Samples for SDK API %1$s%2$s, revision %3$s%4$s",
mVersion.getApiString(),
mVersion.isPreview() ? " Preview" : "",
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
return s;
}
@@ -239,8 +242,8 @@
}
if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -284,12 +287,12 @@
// Otherwise, get a suitable default
File folder = new File(samplesRoot,
- String.format("android-%s", getVersion().getApiString())); //$NON-NLS-1$
+ String.format("android-%s", getAndroidVersion().getApiString())); //$NON-NLS-1$
for (int n = 1; folder.exists(); n++) {
// Keep trying till we find an unused directory.
folder = new File(samplesRoot,
- String.format("android-%s_%d", getVersion().getApiString(), n)); //$NON-NLS-1$
+ String.format("android-%s_%d", getAndroidVersion().getApiString(), n)); //$NON-NLS-1$
}
return folder;
@@ -301,7 +304,7 @@
SamplePackage newPkg = (SamplePackage)pkg;
// check they are the same version.
- return newPkg.getVersion().equals(this.getVersion());
+ return newPkg.getAndroidVersion().equals(this.getAndroidVersion());
}
return false;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
index 2c577e4..1ba2d13 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
@@ -16,6 +16,7 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.AndroidVersion;
@@ -24,7 +25,6 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
@@ -42,11 +42,11 @@
/**
* Represents a source XML node in an SDK repository.
* <p/>
- * Note that a source package has a version and thus implements {@link IPackageVersion}.
+ * Note that a source package has a version and thus implements {@link IAndroidVersionProvider}.
* However there is no mandatory dependency that limits installation so this does not
* implement {@link IPlatformDependency}.
*/
-public class SourcePackage extends Package implements IPackageVersion {
+public class SourcePackage extends MajorRevisionPackage implements IAndroidVersionProvider {
/** The package version, for platform, add-on and doc packages. */
private final AndroidVersion mVersion;
@@ -67,8 +67,10 @@
Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ int apiLevel =
+ PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
if (codeName.length() == 0) {
codeName = null;
}
@@ -184,8 +186,8 @@
/**
* Returns the android version of this package.
*/
- @Override
- public AndroidVersion getVersion() {
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
return mVersion;
}
@@ -226,12 +228,12 @@
if (mVersion.isPreview()) {
return String.format("Sources for Android '%1$s' Preview SDK, revision %2$s%3$s",
mVersion.getCodename(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
} else {
return String.format("Sources for Android SDK, API %1$d, revision %2$s%3$s",
mVersion.getApiLevel(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
}
@@ -250,8 +252,8 @@
}
if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -300,7 +302,7 @@
SourcePackage newPkg = (SourcePackage)pkg;
// check they are the same version.
- return getVersion().equals(newPkg.getVersion());
+ return getAndroidVersion().equals(newPkg.getAndroidVersion());
}
return false;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
index c862882..b6bf501 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
@@ -16,6 +16,7 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.AndroidVersion;
@@ -24,7 +25,6 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.SystemImage;
import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
@@ -41,8 +41,8 @@
/**
* Represents a system-image XML node in an SDK repository.
*/
-public class SystemImagePackage extends Package
- implements IPackageVersion, IPlatformDependency {
+public class SystemImagePackage extends MajorRevisionPackage
+ implements IAndroidVersionProvider, IPlatformDependency {
/** The package version, for platform, add-on and doc packages. */
private final AndroidVersion mVersion;
@@ -66,14 +66,16 @@
Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ int apiLevel =
+ PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
if (codeName.length() == 0) {
codeName = null;
}
mVersion = new AndroidVersion(apiLevel, codeName);
- mAbi = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);
+ mAbi = PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);
}
@VisibleForTesting(visibility=Visibility.PRIVATE)
@@ -217,8 +219,8 @@
* <p/>
* A system-image has the same {@link AndroidVersion} as the platform it depends on.
*/
- @Override
- public AndroidVersion getVersion() {
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
return mVersion;
}
@@ -253,7 +255,7 @@
return String.format("%1$s System Image, Android API %2$s, revision %3$s%4$s",
getAbiDisplayName(),
mVersion.getApiString(),
- getRevision(),
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -271,8 +273,8 @@
}
if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -314,7 +316,7 @@
// check they are the same abi and version.
return getAbi().equals(newPkg.getAbi()) &&
- getVersion().equals(newPkg.getVersion());
+ getAndroidVersion().equals(newPkg.getAndroidVersion());
}
return false;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
index e4a8fe6..8ae469e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
@@ -22,11 +22,11 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkRepoConstants;
import com.android.sdklib.util.GrabProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
@@ -43,19 +43,18 @@
/**
* Represents a tool XML node in an SDK repository.
*/
-public class ToolPackage extends Package implements IMinPlatformToolsDependency {
+public class ToolPackage extends FullRevisionPackage implements IMinPlatformToolsDependency {
/** The value returned by {@link ToolPackage#installId()}. */
public static final String INSTALL_ID = "tools"; //$NON-NLS-1$
-
- public static final String PROP_MIN_PLATFORM_TOOLS_REV =
- "Platform.MinPlatformToolsRev"; //$NON-NLS-1$
+ /** The value returned by {@link ToolPackage#installId()}. */
+ private static final String INSTALL_ID_PREVIEW = "tools-preview"; //$NON-NLS-1$
/**
* The minimal revision of the platform-tools package required by this package
* or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing.
*/
- private final int mMinPlatformToolsRevision;
+ private final FullRevision mMinPlatformToolsRevision;
/**
* Creates a new tool package from the attributes and elements of the given XML node.
@@ -67,14 +66,17 @@
* parameters that vary according to the originating XML schema.
* @param licenses The licenses loaded from the XML originating document.
*/
- public ToolPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
+ public ToolPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- mMinPlatformToolsRevision = XmlParserUtils.getXmlInt(
- packageNode,
- SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV,
- MIN_PLATFORM_TOOLS_REV_INVALID);
- if (mMinPlatformToolsRevision == MIN_PLATFORM_TOOLS_REV_INVALID) {
+ mMinPlatformToolsRevision = PackageParserUtils.parseFullRevisionElement(
+ PackageParserUtils.findChildElement(packageNode,
+ SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV));
+
+ if (mMinPlatformToolsRevision.equals(MIN_PLATFORM_TOOLS_REV_INVALID)) {
// This revision number is mandatory starting with sdk-repository-3.xsd
// and did not exist before. Complain if the URI has level >= 3.
@@ -142,11 +144,16 @@
archiveArch,
archiveOsPath);
- mMinPlatformToolsRevision = Integer.parseInt(
- getProperty(
- props,
- PROP_MIN_PLATFORM_TOOLS_REV,
- Integer.toString(MIN_PLATFORM_TOOLS_REV_INVALID)));
+ String revStr = getProperty(props, PkgProps.MIN_PLATFORM_TOOLS_REV, null);
+
+ FullRevision rev = MIN_PLATFORM_TOOLS_REV_INVALID;
+ if (revStr != null) {
+ try {
+ rev = FullRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ mMinPlatformToolsRevision = rev;
}
/**
@@ -156,19 +163,23 @@
* This attribute is mandatory and should not be normally missing.
*/
@Override
- public int getMinPlatformToolsRevision() {
+ public FullRevision getMinPlatformToolsRevision() {
return mMinPlatformToolsRevision;
}
/**
* Returns a string identifier to install this package from the command line.
- * For tools, we use "tools" since this package is unique.
+ * For tools, we use "tools" or "tools-preview" since this package is unique.
* <p/>
* {@inheritDoc}
*/
@Override
public String installId() {
- return INSTALL_ID;
+ if (getRevision().isPreview()) {
+ return INSTALL_ID_PREVIEW;
+ } else {
+ return INSTALL_ID;
+ }
}
/**
@@ -187,8 +198,8 @@
*/
@Override
public String getShortDescription() {
- return String.format("Android SDK Tools, revision %1$d%2$s",
- getRevision(),
+ return String.format("Android SDK Tools, revision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -201,8 +212,8 @@
}
if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -224,19 +235,38 @@
return new File(osSdkRoot, SdkConstants.FD_TOOLS);
}
+ /**
+ * Check whether 2 tool packages are the same <em>and</em> have the
+ * same preview bit.
+ */
@Override
public boolean sameItemAs(Package pkg) {
+ // Only one tool package so any tool package is the same item
+ return sameItemAs(pkg, false /*ignorePreviews*/);
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg, boolean ignorePreviews) {
// only one tool package so any tool package is the same item.
- return pkg instanceof ToolPackage;
+ if (pkg instanceof ToolPackage) {
+ if (ignorePreviews) {
+ return true;
+ } else {
+ // however previews can only match previews by default, unless we ignore that check.
+ return ((ToolPackage) pkg).getRevision().isPreview() ==
+ getRevision().isPreview();
+ }
+ }
+ return false;
}
@Override
public void saveProperties(Properties props) {
super.saveProperties(props);
- if (getMinPlatformToolsRevision() != MIN_PLATFORM_TOOLS_REV_INVALID) {
- props.setProperty(PROP_MIN_PLATFORM_TOOLS_REV,
- Integer.toString(getMinPlatformToolsRevision()));
+ if (!getMinPlatformToolsRevision().equals(MIN_PLATFORM_TOOLS_REV_INVALID)) {
+ props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
+ getMinPlatformToolsRevision().toShortString());
}
}
@@ -313,7 +343,8 @@
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
- result = prime * result + mMinPlatformToolsRevision;
+ result = prime * result
+ + ((mMinPlatformToolsRevision == null) ? 0 : mMinPlatformToolsRevision.hashCode());
return result;
}
@@ -329,7 +360,11 @@
return false;
}
ToolPackage other = (ToolPackage) obj;
- if (mMinPlatformToolsRevision != other.mMinPlatformToolsRevision) {
+ if (mMinPlatformToolsRevision == null) {
+ if (other.mMinPlatformToolsRevision != null) {
+ return false;
+ }
+ } else if (!mMinPlatformToolsRevision.equals(other.mMinPlatformToolsRevision)) {
return false;
}
return true;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
index 07c3a86..482655f 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
@@ -20,7 +20,7 @@
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.XmlParserUtils;
+import com.android.sdklib.internal.repository.packages.PackageParserUtils;
import com.android.sdklib.repository.RepoConstants;
import com.android.sdklib.repository.SdkRepoConstants;
@@ -340,14 +340,14 @@
prefix,
RepoConstants.NODE_ARCHIVE)) != null) {
try {
- Os os = (Os) XmlParserUtils.getEnumAttribute(archive,
- RepoConstants.ATTR_OS,
- Os.values(),
- null /*default*/);
- Arch arch = (Arch) XmlParserUtils.getEnumAttribute(archive,
- RepoConstants.ATTR_ARCH,
- Arch.values(),
- Arch.ANY);
+ Os os = (Os) PackageParserUtils.getEnumAttribute(archive,
+ RepoConstants.ATTR_OS,
+ Os.values(),
+ null /*default*/);
+ Arch arch = (Arch) PackageParserUtils.getEnumAttribute(archive,
+ RepoConstants.ATTR_ARCH,
+ Arch.values(),
+ Arch.ANY);
if (os == null || !os.isCompatible() ||
arch == null || !arch.isCompatible()) {
continue;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-addon-5.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-addon-5.xsd
new file mode 100755
index 0000000..6250789
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-addon-5.xsd
@@ -0,0 +1,441 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2011 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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/addon/5"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/addon/5"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The repository contains a collection of downloadable items known as
+ "packages". Each package has a type and various attributes and contains
+ a list of file "archives" that can be downloaded for specific OSes.
+
+ An Android Addon repository is a web site that contains an "addon.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r8. It is split out of the
+ main SDK Repository XML Schema and can only contain <addon> and
+ <extra> packages.
+
+ - v2 is used by the SDK Updater in Tools r12.
+ - <extra> element now has a <project-files> element that contains 1 or
+ or more <path>, each indicating the relative path of a file that this package
+ can contribute to installed projects.
+ - <addon> element now has an optional <layoutlib> that indicates the API
+ and revision of the layout library for this particular add-on, if any.
+
+ - v3 is used by the SDK Manager in Tools r14:
+ - <extra> now has an <old-paths> element, a ;-separated list of old paths that
+ should be detected and migrated to the new <path> for that package.
+
+ - v4 is used by the SDK Manager in Tools r18:
+ - <extra> and <addon> are not in the Repository XSD v6 anymore.
+ - <extra> get a new field <name-display>, which is used by the SDK Manager to
+ customize the name of the extra in the list display. The single <vendor>
+ field becomes <vendor-id> and <vendor-display>, the id being used internally
+ and the display in the UI.
+ - <addon> does the same, where <name> is replaced by <name-id> and <name-display>
+ and <vendor> is replaced by <vendor-id> and <vendor-display>.
+
+ - v5 is used by the SDK Manager in Tools r20:
+ - The <beta-rc> element is no longer supported. It was never implemented anyway.
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - <min-tools-rev> for <extra> becomes a full revision element.
+ -->
+
+ <xsd:element name="sdk-addon" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable packages.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="add-on" type="sdk:addonType" />
+ <xsd:element name="extra" type="sdk:extraType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK Add-on package. -->
+
+ <xsd:complexType name="addonType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK add-on package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The internal name id of the add-on. Must be unique per vendor. -->
+ <xsd:element name="name-id" type="sdk:idType" />
+ <!-- The displayed name of the add-on. -->
+ <xsd:element name="name-display" type="xsd:normalizedString" />
+
+ <!-- The internal vendor id of the add-on. Must be unique amongst vendors. -->
+ <xsd:element name="vendor-id" type="sdk:idType" />
+ <!-- The displayed vendor name of the add-on. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" />
+
+ <!-- The Android API Level for the add-on. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- Note: Add-ons do not support 'codenames' (a.k.a. API previews). -->
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- An add-on can declare 0 or more libraries.
+ This element is mandatory but it can be empty.
+ -->
+
+ <xsd:element name="libs">
+ <xsd:complexType>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="lib">
+ <xsd:complexType>
+ <xsd:all>
+ <!-- The name of the library. -->
+ <xsd:element name="name" type="xsd:normalizedString" />
+ <!-- The optional description of this add-on library. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+
+ <!-- Optional information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ An ID string for an addon/extra name-id or vendor-id
+ can only be simple alphanumeric string.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_-]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a layout library used by an addon. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in an addon.
+ .</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The layoutlib API level, an int > 0,
+ incremented with each new incompatible lib. -->
+ <xsd:element name="api" type="xsd:positiveInteger" />
+ <!-- The incremental minor revision for that API, e.g. in case of bug fixes.
+ Optional. An int >= 0, assumed to be 0 if the element is missing. -->
+ <xsd:element name="revision" type="xsd:nonNegativeInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK extra package. This kind of package is for
+ "free" content. Such packages are installed in SDK/extras/vendor/path.
+ -->
+
+ <xsd:complexType name="extraType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ An SDK extra package. This kind of package is for "free" content.
+ Such packages are installed in SDK/vendor/path.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The displayed name of the extra. -->
+ <xsd:element name="name-display" type="xsd:normalizedString" />
+
+ <!-- The internal vendor id of the extra. Must be unique amongst vendors. -->
+ <xsd:element name="vendor-id" type="sdk:idType" />
+ <!-- The displayed vendor name of the extra. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" />
+
+ <!-- The install path sub-folder name. It must not be empty. -->
+ <xsd:element name="path" type="sdk:segmentType" />
+
+ <!-- A semi-colon separated list of "obsolete" path names which are equivalent
+ to the current 'path' name. When a package is seen using an old-paths' name,
+ the package manager will try to upgrade it to the new path. -->
+ <xsd:element name="old-paths" type="sdk:segmentListType" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+ <!-- The minimal API level required by this package.
+ Optional. If present, must be an int > 0. -->
+ <xsd:element name="min-api-level" type="xsd:positiveInteger" minOccurs="0" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+
+ <!-- A list of project files contributed by this package. Optional. -->
+ <xsd:element name="project-files" type="sdk:projectFilesType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro and an optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and an
+ optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The preview/release candidate revision, an int > 0,
+ incremented each time a new preview is generated.
+ Not present for final releases. -->
+ <xsd:element name="preview" type="xsd:positiveInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a path segment used by the extra element. -->
+
+ <xsd:simpleType name="segmentType">
+ <xsd:annotation>
+ <xsd:documentation>
+ One path segment for the install path of an extra element.
+ It must be a single-segment path. It must not be empty.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-repository-7.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-repository-7.xsd
new file mode 100755
index 0000000..ca41b36
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-repository-7.xsd
@@ -0,0 +1,611 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2012 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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/repository/7"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/7"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The repository contains a collection of downloadable items known as
+ "packages". Each package has a type and various attributes and contains
+ a list of file "archives" that can be downloaded for specific OSes.
+
+ An Android SDK repository is a web site that contains a "repository.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r3 and r4.
+
+ - v2 is used by the SDK Updater in Tools r5:
+ - It introduces a new <sample> repository type. Previously samples
+ were included in the <platform> packages. Instead this package is used
+ and and the samples are installed in $SDK/samples.
+ - All repository types have a new <obsolete> node. It works as a marker
+ to indicate the package is obsolete and should not be selected by default.
+ The UI also hides these out by default.
+
+ - v3 is used by the SDK Updater in Tools r8:
+ - It introduces a new <platform-tool> repository type. Previously platform-specific
+ tools were included in the <platform> packages. Instead this package is used
+ and platform-specific tools are installed in $SDK/platform-tools
+ - There's a new element <min-platform-tools-rev> in <tool>. The tool package now
+ requires that at least some minimal version of <platform-tool> be installed.
+ - It removes the <addon> repository type, which is now in its own XML Schema.
+
+ - v4 is used by the SDK Updater in Tools r12:
+ - <extra> element now has a <project-files> element that contains 1 or
+ or more <path>, each indicating the relative path of a file that this package
+ can contribute to installed projects.
+ - <platform> element now has a mandatory <layoutlib> that indicates the API
+ and revision of that layout library for this particular platform.
+
+ - v5 is used by the SDK Manager in Tools r14:
+ - <extra> now has an <old-paths> element, a ;-separated list of old paths that
+ should be detected and migrated to the new <path> for that package.
+ - <platform> has a new optional <abi-included> that describes the ABI of the
+ system image included in the platform, if any.
+ - New <system-image> package type, to store system images outside of <platform>s.
+ - New <source> package type.
+
+ - v6 is used by the SDK Manager in Tools r18:
+ - <extra> packages are removed. They are served only by the addon XML.
+ - <platform>, <system-image>, <source>, <tool>, <platform-tool>, <doc>
+ and <sample> get a new optional field <beta-rc> which can be used to indicate
+ the package is a Beta Release Candidate and not a final release.
+
+ - v7 is used by the SDK Manager in Tools r20:
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - The <beta-rc> element is no longer supported, it is replaced by
+ <revision> -> <preview> and is only for <tool> and <platform-tool> packages.
+ - <min-tools-rev> and <min-platform-tools-rev> also become a full revision element.
+ -->
+
+ <xsd:element name="sdk-repository" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable packages.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="platform" type="sdk:platformType" />
+ <xsd:element name="system-image" type="sdk:systemImageType" />
+ <xsd:element name="source" type="sdk:sourceType" />
+ <xsd:element name="tool" type="sdk:toolType" />
+ <xsd:element name="platform-tool" type="sdk:platformToolType" />
+ <xsd:element name="doc" type="sdk:docType" />
+ <xsd:element name="sample" type="sdk:sampleType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK platform package. -->
+
+ <xsd:complexType name="platformType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android platform version. It is string such as "1.0". -->
+ <xsd:element name="version" type="xsd:normalizedString" />
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- Information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+
+ <!-- The ABI of the system image *included* in this platform, if any.
+ When the field is present, it means the platform already embeds one
+ system image. A platform can also have any number of external
+ <system-image> associated with it. -->
+ <xsd:element name="included-abi" type="sdk:abiType" minOccurs="0" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a layout library used by a platform. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The layoutlib API level, an int > 0,
+ incremented with each new incompatible lib. -->
+ <xsd:element name="api" type="xsd:positiveInteger" />
+ <!-- The incremental minor revision for that API, e.g. in case of bug fixes.
+ Optional. An int >= 0, assumed to be 0 if the element is missing. -->
+ <xsd:element name="revision" type="xsd:nonNegativeInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a system image used by a platform. -->
+
+ <xsd:complexType name="systemImageType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ System Image for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this system image belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The ABI of the system emulated by this image. -->
+ <xsd:element name="abi" type="sdk:abiType" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The definition of the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a source package. -->
+
+ <xsd:complexType name="sourceType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Sources for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this source belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK tool package. -->
+
+ <xsd:complexType name="toolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- The minimal revision of platform-tools required by this package.
+ Mandatory. Must be a revision element. -->
+ <xsd:element name="min-platform-tools-rev" type="sdk:revisionType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK platform-tool package. -->
+
+ <xsd:complexType name="platformToolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK doc package. -->
+
+ <xsd:complexType name="docType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK doc package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK sample package. -->
+
+ <xsd:complexType name="sampleType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK sample package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a path segment used by the extra element. -->
+
+ <xsd:simpleType name="segmentType">
+ <xsd:annotation>
+ <xsd:documentation>
+ One path segment for the install path of an extra element.
+ It must be a single-segment path.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro and an optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and an
+ optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The preview/release candidate revision, an int > 0,
+ incremented each time a new preview is generated.
+ Not present for final releases. -->
+ <xsd:element name="preview" type="xsd:positiveInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
index 579656a..571a4a6 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
@@ -29,7 +29,6 @@
public class PkgProps {
// Base Package
-
public static final String PKG_REVISION = "Pkg.Revision"; //$NON-NLS-1$
public static final String PKG_LICENSE = "Pkg.License"; //$NON-NLS-1$
public static final String PKG_DESC = "Pkg.Desc"; //$NON-NLS-1$
@@ -44,6 +43,7 @@
public static final String VERSION_API_LEVEL = "AndroidVersion.ApiLevel";//$NON-NLS-1$
public static final String VERSION_CODENAME = "AndroidVersion.CodeName";//$NON-NLS-1$
+
// AddonPackage
public static final String ADDON_NAME = "Addon.Name"; //$NON-NLS-1$
@@ -81,7 +81,10 @@
public static final String PLATFORM_VERSION = "Platform.Version"; //$NON-NLS-1$
public static final String PLATFORM_INCLUDED_ABI = "Platform.Included.Abi"; //$NON-NLS-1$
- // PlatformToolPackage
+ // ToolPackage
+
+ public static final String MIN_PLATFORM_TOOLS_REV = "Platform.MinPlatformToolsRev";//$NON-NLS-1$
+
// SamplePackage
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
index 52d3a14..703bf1a 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
@@ -46,7 +46,7 @@
* The latest version of the sdk-addon XML Schema.
* Valid version numbers are between 1 and this number, included.
*/
- public static final int NS_LATEST_VERSION = 4;
+ public static final int NS_LATEST_VERSION = 5;
/** The XML namespace of the latest sdk-addon XML. */
public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
index 258ea26..5ad6285 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
@@ -30,7 +30,7 @@
* The latest version of the sdk-repository XML Schema.
* Valid version numbers are between 1 and this number, included.
*/
- public static final int NS_LATEST_VERSION = 6;
+ public static final int NS_LATEST_VERSION = 7;
/**
* The min version of the sdk-repository XML Schema we'll try to load.
@@ -80,6 +80,23 @@
/** The root sdk-repository element */
public static final String NODE_SDK_REPOSITORY = "sdk-repository"; //$NON-NLS-1$
+ /* The major revision for tool and platform-tool package
+ * (the full revision number is revision.minor.micro + preview#.)
+ * Mandatory int > 0. 0 when missing, which should not happen in
+ * a valid document. */
+ public static final String NODE_MAJOR_REV = "major"; //$NON-NLS-1$
+ /* The minor revision for tool and platform-tool package
+ * (the full revision number is revision.minor.micro + preview#.)
+ * Optional int >= 0. Implied to be 0 when missing. */
+ public static final String NODE_MINOR_REV = "minor"; //$NON-NLS-1$
+ /* The micro revision for tool and platform-tool package
+ * (the full revision number is revision.minor.micro + preview#.)
+ * Optional int >= 0. Implied to be 0 when missing. */
+ public static final String NODE_MICRO_REV = "micro"; //$NON-NLS-1$
+ /* The preview revision for tool and platform-tool package.
+ * Int > 0, only present for "preview / release candidate" packages. */
+ public static final String NODE_PREVIEW = "preview"; //$NON-NLS-1$
+
/** A platform package. */
public static final String NODE_PLATFORM = "platform"; //$NON-NLS-1$
/** A tool package. */
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
index 564aaeb..c31efbf 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
@@ -137,7 +137,8 @@
<!-- An optional element indicating the package is a beta/preview.
When present, it indicates the release-candidate number.
- When the element is absent, it indicates this is a released package. -->
+ When the element is absent, it indicates this is a released package.
+ DEPRECATED. TODO remove in sdk-addon-5. -->
<xsd:element name="beta-rc" type="xsd:positiveInteger" minOccurs="0" />
<!-- Optional information on the layoutlib packaged in this platform. -->
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
index 87e183a..bccce69 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2012 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.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/AndroidVersionTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/AndroidVersionTest.java
new file mode 100755
index 0000000..5270bd1
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/AndroidVersionTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 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.sdklib;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link AndroidVersion}.
+ */
+public class AndroidVersionTest extends TestCase {
+
+ public final void testAndroidVersion() {
+ AndroidVersion v = new AndroidVersion(1, " CODENAME ");
+ assertEquals(1, v.getApiLevel());
+ assertEquals("CODENAME", v.getApiString());
+ assertTrue(v.isPreview());
+ assertEquals("CODENAME", v.getCodename());
+ assertEquals("CODENAME".hashCode(), v.hashCode());
+ assertEquals("API 1, CODENAME preview", v.toString());
+
+ v = new AndroidVersion(15, "REL");
+ assertEquals(15, v.getApiLevel());
+ assertEquals("15", v.getApiString());
+ assertFalse(v.isPreview());
+ assertNull(v.getCodename());
+ assertTrue(v.equals(15));
+ assertEquals(15, v.hashCode());
+ assertEquals("API 15", v.toString());
+
+ v = new AndroidVersion(15, null);
+ assertEquals(15, v.getApiLevel());
+ assertEquals("15", v.getApiString());
+ assertFalse(v.isPreview());
+ assertNull(v.getCodename());
+ assertTrue(v.equals(15));
+ assertEquals(15, v.hashCode());
+ assertEquals("API 15", v.toString());
+
+ // An empty codename is like a null codename
+ v = new AndroidVersion(15, " ");
+ assertFalse(v.isPreview());
+ assertNull(v.getCodename());
+ assertEquals("15", v.getApiString());
+
+ v = new AndroidVersion(15, "");
+ assertFalse(v.isPreview());
+ assertNull(v.getCodename());
+ assertEquals("15", v.getApiString());
+
+ assertTrue(v.isGreaterOrEqualThan(0));
+ assertTrue(v.isGreaterOrEqualThan(14));
+ assertTrue(v.isGreaterOrEqualThan(15));
+ assertFalse(v.isGreaterOrEqualThan(16));
+ assertFalse(v.isGreaterOrEqualThan(Integer.MAX_VALUE));
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
index 3f70d79..609481b 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
@@ -20,15 +20,11 @@
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.MockEmptySdkManager;
import com.android.sdklib.internal.repository.MockMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
-import com.android.sdklib.internal.repository.archives.ArchiveReplacement;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.packages.ExtraPackage;
import com.android.sdklib.internal.repository.packages.MockEmptyPackage;
import com.android.sdklib.internal.repository.packages.MockExtraPackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
import com.android.sdklib.internal.repository.sources.SdkRepoSource;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdklib.io.IFileOp;
@@ -366,7 +362,7 @@
int min_platform_tools_rev) {
Properties props = new Properties();
props.setProperty(PkgProps.EXTRA_OLD_PATHS, oldPaths);
- props.setProperty(ToolPackage.PROP_MIN_PLATFORM_TOOLS_REV,
+ props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
Integer.toString((min_platform_tools_rev)));
return new MockExtraPackage(source, props, vendor, newPath, revision) {
@Override
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java
new file mode 100755
index 0000000..9bf2703
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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.sdklib.internal.repository.packages;
+
+import com.android.sdklib.repository.PkgProps;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public class FullRevisionPackageTest extends TestCase {
+
+ /**
+ * Helper that creates the {@link Properties} from a {@link FullRevision}
+ * as expected by {@link FullRevisionPackage}.
+ */
+ public static Properties createProps(FullRevision revision) {
+ Properties props = new Properties();
+ if (revision != null) {
+ props.setProperty(PkgProps.PKG_REVISION, revision.toString());
+ }
+ return props;
+ }
+
+ public void testCompareTo() throws Exception {
+ // Test order of full revision packages.
+ //
+ // Note that Package.compareTo() is designed to return the desired
+ // ordering for a list display and as such a final/release package
+ // needs to be listed before its rc/preview package.
+ //
+ // This differs from the order used by FullRevision.compareTo().
+
+ ArrayList<Package> list = new ArrayList<Package>();
+
+ list.add(new MockToolPackage(null, new FullRevision(1, 0, 0, 0), 8));
+ list.add(new MockToolPackage(null, new FullRevision(1, 0, 0, 1), 8));
+ list.add(new MockToolPackage(null, new FullRevision(1, 0, 1, 0), 8));
+ list.add(new MockToolPackage(null, new FullRevision(1, 0, 1, 1), 8));
+ list.add(new MockToolPackage(null, new FullRevision(1, 1, 0, 0), 8));
+ list.add(new MockToolPackage(null, new FullRevision(1, 1, 0, 1), 8));
+ list.add(new MockToolPackage(null, new FullRevision(2, 1, 1, 0), 8));
+ list.add(new MockToolPackage(null, new FullRevision(2, 1, 1, 1), 8));
+
+ Collections.sort(list);
+
+ assertEquals(
+ "[Android SDK Tools, revision 1, " +
+ "Android SDK Tools, revision 1 rc1, " +
+ "Android SDK Tools, revision 1.0.1, " +
+ "Android SDK Tools, revision 1.0.1 rc1, " +
+ "Android SDK Tools, revision 1.1, " +
+ "Android SDK Tools, revision 1.1 rc1, " +
+ "Android SDK Tools, revision 2.1.1, " +
+ "Android SDK Tools, revision 2.1.1 rc1]",
+ Arrays.toString(list.toArray()));
+ }
+}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionTest.java
new file mode 100755
index 0000000..d072d05
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 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.sdklib.internal.repository.packages;
+
+import junit.framework.TestCase;
+
+public class FullRevisionTest extends TestCase {
+
+ public final void testFullRevision() {
+ FullRevision p = new FullRevision(5);
+ assertEquals(5, p.getMajor());
+ assertEquals(FullRevision.IMPLICIT_MINOR_REV, p.getMinor());
+ assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
+ assertEquals(FullRevision.NOT_A_PREVIEW, p.getPreview());
+ assertFalse (p.isPreview());
+ assertEquals("5", p.toShortString());
+ assertEquals(p, FullRevision.parseRevision("5"));
+ assertEquals("5.0.0", p.toString());
+ assertEquals(p, FullRevision.parseRevision("5.0.0"));
+
+ p = new FullRevision(5, 0, 0, 6);
+ assertEquals(5, p.getMajor());
+ assertEquals(FullRevision.IMPLICIT_MINOR_REV, p.getMinor());
+ assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
+ assertEquals(6, p.getPreview());
+ assertTrue (p.isPreview());
+ assertEquals("5 rc6", p.toShortString());
+ assertEquals(p, FullRevision.parseRevision("5 rc6"));
+ assertEquals("5.0.0 rc6", p.toString());
+ assertEquals(p, FullRevision.parseRevision("5.0.0 rc6"));
+
+ p = new FullRevision(6, 7, 0);
+ assertEquals(6, p.getMajor());
+ assertEquals(7, p.getMinor());
+ assertEquals(0, p.getMicro());
+ assertEquals(0, p.getPreview());
+ assertFalse (p.isPreview());
+ assertEquals("6.7", p.toShortString());
+ assertEquals(p, FullRevision.parseRevision("6.7"));
+ assertEquals("6.7.0", p.toString());
+ assertEquals(p, FullRevision.parseRevision("6.7.0"));
+
+ p = new FullRevision(10, 11, 12, FullRevision.NOT_A_PREVIEW);
+ assertEquals(10, p.getMajor());
+ assertEquals(11, p.getMinor());
+ assertEquals(12, p.getMicro());
+ assertEquals(0, p.getPreview());
+ assertFalse (p.isPreview());
+ assertEquals("10.11.12", p.toShortString());
+ assertEquals("10.11.12", p.toString());
+ assertEquals(p, FullRevision.parseRevision("10.11.12"));
+
+ p = new FullRevision(10, 11, 12, 13);
+ assertEquals(10, p.getMajor());
+ assertEquals(11, p.getMinor());
+ assertEquals(12, p.getMicro());
+ assertEquals(13, p.getPreview());
+ assertTrue (p.isPreview());
+ assertEquals("10.11.12 rc13", p.toShortString());
+ assertEquals("10.11.12 rc13", p.toString());
+ assertEquals(p, FullRevision.parseRevision("10.11.12 rc13"));
+ assertEquals(p, FullRevision.parseRevision(" 10.11.12 rc13"));
+ assertEquals(p, FullRevision.parseRevision("10.11.12 rc13 "));
+ assertEquals(p, FullRevision.parseRevision(" 10.11.12 rc13 "));
+ }
+
+ public final void testParseError() {
+ String errorMsg = null;
+ try {
+ FullRevision.parseRevision("not a number");
+ fail("FullRevision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("Invalid full revision: not a number", errorMsg);
+
+ errorMsg = null;
+ try {
+ FullRevision.parseRevision("5 .6 .7");
+ fail("FullRevision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("Invalid full revision: 5 .6 .7", errorMsg);
+
+ errorMsg = null;
+ try {
+ FullRevision.parseRevision("5.0.0 preview 1");
+ fail("FullRevision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("Invalid full revision: 5.0.0 preview 1", errorMsg);
+
+ errorMsg = null;
+ try {
+ FullRevision.parseRevision(" 5.1.2 rc 42 ");
+ fail("FullRevision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("Invalid full revision: 5.1.2 rc 42 ", errorMsg);
+ }
+
+ public final void testCompareTo() {
+ FullRevision s4 = new FullRevision(4);
+ FullRevision i4 = new FullRevision(4);
+ FullRevision g5 = new FullRevision(5, 1, 0, 6);
+ FullRevision y5 = new FullRevision(5);
+ FullRevision c5 = new FullRevision(5, 1, 0, 6);
+ FullRevision o5 = new FullRevision(5, 0, 0, 7);
+ FullRevision p5 = new FullRevision(5, 1, 0, 0);
+
+ assertEquals(s4, i4); // 4.0.0-0 == 4.0.0-0
+ assertEquals(g5, c5); // 5.1.0-6 == 5.1.0-6
+
+ assertFalse(y5.equals(p5)); // 5.0.0-0 != 5.1.0-0
+ assertFalse(g5.equals(p5)); // 5.1.0-6 != 5.1.0-0
+ assertTrue (s4.compareTo(i4) == 0); // 4.0.0-0 == 4.0.0-0
+ assertTrue (s4.compareTo(y5) < 0); // 4.0.0-0 < 5.0.0-0
+ assertTrue (y5.compareTo(y5) == 0); // 5.0.0-0 == 5.0.0-0
+ assertTrue (y5.compareTo(p5) < 0); // 5.0.0-0 < 5.1.0-0
+ assertTrue (o5.compareTo(y5) < 0); // 5.0.0-7 < 5.0.0-0
+ assertTrue (p5.compareTo(p5) == 0); // 5.1.0-0 == 5.1.0-0
+ assertTrue (c5.compareTo(p5) < 0); // 5.1.0-6 < 5.1.0-0
+ assertTrue (p5.compareTo(c5) > 0); // 5.1.0-0 > 5.1.0-6
+ assertTrue (p5.compareTo(o5) > 0); // 5.1.0-0 > 5.0.0-7
+ assertTrue (c5.compareTo(o5) > 0); // 5.1.0-6 > 5.0.0-7
+ assertTrue (o5.compareTo(o5) == 0); // 5.0.0-7 > 5.0.0-7
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java
new file mode 100755
index 0000000..b77caad
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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.sdklib.internal.repository.packages;
+
+import junit.framework.TestCase;
+
+public class MajorRevisionTest extends TestCase {
+
+ public final void testMajorRevision() {
+ MajorRevision p = new MajorRevision(5);
+ assertEquals(5, p.getMajor());
+ assertEquals(FullRevision.IMPLICIT_MINOR_REV, p.getMinor());
+ assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
+ assertEquals(FullRevision.NOT_A_PREVIEW, p.getPreview());
+ assertFalse (p.isPreview());
+ assertEquals("5", p.toShortString());
+ assertEquals(p, MajorRevision.parseRevision("5"));
+ assertEquals("5", p.toString());
+
+ assertEquals(new FullRevision(5, 0, 0, 0), p);
+ }
+
+ public final void testParseError() {
+ String errorMsg = null;
+ try {
+ MajorRevision.parseRevision("5.0.0");
+ fail("MajorRevision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("For input string: \"5.0.0\"", errorMsg);
+ }
+
+ public final void testCompareTo() {
+ MajorRevision s4 = new MajorRevision(4);
+ MajorRevision i4 = new MajorRevision(4);
+ FullRevision g5 = new FullRevision (5, 1, 0, 6);
+ MajorRevision y5 = new MajorRevision(5);
+ FullRevision c5 = new FullRevision (5, 1, 0, 6);
+ FullRevision o5 = new FullRevision (5, 0, 0, 7);
+ FullRevision p5 = new FullRevision (5, 1, 0, 0);
+
+ assertEquals(s4, i4); // 4.0.0-0 == 4.0.0-0
+ assertEquals(g5, c5); // 5.1.0-6 == 5.1.0-6
+
+ assertFalse(y5.equals(p5)); // 5.0.0-0 != 5.1.0-0
+ assertFalse(g5.equals(p5)); // 5.1.0-6 != 5.1.0-0
+ assertTrue (s4.compareTo(i4) == 0); // 4.0.0-0 == 4.0.0-0
+ assertTrue (s4.compareTo(y5) < 0); // 4.0.0-0 < 5.0.0-0
+ assertTrue (y5.compareTo(y5) == 0); // 5.0.0-0 == 5.0.0-0
+ assertTrue (y5.compareTo(p5) < 0); // 5.0.0-0 < 5.1.0-0
+ assertTrue (o5.compareTo(y5) < 0); // 5.0.0-7 < 5.0.0-0
+ assertTrue (p5.compareTo(p5) == 0); // 5.1.0-0 == 5.1.0-0
+ assertTrue (c5.compareTo(p5) < 0); // 5.1.0-6 < 5.1.0-0
+ assertTrue (p5.compareTo(c5) > 0); // 5.1.0-0 > 5.1.0-6
+ assertTrue (p5.compareTo(o5) > 0); // 5.1.0-0 > 5.0.0-7
+ assertTrue (c5.compareTo(o5) > 0); // 5.1.0-6 > 5.0.0-7
+ assertTrue (o5.compareTo(o5) == 0); // 5.0.0-7 > 5.0.0-7
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
index 00986a4..adffe2e 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
@@ -124,7 +124,7 @@
Properties props = super.createProps();
// MinToolsPackage properties
- props.setProperty(PkgProps.MIN_TOOLS_REV, "3");
+ props.setProperty(PkgProps.MIN_TOOLS_REV, "3.0.1");
return props;
}
@@ -133,6 +133,6 @@
super.testCreatedPackage(p);
// MinToolsPackage properties
- assertEquals(3, p.getMinToolsRevision());
+ assertEquals("3.0.1", p.getMinToolsRevision().toShortString());
}
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
index f1e4344..70f57b4 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
@@ -30,7 +30,7 @@
* A mock empty package, of no particular subpackage type.
* {@link #sameItemAs(Package)} will return true if these packages have the same handle.
*/
-public class MockEmptyPackage extends Package {
+public class MockEmptyPackage extends MajorRevisionPackage {
private final String mTestHandle;
/**
@@ -134,7 +134,7 @@
public String getShortDescription() {
StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
sb.append(" '").append(mTestHandle).append('\'');
- if (getRevision() > 0) {
+ if (getRevision().getMajor() > 0) {
sb.append(" rev=").append(getRevision());
}
return sb.toString();
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockExtraPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
index 6225935..d6a98a0 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
@@ -18,9 +18,8 @@
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
import java.util.Properties;
@@ -73,7 +72,7 @@
private static Properties createProps(int min_platform_tools_rev) {
Properties props = new Properties();
- props.setProperty(ToolPackage.PROP_MIN_PLATFORM_TOOLS_REV,
+ props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
Integer.toString((min_platform_tools_rev)));
return props;
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
index fc01b3c..e896de6 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
@@ -18,7 +18,6 @@
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
import com.android.sdklib.internal.repository.sources.SdkSource;
/**
@@ -57,5 +56,25 @@
"foo" // archiveOsPath
);
}
+
+ /**
+ * Creates a {@link MockPlatformToolPackage} with the given revision and hardcoded defaults
+ * for everything else.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MockPlatformToolPackage(SdkSource source, FullRevision revision) {
+ super(
+ source, // source,
+ FullRevisionPackageTest.createProps(revision), // props,
+ revision.getMajor(),
+ null, // license,
+ "desc", // description,
+ "url", // descUrl,
+ Os.getCurrentOs(), // archiveOs,
+ Arch.getCurrentArch(), // archiveArch,
+ "foo" // archiveOsPath
+ );
+ }
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java
index 534dad8..1726bbd 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java
@@ -33,12 +33,12 @@
* By design, this package contains one and only one archive.
*/
public MockSystemImagePackage(MockPlatformPackage basePlatform, int revision, String abi) {
- super(basePlatform.getVersion(),
+ super(basePlatform.getAndroidVersion(),
revision,
abi,
null /*props*/,
String.format("/sdk/system-images/android-%s/%s",
- basePlatform.getVersion().getApiString(), abi));
+ basePlatform.getAndroidVersion().getApiString(), abi));
}
/**
@@ -53,11 +53,11 @@
int revision,
String abi) {
super(source,
- basePlatform.getVersion(),
+ basePlatform.getAndroidVersion(),
revision,
abi,
null /*props*/,
String.format("/sdk/system-images/android-%s/%s",
- basePlatform.getVersion().getApiString(), abi));
+ basePlatform.getAndroidVersion().getApiString(), abi));
}
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockToolPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockToolPackage.java
index e1da422..155946c 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockToolPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockToolPackage.java
@@ -20,6 +20,7 @@
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.packages.ToolPackage;
import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
import java.util.Properties;
@@ -46,10 +47,10 @@
* <p/>
* By design, this creates a package with one and only one archive.
*/
- public MockToolPackage(SdkSource source, int revision, int min_platform_tools_rev) {
+ public MockToolPackage(SdkSource source, int revision, int minPlatformToolsRev) {
super(
source, // source,
- createProps(min_platform_tools_rev), // props,
+ createProps(null /*version*/, minPlatformToolsRev), // props,
revision,
null, // license,
"desc", // description,
@@ -60,10 +61,33 @@
);
}
- private static Properties createProps(int min_platform_tools_rev) {
- Properties props = new Properties();
- props.setProperty(ToolPackage.PROP_MIN_PLATFORM_TOOLS_REV,
- Integer.toString((min_platform_tools_rev)));
+ /**
+ * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults
+ * for everything else.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MockToolPackage(
+ SdkSource source,
+ FullRevision revision,
+ int minPlatformToolsRev) {
+ super(
+ source, // source,
+ createProps(revision, minPlatformToolsRev), // props,
+ revision.getMajor(),
+ null, // license,
+ "desc", // description,
+ "url", // descUrl,
+ Os.getCurrentOs(), // archiveOs,
+ Arch.getCurrentArch(), // archiveArch,
+ "foo" // archiveOsPath
+ );
+ }
+
+ private static Properties createProps(FullRevision revision, int minPlatformToolsRev) {
+ Properties props = FullRevisionPackageTest.createProps(revision);
+ props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
+ Integer.toString((minPlatformToolsRev)));
return props;
}
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PackageTest.java
index ce41a4d..f4d7b56 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PackageTest.java
@@ -39,7 +39,7 @@
protected static final String LOCAL_ARCHIVE_PATH = "/local/archive/path";
/** Local class used to test the abstract Package class */
- protected static class MockPackage extends Package {
+ protected static class MockPackage extends MajorRevisionPackage {
public MockPackage(
SdkSource source,
Properties props,
@@ -155,7 +155,7 @@
*/
protected void testCreatedPackage(Package p) {
// Package properties
- assertEquals(42, p.getRevision());
+ assertEquals("42", p.getRevision().toShortString());
assertEquals("The License", p.getLicense());
assertEquals("Some description.", p.getDescription());
assertEquals("http://description/url", p.getDescUrl());
@@ -197,4 +197,20 @@
"Vendor Path, revision 5]",
Arrays.toString(list.toArray()));
}
+
+ public void testGetPropertyInt() {
+ Properties p = new Properties();
+
+ assertEquals(42, Package.getPropertyInt(p, "key", 42));
+
+ p.setProperty("key", "");
+ assertEquals(43, Package.getPropertyInt(p, "key", 43));
+
+ p.setProperty("key", "I am not a number");
+ assertEquals(44, Package.getPropertyInt(p, "key", 44));
+
+ p.setProperty("key", "6");
+ assertEquals(6, Package.getPropertyInt(p, "key", 45));
+ }
+
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
index a0b4ab7..acb57ee 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
@@ -75,7 +75,7 @@
super.testCreatedPackage(p);
// Package properties
- assertEquals("API 5", p.getVersion().toString());
+ assertEquals("API 5", p.getAndroidVersion().toString());
assertEquals("armeabi", p.getIncludedAbi());
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SourcePackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
index 66f37dc..d87eab3 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
@@ -79,7 +79,7 @@
super.testCreatedPackage(p);
// SourcePackageTest properties
- assertEquals("API 5", p.getVersion().toString());
+ assertEquals("API 5", p.getAndroidVersion().toString());
}
// ----
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java
index 14e1c3e..c32b81f 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java
@@ -85,7 +85,7 @@
super.testCreatedPackage(p);
// SystemImagePackage properties
- assertEquals("API 5", p.getVersion().toString());
+ assertEquals("API 5", p.getAndroidVersion().toString());
assertEquals("armeabi-v7a", p.getAbi());
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
index b9a3436..d866018 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
@@ -22,6 +22,7 @@
import com.android.sdklib.internal.repository.MockMonitor;
import com.android.sdklib.internal.repository.packages.AddonPackage;
import com.android.sdklib.internal.repository.packages.ExtraPackage;
+import com.android.sdklib.internal.repository.packages.IMinToolsDependency;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.sources.SdkAddonSource;
import com.android.sdklib.repository.SdkAddonConstants;
@@ -564,6 +565,154 @@
}
/**
+ * Validate we can load a valid add-on schema version 5
+ */
+ public void testLoadAddonXml_5() throws Exception {
+ InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_5.xml");
+
+ // guess the version from the XML document
+ int version = mSource._getXmlSchemaVersion(xmlStream);
+ assertEquals(5, version);
+
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
+
+ String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
+ assertEquals(Boolean.TRUE, validatorFound[0]);
+ assertEquals(null, validationError[0]);
+ assertEquals(SdkAddonConstants.getSchemaUri(5), uri);
+
+ // Validation was successful, load the document
+ MockMonitor monitor = new MockMonitor();
+ Document doc = mSource._getDocument(xmlStream, monitor);
+ assertNotNull(doc);
+
+ // Get the packages
+ assertTrue(mSource._parsePackages(doc, uri, monitor));
+
+ assertEquals("Found My First add-on, Android API 1, revision 1\n" +
+ "Found My Second add-on, Android API 2, revision 42\n" +
+ "Found This add-on has no libraries, Android API 4, revision 3\n" +
+ "Found Random name, not an id!, revision 43 (Obsolete)\n" +
+ "Found Yet another extra, by Android, revision 2\n" +
+ "Found . -..- - .-. .-, revision 2 (Obsolete)\n",
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
+
+ // check the packages we found... we expected to find 6 packages with each at least
+ // one archive.
+ // Note the order doesn't necessary match the one from the
+ // assertEquald(getCapturedVerboseLog) because packages are sorted using the
+ // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
+ Package[] pkgs = mSource.getPackages();
+
+ assertEquals(6, pkgs.length);
+ for (Package p : pkgs) {
+ assertTrue(p.getArchives().length >= 1);
+ }
+
+ // Check the addon packages: vendor/name id vs display
+ ArrayList<String> addonNames = new ArrayList<String>();
+ ArrayList<String> addonVendors = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof AddonPackage) {
+ AddonPackage ap = (AddonPackage) p;
+ addonNames.add(ap.getNameId() + "/" + ap.getDisplayName());
+ addonVendors.add(ap.getVendorId() + "/" + ap.getDisplayVendor());
+ }
+ }
+ // Addons are sorted by addon/vendor id and thus their order differs from the
+ // XML or the parsed package list.
+ assertEquals(
+ "[no_libs/This add-on has no libraries, " +
+ "My_Second_add-on/My Second add-on, " +
+ "My_First_add-on/My First add-on]",
+ Arrays.toString(addonNames.toArray()));
+ assertEquals(
+ "[Joe_Bar/Joe Bar, " +
+ "John_Deer/John Deer, " +
+ "John_Doe/John Doe]",
+ Arrays.toString(addonVendors.toArray()));
+
+ // Check the layoutlib of the platform packages.
+ ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
+ for (Package p : pkgs) {
+ if (p instanceof AddonPackage) {
+ layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());
+ }
+ }
+ assertEquals(
+ "[Pair [first=3, second=42], " + // for #3 "This add-on has no libraries"
+ "Pair [first=0, second=0], " + // for #2 "My Second add-on"
+ "Pair [first=5, second=0]]", // for #1 "My First add-on"
+ Arrays.toString(layoutlibVers.toArray()));
+
+
+ // Check the extra packages: path, vendor, install folder, old-paths
+ final String osSdkPath = "SDK";
+ final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
+
+ ArrayList<String> extraPaths = new ArrayList<String>();
+ ArrayList<String> extraVendors = new ArrayList<String>();
+ ArrayList<File> extraInstall = new ArrayList<File>();
+ ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
+ for (Package p : pkgs) {
+ if (p instanceof ExtraPackage) {
+ ExtraPackage ep = (ExtraPackage) p;
+ // combine path and old-paths in the form "path [old_path1, old_path2]"
+ extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
+ extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
+ extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
+
+ ArrayList<String> filePaths = new ArrayList<String>();
+ for (String filePath : ep.getProjectFiles()) {
+ filePaths.add(filePath);
+ }
+ extraFilePaths.add(filePaths);
+ }
+ }
+ // Extras are sorted by vendor-id/path and thus their order differs from the
+ // XML or the parsed package list.
+ assertEquals(
+ "[extra0000005f [], " + // for extra #3
+ "extra_api_dep [path1, old_path2, oldPath3], " + // for extra #2
+ "usb_driver []]", // for extra #1
+ Arrays.toString(extraPaths.toArray()));
+ assertEquals(
+ "[____/____, " +
+ "android_vendor/Android Vendor, " +
+ "cyclop/The big bus]",
+ Arrays.toString(extraVendors.toArray()));
+ assertEquals(
+ ("[SDK/extras/____/extra0000005f, " +
+ "SDK/extras/android_vendor/extra_api_dep, " +
+ "SDK/extras/cyclop/usb_driver]").replace('/', File.separatorChar),
+ Arrays.toString(extraInstall.toArray()));
+ assertEquals(
+ "[[], " +
+ "[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +
+ "[]]",
+ Arrays.toString(extraFilePaths.toArray()));
+
+
+ // Check the min-tools-rev
+ ArrayList<String> minToolsRevs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof IMinToolsDependency) {
+ minToolsRevs.add(p.getListDescription() + ": " +
+ ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
+ }
+ }
+ assertEquals(
+ "[. -..- - .-. .- (Obsolete): 3.0.1, " +
+ "Yet another extra, by Android: 3, " +
+ "Random name, not an id! (Obsolete): 3.2.1 rc42]",
+ Arrays.toString(minToolsRevs.toArray()));
+ }
+
+ /**
* Returns an SdkLib file resource as a {@link ByteArrayInputStream},
* which has the advantage that we can use {@link InputStream#reset()} on it
* at any time to read it multiple times.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
index 1bc9639..413902a 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
@@ -21,6 +21,8 @@
import com.android.sdklib.internal.repository.MockEmptySdkManager;
import com.android.sdklib.internal.repository.MockMonitor;
import com.android.sdklib.internal.repository.packages.ExtraPackage;
+import com.android.sdklib.internal.repository.packages.IMinPlatformToolsDependency;
+import com.android.sdklib.internal.repository.packages.IMinToolsDependency;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.packages.PlatformPackage;
import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
@@ -209,7 +211,7 @@
/**
* Validate we can still load an old repository in schema version 1
*/
- public void testLoadOldXml_1() throws Exception {
+ public void testLoadXml_1() throws Exception {
InputStream xmlStream = getTestResource(
"/com/android/sdklib/testdata/repository_sample_1.xml");
@@ -287,7 +289,7 @@
/**
* Validate we can still load an old repository in schema version 2
*/
- public void testLoadOldXml_2() throws Exception {
+ public void testLoadXml_2() throws Exception {
InputStream xmlStream = getTestResource(
"/com/android/sdklib/testdata/repository_sample_2.xml");
@@ -678,7 +680,7 @@
for (Package p : pkgs) {
if (p instanceof SystemImagePackage) {
SystemImagePackage sip = (SystemImagePackage) p;
- String v = sip.getVersion().getApiString();
+ String v = sip.getAndroidVersion().getApiString();
String a = sip.getAbi();
sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$
}
@@ -694,7 +696,7 @@
for (Package p : pkgs) {
if (p instanceof SourcePackage) {
SourcePackage sp = (SourcePackage) p;
- String v = sp.getVersion().getApiString();
+ String v = sp.getAndroidVersion().getApiString();
sourceVersion.add(v);
}
}
@@ -820,7 +822,7 @@
for (Package p : pkgs) {
if (p instanceof SystemImagePackage) {
SystemImagePackage sip = (SystemImagePackage) p;
- String v = sip.getVersion().getApiString();
+ String v = sip.getAndroidVersion().getApiString();
String a = sip.getAbi();
sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$
}
@@ -837,7 +839,7 @@
for (Package p : pkgs) {
if (p instanceof SourcePackage) {
SourcePackage sp = (SourcePackage) p;
- String v = sp.getVersion().getApiString();
+ String v = sp.getAndroidVersion().getApiString();
sourceVersion.add(v);
}
}
@@ -847,6 +849,183 @@
}
/**
+ * Validate what we can load from repository in schema version 6
+ */
+ public void testLoadXml_7() throws Exception {
+ InputStream xmlStream = getTestResource(
+ "/com/android/sdklib/testdata/repository_sample_7.xml");
+
+ // guess the version from the XML document
+ int version = mSource._getXmlSchemaVersion(xmlStream);
+ assertEquals(7, version);
+
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
+
+ String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
+ assertEquals(Boolean.TRUE, validatorFound[0]);
+ assertEquals(null, validationError[0]);
+ assertEquals(SdkRepoConstants.getSchemaUri(7), uri);
+
+ // Validation was successful, load the document
+ MockMonitor monitor = new MockMonitor();
+ Document doc = mSource._getDocument(xmlStream, monitor);
+ assertNotNull(doc);
+
+ // Get the packages
+ assertTrue(mSource._parsePackages(doc, uri, monitor));
+
+ assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
+ "Found Documentation for Android SDK, API 1, revision 1\n" +
+ "Found Sources for Android SDK, API 1, revision 1\n" +
+ "Found SDK Platform Android 1.1, API 2, revision 12\n" +
+ "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
+ "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
+ "Found Sources for Android SDK, API 2, revision 2\n" +
+ "Found SDK Platform Android Pastry Preview, revision 3\n" +
+ "Found Android SDK Tools, revision 1.2.3 rc4\n" +
+ "Found Documentation for Android SDK, API 2, revision 42\n" +
+ "Found Android SDK Tools, revision 42\n" +
+ "Found Android SDK Platform-tools, revision 3 rc5\n" +
+ "Found Samples for SDK API 14, revision 24 (Obsolete)\n" +
+ "Found Samples for SDK API 14, revision 25 (Obsolete)\n" +
+ "Found ARM EABI System Image, Android API 42, revision 12\n" +
+ "Found Mips System Image, Android API 42, revision 12\n" +
+ "Found Sources for Android SDK, API 42, revision 12\n",
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
+
+ // check the packages we found... we expected to find 13 packages with each at least
+ // one archive.
+ // Note the order doesn't necessary match the one from the
+ // assertEquald(getCapturedVerboseLog) because packages are sorted using the
+ // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
+ Package[] pkgs = mSource.getPackages();
+
+ assertEquals(17, pkgs.length);
+ for (Package p : pkgs) {
+ assertTrue(p.getArchives().length >= 1);
+ }
+
+ // Check the layoutlib & included-abi of the platform packages.
+ ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
+ ArrayList<String> includedAbi = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof PlatformPackage) {
+ layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
+ String abi = ((PlatformPackage) p).getIncludedAbi();
+ includedAbi.add(abi == null ? "(null)" : abi);
+ }
+ }
+ assertEquals(
+ "[Pair [first=1, second=0], " + // platform API 5 preview
+ "Pair [first=5, second=31415], " + // platform API 2
+ "Pair [first=5, second=0]]", // platform API 1
+ Arrays.toString(layoutlibVers.toArray()));
+ assertEquals(
+ "[(null), " + // platform API 5 preview
+ "x86, " + // platform API 2
+ "armeabi]", // platform API 1
+ Arrays.toString(includedAbi.toArray()));
+
+ // Check the extra packages path, vendor, install folder, project-files, old-paths
+
+ final String osSdkPath = "SDK";
+ final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
+
+ ArrayList<String> extraPaths = new ArrayList<String>();
+ ArrayList<String> extraVendors = new ArrayList<String>();
+ ArrayList<File> extraInstall = new ArrayList<File>();
+ ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
+ for (Package p : pkgs) {
+ if (p instanceof ExtraPackage) {
+ ExtraPackage ep = (ExtraPackage) p;
+ // combine path and old-paths in the form "path [old_path1, old_path2]"
+ extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
+ extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
+ extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
+
+ ArrayList<String> filePaths = new ArrayList<String>();
+ for (String filePath : ep.getProjectFiles()) {
+ filePaths.add(filePath);
+ }
+ extraFilePaths.add(filePaths);
+ }
+ }
+
+ // There are no extra packages anymore in repository-6
+ assertEquals("[]", Arrays.toString(extraPaths.toArray()));
+ assertEquals("[]", Arrays.toString(extraVendors.toArray()));
+ assertEquals("[]", Arrays.toString(extraInstall.toArray()));
+ assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));
+
+
+ // Check the system-image packages
+ ArrayList<String> sysImgVersionAbi = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SystemImagePackage) {
+ SystemImagePackage sip = (SystemImagePackage) p;
+ String v = sip.getAndroidVersion().getApiString();
+ String a = sip.getAbi();
+ sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$
+ }
+ }
+ assertEquals(
+ "[42 armeabi, " +
+ "42 mips, " +
+ "2 armeabi-v7a, " +
+ "2 x86]",
+ Arrays.toString(sysImgVersionAbi.toArray()));
+
+
+ // Check the source packages
+ ArrayList<String> sourceVersion = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof SourcePackage) {
+ SourcePackage sp = (SourcePackage) p;
+ String v = sp.getAndroidVersion().getApiString();
+ sourceVersion.add(v);
+ }
+ }
+ assertEquals(
+ "[42, 2, 1]",
+ Arrays.toString(sourceVersion.toArray()));
+
+
+ // Check the min-tools-rev
+ ArrayList<String> minToolsRevs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof IMinToolsDependency) {
+ minToolsRevs.add(p.getListDescription() + ": " +
+ ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
+ }
+ }
+ assertEquals(
+ "[SDK Platform Android Pastry Preview: 0, " +
+ "SDK Platform Android 1.1: 0, " +
+ "SDK Platform Android 1.0: 2.0.1, " +
+ "Samples for SDK API 14 (Obsolete): 5, " +
+ "Samples for SDK API 14 (Obsolete): 5.1.2 rc3]",
+ Arrays.toString(minToolsRevs.toArray()));
+
+
+ // Check the min-platform-tools-rev
+ ArrayList<String> minPlatToolsRevs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof IMinPlatformToolsDependency) {
+ minPlatToolsRevs.add(p.getListDescription() + ": " +
+ ((IMinPlatformToolsDependency) p).getMinPlatformToolsRevision().toShortString());
+ }
+ }
+ assertEquals(
+ "[Android SDK Tools: 4, " +
+ "Android SDK Tools: 4 rc5]",
+ Arrays.toString(minPlatToolsRevs.toArray()));
+ }
+
+ /**
* Returns an SdkLib file resource as a {@link ByteArrayInputStream},
* which has the advantage that we can use {@link InputStream#reset()} on it
* at any time to read it multiple times.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/ValidateRepositoryXmlTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
index ce21650..648ff05 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
@@ -162,6 +162,32 @@
}
+ /** Validate a valid sample using namespace version 5 using an InputStream */
+ public void testValidateLocalRepositoryFile6() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/repository_sample_6.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getRepoValidator(6, handler);
+ validator.validate(source);
+ handler.verify();
+
+ }
+
+ /** Validate a valid sample using namespace version 5 using an InputStream */
+ public void testValidateLocalRepositoryFile7() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/repository_sample_7.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getRepoValidator(7, handler);
+ validator.validate(source);
+ handler.verify();
+
+ }
+
// IMPORTANT: each time you add a test here, you should add a corresponding
// test in SdkRepoSourceTest to validate the XML content is parsed correctly.
@@ -276,8 +302,8 @@
String document = "<?xml version=\"1.0\"?>" +
OPEN_TAG_REPO +
"<r:license id=\"lic1\"> some license </r:license> " +
- "<r:tool> <r:uses-license ref=\"lic2\" /> <r:revision>1</r:revision> " +
- "<r:min-platform-tools-rev>1</r:min-platform-tools-rev> " +
+ "<r:tool> <r:uses-license ref=\"lic2\" /> <r:revision> <r:major>1</r:major> </r:revision> " +
+ "<r:min-platform-tools-rev> <r:major>1</r:major> </r:min-platform-tools-rev> " +
"<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
"<r:url>url</r:url> </r:archive> </r:archives> </r:tool>" +
CLOSE_TAG_REPO;
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addon_sample_5.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addon_sample_5.xml
new file mode 100755
index 0000000..47b37be
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addon_sample_5.xml
@@ -0,0 +1,214 @@
+<?xml version="1.0"?>
+<!--
+ * Copyright (C) 2012 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.
+-->
+<sdk:sdk-addon
+ xmlns:sdk="http://schemas.android.com/sdk/android/addon/5">
+
+ <!-- Define a couple of licenses. These will be referenced by uses-license later. -->
+
+ <sdk:license type="text" id="license1">
+ This is the license
+ for this platform.
+ </sdk:license>
+
+ <sdk:license id="license2">
+ Licenses are only of type 'text' right now, so this is implied.
+ </sdk:license>
+
+ <!-- Inner elements must be either platform, add-on, doc or tool.
+ There can be 0 or more of each, in any order. -->
+
+ <sdk:add-on>
+ <sdk:name-id>My_First_add-on</sdk:name-id>
+ <sdk:name-display>My First add-on</sdk:name-display>
+
+ <sdk:vendor-id>John_Doe</sdk:vendor-id>
+ <sdk:vendor-display>John Doe</sdk:vendor-display>
+
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:uses-license ref="license2" />
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/myfirstaddon</sdk:desc-url>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/add-ons/first.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <!-- The libs node is mandatory, however it can be empty. -->
+ <sdk:libs>
+ <sdk:lib>
+ <sdk:name>android.blah.somelib</sdk:name>
+ <sdk:description>The description for this library.</sdk:description>
+ </sdk:lib>
+ <sdk:lib>
+ <!-- sdk:description is optional, name is not -->
+ <sdk:name>com.android.mymaps</sdk:name>
+ </sdk:lib>
+ </sdk:libs>
+ <sdk:layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>0</sdk:revision>
+ </sdk:layoutlib>
+ </sdk:add-on>
+
+ <sdk:add-on>
+ <sdk:name-id>My_Second_add-on</sdk:name-id>
+ <sdk:name-display>My Second add-on</sdk:name-display>
+
+ <sdk:vendor-id>John_Deer</sdk:vendor-id>
+ <sdk:vendor-display>John Deer</sdk:vendor-display>
+
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>42</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/second-42-win.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/second-42-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:libs>
+ <sdk:lib>
+ <sdk:name>android.blah.somelib</sdk:name>
+ <sdk:description>The description for this library.</sdk:description>
+ </sdk:lib>
+ <sdk:lib>
+ <sdk:name>com.android.mymaps</sdk:name>
+ </sdk:lib>
+ </sdk:libs>
+ <sdk:uses-license ref="license2" />
+ <!-- No layoutlib element in this package. It's optional. -->
+ </sdk:add-on>
+
+ <sdk:add-on>
+ <sdk:name-id>no_libs</sdk:name-id>
+ <sdk:name-display>This add-on has no libraries</sdk:name-display>
+
+ <sdk:vendor-id>Joe_Bar</sdk:vendor-id>
+ <sdk:vendor-display>Joe Bar</sdk:vendor-display>
+
+ <sdk:uses-license ref="license2" />
+ <sdk:api-level>4</sdk:api-level>
+ <sdk:revision>3</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/imnotanarchiveimadoctorjim.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <!-- The libs node is mandatory, however it can be empty. -->
+ <sdk:libs />
+ <sdk:layoutlib>
+ <sdk:api>3</sdk:api>
+ <sdk:revision>42</sdk:revision>
+ </sdk:layoutlib>
+ </sdk:add-on>
+
+ <sdk:extra>
+ <sdk:name-display>Random name, not an id!</sdk:name-display>
+
+ <sdk:vendor-id>cyclop</sdk:vendor-id>
+ <sdk:vendor-display>The big bus</sdk:vendor-display>
+
+ <sdk:path>usb_driver</sdk:path>
+ <sdk:uses-license ref="license2" />
+ <sdk:revision>43</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/extraduff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>An Extra package for the USB driver, it will install in $SDK/usb_driver</sdk:description>
+ <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>2</sdk:minor>
+ <sdk:micro>1</sdk:micro>
+ <sdk:preview>42</sdk:preview>
+ </sdk:min-tools-rev>
+ <sdk:obsolete/>
+ </sdk:extra>
+
+ <sdk:extra>
+ <sdk:name-display>Yet another extra, by Android</sdk:name-display>
+
+ <sdk:vendor-id>android_vendor</sdk:vendor-id>
+ <sdk:vendor-display>Android Vendor</sdk:vendor-display>
+
+ <sdk:path>extra_api_dep</sdk:path>
+ <sdk:uses-license ref="license2" />
+ <sdk:revision>2</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/extra_mega_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some extra package that has a min-api-level of 42</sdk:description>
+ <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>3</sdk:major>
+ </sdk:min-tools-rev>
+ <sdk:min-api-level>42</sdk:min-api-level>
+ <sdk:project-files>
+ <sdk:path>v8/veggies_8.jar</sdk:path>
+ <sdk:path>root.jar</sdk:path>
+ <sdk:path>dir1/dir 2 with space/mylib.jar</sdk:path>
+ </sdk:project-files>
+ <sdk:old-paths>path1;old_path2;oldPath3</sdk:old-paths>
+ </sdk:extra>
+
+ <sdk:extra>
+ <sdk:name-display>. -..- - .-. .-</sdk:name-display>
+
+ <sdk:vendor-id>____</sdk:vendor-id>
+ <sdk:vendor-display>____</sdk:vendor-display>
+
+ <sdk:path>____</sdk:path>
+ <sdk:uses-license ref="license2" />
+ <sdk:revision>2</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/extra_mega_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some extra package that has a min-api-level of 42</sdk:description>
+ <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>3</sdk:major>
+ <!-- no minor, assumed to be 0 -->
+ <sdk:micro>1</sdk:micro>
+ </sdk:min-tools-rev>
+ <sdk:min-api-level>42</sdk:min-api-level>
+ <sdk:obsolete></sdk:obsolete>
+ <!-- No project-files element in this package. -->
+ </sdk:extra>
+
+</sdk:sdk-addon>
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_4.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_4.xml
index a201a63..f68b033 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_4.xml
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_4.xml
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<!--
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 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.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_5.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_5.xml
index 4db8b33..471550e 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_5.xml
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_5.xml
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<!--
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 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.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_6.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_6.xml
index e57b831..12be591 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_6.xml
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_6.xml
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<!--
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2012 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.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_7.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_7.xml
new file mode 100755
index 0000000..401521b
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_7.xml
@@ -0,0 +1,371 @@
+<?xml version="1.0"?>
+<!--
+ * Copyright (C) 2012 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.
+-->
+<sdk:sdk-repository
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/7">
+
+ <!-- Define a couple of licenses. These will be referenced by uses-license later. -->
+
+ <sdk:license type="text" id="license1">
+ This is the license
+ for this platform.
+ </sdk:license>
+
+ <sdk:license id="license2">
+ Licenses are only of type 'text' right now, so this is implied.
+ </sdk:license>
+
+ <!-- Inner elements must be either platform, add-on, doc or tool.
+ There can be 0 or more of each, in any order. -->
+
+ <sdk:platform>
+ <sdk:version>1.0</sdk:version>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>3</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+ <sdk:release-note>This is an optional release note
+ for this package. It's a free multi-line text.
+ </sdk:release-note>
+ <sdk:release-url>http://some/url/for/the/release/note.html</sdk:release-url>
+ <sdk:min-tools-rev>
+ <sdk:major>2</sdk:major>
+ <!-- minor is missing and equivalent to 0 -->
+ <sdk:micro>1</sdk:micro>
+ </sdk:min-tools-rev>
+ <!-- The archives node is mandatory and it cannot be empty. -->
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>0</sdk:revision>
+ </sdk:layoutlib>
+ <sdk:included-abi>armeabi</sdk:included-abi>
+ </sdk:platform>
+
+ <sdk:doc>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <!-- the license element is not mandatory. -->
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/docs.html</sdk:desc-url>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/docs/docs1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:doc>
+
+ <sdk:source>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/sources1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+ <sdk:platform>
+ <sdk:version>1.1</sdk:version>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <!-- sdk:description and sdk:desc-url are optional -->
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <!-- arch attribute is optional -->
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-win.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx" arch="ppc">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux" arch="x86">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux" arch="x86_64">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>31415</sdk:revision>
+ </sdk:layoutlib>
+ <sdk:included-abi>x86</sdk:included-abi>
+ </sdk:platform>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:abi>x86</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65535</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:abi>armeabi-v7a</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/armv7/image2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:source>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>2</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65534</sdk:size>
+ <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat1/sources2.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+ <sdk:platform>
+ <sdk:version>Pastry</sdk:version>
+ <sdk:api-level>5</sdk:api-level>
+ <sdk:codename>Pastry</sdk:codename>
+ <sdk:revision>3</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:description>Preview version for Pastry</sdk:description>
+ <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+ <!-- The archives node is mandatory and it cannot be empty. -->
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:layoutlib>
+ <sdk:api>1</sdk:api>
+ </sdk:layoutlib>
+ </sdk:platform>
+
+ <sdk:tool>
+ <sdk:revision>
+ <sdk:major>1</sdk:major>
+ <sdk:minor>2</sdk:minor>
+ <sdk:micro>3</sdk:micro>
+ <sdk:preview>4</sdk:preview>
+ </sdk:revision>
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/tools.html</sdk:desc-url>
+ <sdk:uses-license ref="license1" />
+ <sdk:min-platform-tools-rev>
+ <sdk:major>4</sdk:major>
+ </sdk:min-platform-tools-rev>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/tools1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:tool>
+
+ <sdk:doc>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>42</sdk:revision>
+ <sdk:uses-license ref="license2" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:doc>
+
+ <sdk:tool>
+ <sdk:revision>
+ <sdk:major>42</sdk:major>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:min-platform-tools-rev>
+ <sdk:major>4</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ <sdk:preview>5</sdk:preview>
+ </sdk:min-platform-tools-rev>
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:tool>
+
+ <sdk:platform-tool>
+ <sdk:revision>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>0</sdk:minor>
+ <sdk:micro>0</sdk:micro>
+ <sdk:preview>5</sdk:preview>
+ </sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:platform-tool>
+
+ <sdk:sample>
+ <sdk:api-level>14</sdk:api-level>
+ <sdk:revision>24</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65537</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/sample_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some sample package</sdk:description>
+ <sdk:desc-url>http://www.example.com/sample.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>5</sdk:major>
+ </sdk:min-tools-rev>
+ <sdk:obsolete>This is obsolete</sdk:obsolete>
+ </sdk:sample>
+
+ <sdk:sample>
+ <sdk:api-level>14</sdk:api-level>
+ <sdk:revision>25</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65537</sdk:size>
+ <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/sample_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some sample package</sdk:description>
+ <sdk:desc-url>http://www.example.com/sample.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>5</sdk:major>
+ <sdk:minor>1</sdk:minor>
+ <sdk:micro>2</sdk:micro>
+ <sdk:preview>3</sdk:preview>
+ </sdk:min-tools-rev>
+ <sdk:obsolete>This is obsolete</sdk:obsolete>
+ </sdk:sample>
+
+ <sdk:system-image>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:abi>armeabi</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>1234</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/86/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:system-image>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:abi>mips</sdk:abi>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>12345</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:system-image>
+
+ <sdk:source>
+ <sdk:api-level>42</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>1234</sdk:size>
+ <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+ <sdk:url>http://www.example.com/plat42/source12.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:source>
+
+</sdk:sdk-repository>
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterChooserDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterChooserDialog.java
index b5ab90b..a41a952 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterChooserDialog.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterChooserDialog.java
@@ -19,8 +19,9 @@
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.packages.IPackageVersion;
+import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.FullRevision;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.ui.GridDialog;
@@ -423,28 +424,29 @@
if (aOld != null) {
Package pOld = aOld.getParentPackage();
- int rOld = pOld.getRevision();
- int rNew = pNew.getRevision();
+ FullRevision rOld = pOld.getRevision();
+ FullRevision rNew = pNew.getRevision();
boolean showRev = true;
- if (pNew instanceof IPackageVersion && pOld instanceof IPackageVersion) {
- AndroidVersion vOld = ((IPackageVersion) pOld).getVersion();
- AndroidVersion vNew = ((IPackageVersion) pNew).getVersion();
+ if (pNew instanceof IAndroidVersionProvider &&
+ pOld instanceof IAndroidVersionProvider) {
+ AndroidVersion vOld = ((IAndroidVersionProvider) pOld).getAndroidVersion();
+ AndroidVersion vNew = ((IAndroidVersionProvider) pNew).getAndroidVersion();
if (!vOld.equals(vNew)) {
// Versions are different, so indicate more than just the revision.
- addText(String.format("This update will replace API %1$s revision %2$d with API %3$s revision %4$d.\n\n",
- vOld.getApiString(), rOld,
- vNew.getApiString(), rNew));
+ addText(String.format("This update will replace API %1$s revision %2$s with API %3$s revision %4$s.\n\n",
+ vOld.getApiString(), rOld.toShortString(),
+ vNew.getApiString(), rNew.toShortString()));
showRev = false;
}
}
if (showRev) {
- addText(String.format("This update will replace revision %1$d with revision %2$d.\n\n",
- rOld,
- rNew));
+ addText(String.format("This update will replace revision %1$s with revision %2$s.\n\n",
+ rOld.toShortString(),
+ rNew.toShortString()));
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterLogic.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterLogic.java
index 0b9f396..ba1cd2d 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterLogic.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterLogic.java
@@ -23,20 +23,21 @@
import com.android.sdklib.internal.repository.packages.AddonPackage;
import com.android.sdklib.internal.repository.packages.DocPackage;
import com.android.sdklib.internal.repository.packages.ExtraPackage;
+import com.android.sdklib.internal.repository.packages.FullRevision;
+import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
import com.android.sdklib.internal.repository.packages.IExactApiLevelDependency;
import com.android.sdklib.internal.repository.packages.IMinApiLevelDependency;
import com.android.sdklib.internal.repository.packages.IMinPlatformToolsDependency;
import com.android.sdklib.internal.repository.packages.IMinToolsDependency;
-import com.android.sdklib.internal.repository.packages.IPackageVersion;
import com.android.sdklib.internal.repository.packages.IPlatformDependency;
import com.android.sdklib.internal.repository.packages.MinToolsPackage;
import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
import com.android.sdklib.internal.repository.packages.PlatformPackage;
import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
import com.android.sdklib.internal.repository.packages.SamplePackage;
import com.android.sdklib.internal.repository.packages.SystemImagePackage;
import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdklib.internal.repository.sources.SdkSources;
@@ -184,6 +185,14 @@
return archives;
}
+ private double getRevisionRank(FullRevision rev) {
+ int p = rev.isPreview() ? 999 : 999 - rev.getPreview();
+ return rev.getMajor() +
+ rev.getMinor() / 1000.d +
+ rev.getMicro() / 1000000.d +
+ p / 1000000000.d;
+ }
+
/**
* Finds new packages that the user does not have in his/her local SDK
* and adds them to the list of archives to install.
@@ -212,27 +221,27 @@
ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
// Find the highest platform installed
- float currentPlatformScore = 0;
- float currentSampleScore = 0;
- float currentAddonScore = 0;
- float currentDocScore = 0;
- HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();
+ double currentPlatformScore = 0;
+ double currentSampleScore = 0;
+ double currentAddonScore = 0;
+ double currentDocScore = 0;
+ HashMap<String, Double> currentExtraScore = new HashMap<String, Double>();
if (!includeAll) {
if (localPkgs != null) {
for (Package p : localPkgs) {
- int rev = p.getRevision();
+ double rev = getRevisionRank(p.getRevision());
int api = 0;
boolean isPreview = false;
- if (p instanceof IPackageVersion) {
- AndroidVersion vers = ((IPackageVersion) p).getVersion();
+ if (p instanceof IAndroidVersionProvider) {
+ AndroidVersion vers = ((IAndroidVersionProvider) p).getAndroidVersion();
api = vers.getApiLevel();
isPreview = vers.isPreview();
}
- // The score is 10*api + (1 if preview) + rev/100
+ // The score is 1000*api + (999 if preview) + rev
// This allows previews to rank above a non-preview and
// allows revisions to rank appropriately.
- float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
+ double score = api * 1000 + (isPreview ? 999 : 0) + rev;
if (p instanceof PlatformPackage) {
currentPlatformScore = Math.max(currentPlatformScore, score);
@@ -261,16 +270,16 @@
continue;
}
- int rev = p.getRevision();
+ double rev = getRevisionRank(p.getRevision());
int api = 0;
boolean isPreview = false;
- if (p instanceof IPackageVersion) {
- AndroidVersion vers = ((IPackageVersion) p).getVersion();
+ if (p instanceof IAndroidVersionProvider) {
+ AndroidVersion vers = ((IAndroidVersionProvider) p).getAndroidVersion();
api = vers.getApiLevel();
isPreview = vers.isPreview();
}
- float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
+ double score = api * 1000 + (isPreview ? 999 : 0) + rev;
boolean shouldAdd = false;
if (p instanceof PlatformPackage) {
@@ -282,7 +291,7 @@
} else if (p instanceof ExtraPackage) {
String key = ((ExtraPackage) p).getPath();
shouldAdd = !currentExtraScore.containsKey(key) ||
- score > currentExtraScore.get(key).floatValue();
+ score > currentExtraScore.get(key).doubleValue();
} else if (p instanceof DocPackage) {
// We don't want all the doc, only the most recent one
if (score > currentDocScore) {
@@ -318,7 +327,7 @@
continue;
}
SystemImagePackage sip = (SystemImagePackage) p2;
- if (sip.getVersion().equals(pp.getVersion())) {
+ if (sip.getAndroidVersion().equals(pp.getAndroidVersion())) {
for (Archive a : sip.getArchives()) {
if (a.isCompatible()) {
insertArchive(a,
@@ -673,9 +682,9 @@
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
// This is the requirement to match.
- int rev = pkg.getMinToolsRevision();
+ FullRevision rev = pkg.getMinToolsRevision();
- if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {
+ if (rev.equals(MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED)) {
// Well actually there's no requirement.
return null;
}
@@ -686,7 +695,7 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision() >= rev) {
+ if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
// We found one already installed.
return null;
}
@@ -700,7 +709,7 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision() >= rev) {
+ if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
// The dependency is already scheduled for install, nothing else to do.
return ai;
}
@@ -713,7 +722,7 @@
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision() >= rev) {
+ if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
// It's not already in the list of things to install, so add it now
return insertArchive(a,
outArchives,
@@ -731,7 +740,7 @@
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision() >= rev) {
+ if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
@@ -771,12 +780,12 @@
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
// This is the requirement to match.
- int rev = pkg.getMinPlatformToolsRevision();
+ FullRevision rev = pkg.getMinPlatformToolsRevision();
boolean findMax = false;
ArchiveInfo aiMax = null;
Archive aMax = null;
- if (rev == IMinPlatformToolsDependency.MIN_PLATFORM_TOOLS_REV_INVALID) {
+ if (rev.equals(IMinPlatformToolsDependency.MIN_PLATFORM_TOOLS_REV_INVALID)) {
// The requirement is invalid, which is not supposed to happen since this
// property is mandatory. However in a typical upgrade scenario we can end
// up with the previous updater managing a new package and not dealing
@@ -792,11 +801,11 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformToolPackage) {
- int r = ((PlatformToolPackage) p).getRevision();
- if (findMax && r > rev) {
+ FullRevision r = ((PlatformToolPackage) p).getRevision();
+ if (findMax && r.compareTo(rev) > 0) {
rev = r;
aiMax = ai;
- } else if (!findMax && r >= rev) {
+ } else if (!findMax && r.compareTo(rev) >= 0) {
// We found one already installed.
return null;
}
@@ -810,11 +819,11 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformToolPackage) {
- int r = ((PlatformToolPackage) p).getRevision();
- if (findMax && r > rev) {
+ FullRevision r = ((PlatformToolPackage) p).getRevision();
+ if (findMax && r.compareTo(rev) > 0) {
rev = r;
aiMax = ai;
- } else if (!findMax && r >= rev) {
+ } else if (!findMax && r.compareTo(rev) >= 0) {
// The dependency is already scheduled for install, nothing else to do.
return ai;
}
@@ -827,12 +836,12 @@
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof PlatformToolPackage) {
- int r = ((PlatformToolPackage) p).getRevision();
- if (findMax && r > rev) {
+ FullRevision r = ((PlatformToolPackage) p).getRevision();
+ if (findMax && r.compareTo(rev) > 0) {
rev = r;
aiMax = null;
aMax = a;
- } else if (!findMax && r >= rev) {
+ } else if (!findMax && r.compareTo(rev) >= 0) {
// It's not already in the list of things to install, so add it now
return insertArchive(a,
outArchives,
@@ -850,16 +859,16 @@
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof PlatformToolPackage) {
- int r = ((PlatformToolPackage) p).getRevision();
- if (r >= rev) {
+ FullRevision r = ((PlatformToolPackage) p).getRevision();
+ if (r.compareTo(rev) >= 0) {
// Make sure there's at least one valid archive here
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
- if (findMax && r > rev) {
+ if (findMax && r.compareTo(rev) > 0) {
rev = r;
aiMax = null;
aMax = a;
- } else if (!findMax && r >= rev) {
+ } else if (!findMax && r.compareTo(rev) >= 0) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
return insertArchive(a,
@@ -912,7 +921,7 @@
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
// This is the requirement to match.
- AndroidVersion v = pkg.getVersion();
+ AndroidVersion v = pkg.getAndroidVersion();
// Find a platform that would satisfy the requirement.
@@ -922,7 +931,7 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (v.equals(((PlatformPackage) p).getVersion())) {
+ if (v.equals(((PlatformPackage) p).getAndroidVersion())) {
// We found one already installed.
return null;
}
@@ -936,7 +945,7 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (v.equals(((PlatformPackage) p).getVersion())) {
+ if (v.equals(((PlatformPackage) p).getAndroidVersion())) {
// The dependency is already scheduled for install, nothing else to do.
return ai;
}
@@ -949,7 +958,7 @@
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (v.equals(((PlatformPackage) p).getVersion())) {
+ if (v.equals(((PlatformPackage) p).getAndroidVersion())) {
// It's not already in the list of things to install, so add it now
return insertArchive(a,
outArchives,
@@ -967,7 +976,7 @@
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof PlatformPackage) {
- if (v.equals(((PlatformPackage) p).getVersion())) {
+ if (v.equals(((PlatformPackage) p).getAndroidVersion())) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
@@ -988,7 +997,7 @@
// We end up here if nothing matches. We don't have a good platform to match.
// We need to indicate this addon depends on a missing platform archive
// so that it can be impossible to install later on.
- return new MissingPlatformArchiveInfo(pkg.getVersion());
+ return new MissingPlatformArchiveInfo(pkg.getAndroidVersion());
}
/**
@@ -1025,7 +1034,7 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
+ if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
// We found one already installed.
return null;
}
@@ -1042,7 +1051,7 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
+ if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
if (api > foundApi) {
foundApi = api;
foundAi = ai;
@@ -1065,7 +1074,7 @@
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
+ if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
if (api > foundApi) {
foundApi = api;
foundArchive = a;
@@ -1079,7 +1088,7 @@
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
+ if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
if (api > foundApi) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
@@ -1140,7 +1149,7 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getVersion().equals(api)) {
+ if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
// We found one already installed.
return null;
}
@@ -1155,7 +1164,7 @@
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getVersion().equals(api)) {
+ if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
return ai;
}
}
@@ -1167,7 +1176,7 @@
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getVersion().equals(api)) {
+ if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
// It's not already in the list of things to install, so add it now
return insertArchive(a,
outArchives,
@@ -1185,7 +1194,7 @@
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getVersion().equals(api)) {
+ if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
@@ -1385,7 +1394,7 @@
*/
private static class MissingArchiveInfo extends ArchiveInfo {
- private final int mRevision;
+ private final FullRevision mRevision;
private final String mTitle;
public static final String TITLE_TOOL = "Tools";
@@ -1398,7 +1407,7 @@
* @param title Typically "Tools" or "Platform-tools".
* @param revision The required revision.
*/
- public MissingArchiveInfo(String title, int revision) {
+ public MissingArchiveInfo(String title, FullRevision revision) {
super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
mTitle = title;
mRevision = revision;
@@ -1418,7 +1427,9 @@
@Override
public String getShortDescription() {
- return String.format("Missing Android SDK %1$s, revision %2$d", mTitle, mRevision);
+ return String.format("Missing Android SDK %1$s, revision %2$s",
+ mTitle,
+ mRevision.toShortString());
}
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
index a58e0f0..af40d78 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
@@ -24,31 +24,28 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.repository.AdbWrapper;
-import com.android.sdklib.internal.repository.AddonsListFetcher;
-import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
-import com.android.sdklib.internal.repository.packages.AddonPackage;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.sdklib.internal.repository.sources.SdkAddonSource;
-import com.android.sdklib.internal.repository.sources.SdkRepoSource;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
-import com.android.sdklib.internal.repository.sources.SdkSources;
import com.android.sdklib.internal.repository.DownloadCache;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskFactory;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.LocalSdkParser;
import com.android.sdklib.internal.repository.NullTaskMonitor;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
+import com.android.sdklib.internal.repository.packages.AddonPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
+import com.android.sdklib.internal.repository.packages.ToolPackage;
+import com.android.sdklib.internal.repository.sources.SdkRepoSource;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
+import com.android.sdklib.internal.repository.sources.SdkSources;
import com.android.sdklib.repository.SdkAddonConstants;
-import com.android.sdklib.repository.SdkAddonsListConstants;
import com.android.sdklib.repository.SdkRepoConstants;
import com.android.sdklib.util.LineUtil;
import com.android.sdklib.util.SparseIntArray;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
+import com.android.sdkuilib.internal.repository.sdkman2.PackageLoader;
import com.android.sdkuilib.internal.repository.sdkman2.SdkUpdaterWindowImpl2;
import com.android.sdkuilib.repository.ISdkChangeListener;
@@ -79,27 +76,32 @@
private String mOsSdkRoot;
- private final ISdkLog mSdkLog;
- private ITaskFactory mTaskFactory;
-
- private SdkManager mSdkManager;
- private AvdManager mAvdManager;
- private DownloadCache mDownloadCache; // lazily created in getDownloadCache
private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
private final SdkSources mSources = new SdkSources();
- private ImageFactory mImageFactory;
private final SettingsController mSettingsController;
private final ArrayList<ISdkChangeListener> mListeners = new ArrayList<ISdkChangeListener>();
+ private final ISdkLog mSdkLog;
+ private ITaskFactory mTaskFactory;
private Shell mWindowShell;
- private AndroidLocationException mAvdManagerInitError;
-
+ private SdkManager mSdkManager;
+ private AvdManager mAvdManager;
/**
- * 0 = need to fetch remote addons list once..
- * 1 = fetch succeeded, don't need to do it any more.
- * -1= fetch failed, do it again only if the user requests a refresh
- * or changes the force-http setting.
+ * The current {@link PackageLoader} to use.
+ * Lazily created in {@link #getPackageLoader()}.
*/
- private int mStateFetchRemoteAddonsList;
+ private PackageLoader mPackageLoader;
+ /**
+ * The current {@link DownloadCache} to use.
+ * Lazily created in {@link #getDownloadCache()}.
+ */
+ private DownloadCache mDownloadCache;
+ /**
+ * The current {@link ImageFactory}.
+ * Set via {@link #setImageFactory(ImageFactory)} by the window implementation.
+ * It is null when invoked using the command-line interface.
+ */
+ private ImageFactory mImageFactory;
+ private AndroidLocationException mAvdManagerInitError;
/**
* Creates a new updater data.
@@ -111,7 +113,6 @@
mOsSdkRoot = osSdkRoot;
mSdkLog = sdkLog;
- mDownloadCache = getDownloadCache();
mSettingsController = new SettingsController(this);
initSdk();
@@ -198,6 +199,14 @@
return mWindowShell;
}
+ public PackageLoader getPackageLoader() {
+ // The package loader is lazily initialized here.
+ if (mPackageLoader == null) {
+ mPackageLoader = new PackageLoader(this);
+ }
+ return mPackageLoader;
+ }
+
/**
* Check if any error occurred during initialization.
* If it did, display an error message.
@@ -446,7 +455,7 @@
mOsSdkRoot,
forceHttp,
mSdkManager,
- mDownloadCache,
+ getDownloadCache(),
monitor)) {
// We installed this archive.
newlyInstalledArchives.add(archive);
@@ -698,7 +707,7 @@
includeObsoletes);
if (selectedArchives == null) {
- loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
+ getPackageLoader().loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
ul.addNewPlatforms(
archives,
getSources(),
@@ -733,7 +742,7 @@
*/
private List<ArchiveInfo> getRemoteArchives_NoGUI(boolean includeAll) {
refreshSources(true);
- loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
+ getPackageLoader().loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
List<ArchiveInfo> archives;
SdkUpdaterLogic ul = new SdkUpdaterLogic(this);
@@ -998,9 +1007,7 @@
@Override
public void run(ITaskMonitor monitor) {
- if (mStateFetchRemoteAddonsList <= 0) {
- loadRemoteAddonsListInTask(monitor);
- }
+ getPackageLoader().loadRemoteAddonsList(monitor);
SdkSource[] sources = mSources.getAllSources();
monitor.setDescription("Refresh Sources");
@@ -1009,7 +1016,7 @@
if (forceFetching ||
source.getPackages() != null ||
source.getFetchError() != null) {
- source.load(mDownloadCache, monitor.createSubMonitor(1), forceHttp);
+ source.load(getDownloadCache(), monitor.createSubMonitor(1), forceHttp);
}
monitor.incProgress(1);
}
@@ -1018,67 +1025,6 @@
}
/**
- * Loads the remote add-ons list.
- */
- public void loadRemoteAddonsList(ITaskMonitor monitor) {
-
- if (mStateFetchRemoteAddonsList != 0) {
- return;
- }
-
- mTaskFactory.start("Load Add-ons List", monitor, new ITask() {
- @Override
- public void run(ITaskMonitor subMonitor) {
- loadRemoteAddonsListInTask(subMonitor);
- }
- });
- }
-
- private void loadRemoteAddonsListInTask(ITaskMonitor monitor) {
- mStateFetchRemoteAddonsList = -1;
-
- String url = SdkAddonsListConstants.URL_ADDON_LIST;
-
- // We override SdkRepoConstants.URL_GOOGLE_SDK_SITE if this is defined
- String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
- if (baseUrl != null) {
- if (baseUrl.length() > 0 && baseUrl.endsWith("/")) { //$NON-NLS-1$
- if (url.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
- url = baseUrl + url.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
- }
- } else {
- monitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl); //$NON-NLS-1$
- }
- }
-
- if (getSettingsController().getForceHttp()) {
- url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- // Hook to bypass loading 3rd party addons lists.
- boolean fetch3rdParties = System.getenv("SDK_SKIP_3RD_PARTIES") == null;
-
- AddonsListFetcher fetcher = new AddonsListFetcher();
- Site[] sites = fetcher.fetch(url, mDownloadCache, monitor);
- if (sites != null) {
- mSources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
-
- if (fetch3rdParties) {
- for (Site s : sites) {
- mSources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
- new SdkAddonSource(s.getUrl(), s.getUiName()));
- }
- }
-
- mSources.notifyChangeListeners();
-
- mStateFetchRemoteAddonsList = 1;
- }
-
- monitor.setDescription("Fetched Add-ons List successfully");
- }
-
- /**
* Safely invoke all the registered {@link ISdkChangeListener#onSdkLoaded()}.
* This can be called from any thread.
*/
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/AdtUpdateDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/AdtUpdateDialog.java
index d619ea0..ba9bce7 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/AdtUpdateDialog.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/AdtUpdateDialog.java
@@ -76,7 +76,7 @@
private Map<Package, File> mResultPaths = null;
private SettingsController mSettingsController;
private PackageFilter mPackageFilter;
- private PackageLoader mPackageMananger;
+ private PackageLoader mPackageLoader;
private ProgressBar mProgressBar;
private Label mStatusText;
@@ -221,12 +221,12 @@
mUpdaterData.broadcastOnSdkLoaded();
- mPackageMananger = new PackageLoader(mUpdaterData);
+ mPackageLoader = new PackageLoader(mUpdaterData);
}
@Override
protected void eventLoop() {
- mPackageMananger.loadPackagesWithInstallTask(
+ mPackageLoader.loadPackagesWithInstallTask(
mPackageFilter.installFlags(),
new IAutoInstallTask() {
@Override
@@ -346,7 +346,7 @@
boolean accept(Package pkg) {
if (pkg instanceof PlatformPackage) {
PlatformPackage pp = (PlatformPackage) pkg;
- AndroidVersion v = pp.getVersion();
+ AndroidVersion v = pp.getAndroidVersion();
return !v.isPreview() && v.getApiLevel() == mApiLevel;
}
return false;
@@ -359,7 +359,7 @@
pkg instanceof PlatformPackage &&
!pkg.isLocal()) {
PlatformPackage pp = (PlatformPackage) pkg;
- AndroidVersion v = pp.getVersion();
+ AndroidVersion v = pp.getAndroidVersion();
if (!v.isPreview()) {
int api = v.getApiLevel();
if (api > mApiLevel) {
@@ -388,7 +388,7 @@
if (!pkg.isLocal()) {
if (pkg instanceof PlatformPackage) {
PlatformPackage pp = (PlatformPackage) pkg;
- AndroidVersion v = pp.getVersion();
+ AndroidVersion v = pp.getAndroidVersion();
if (!v.isPreview()) {
int level = v.getApiLevel();
if ((mFindMaxApi && level == mMaxApiLevel) ||
@@ -418,7 +418,7 @@
pkg instanceof PlatformPackage &&
!pkg.isLocal()) {
PlatformPackage pp = (PlatformPackage) pkg;
- AndroidVersion v = pp.getVersion();
+ AndroidVersion v = pp.getAndroidVersion();
if (!v.isPreview()) {
int api = v.getApiLevel();
if (api > mMaxApiLevel) {
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackageLoader.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackageLoader.java
index 4ac03e9..ecbefb7 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackageLoader.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackageLoader.java
@@ -16,6 +16,8 @@
package com.android.sdkuilib.internal.repository.sdkman2;
+import com.android.sdklib.internal.repository.AddonsListFetcher;
+import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
import com.android.sdklib.internal.repository.DownloadCache;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
@@ -23,7 +25,12 @@
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
+import com.android.sdklib.internal.repository.sources.SdkAddonSource;
import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
+import com.android.sdklib.internal.repository.sources.SdkSources;
+import com.android.sdklib.repository.SdkAddonsListConstants;
+import com.android.sdklib.repository.SdkRepoConstants;
import com.android.sdkuilib.internal.repository.UpdaterData;
import org.eclipse.swt.widgets.Display;
@@ -39,13 +46,30 @@
* Loads packages fetched from the remote SDK Repository and keeps track
* of their state compared with the current local SDK installation.
*/
-class PackageLoader {
+public class PackageLoader {
+ /** The update data context. Never null. */
private final UpdaterData mUpdaterData;
/**
+ * The {@link DownloadCache} override. Can be null, in which case the one from
+ * {@link UpdaterData} is used instead.
+ * @see #getDownloadCache()
+ */
+ private final DownloadCache mOverrideCache;
+
+ /**
+ * 0 = need to fetch remote addons list once..
+ * 1 = fetch succeeded, don't need to do it any more.
+ * -1= fetch failed, do it again only if the user requests a refresh
+ * or changes the force-http setting.
+ */
+ private int mStateFetchRemoteAddonsList;
+
+
+ /**
* Interface for the callback called by
- * {@link PackageLoader#loadPackages(DownloadCache, ISourceLoadedCallback)}.
+ * {@link PackageLoader#loadPackages(ISourceLoadedCallback)}.
* <p/>
* After processing each source, the package loader calls {@link #onUpdateSource}
* with the list of packages found in that source.
@@ -128,12 +152,27 @@
}
/**
- * Creates a new PackageManager associated with the given {@link UpdaterData}.
+ * Creates a new PackageManager associated with the given {@link UpdaterData}
+ * and using the {@link UpdaterData}'s default {@link DownloadCache}.
*
* @param updaterData The {@link UpdaterData}. Must not be null.
*/
public PackageLoader(UpdaterData updaterData) {
mUpdaterData = updaterData;
+ mOverrideCache = null;
+ }
+
+ /**
+ * Creates a new PackageManager associated with the given {@link UpdaterData}
+ * but using the specified {@link DownloadCache} instead of the one from
+ * {@link UpdaterData}.
+ *
+ * @param updaterData The {@link UpdaterData}. Must not be null.
+ * @param cache The {@link DownloadCache} to use instead of the one from {@link UpdaterData}.
+ */
+ public PackageLoader(UpdaterData updaterData, DownloadCache cache) {
+ mUpdaterData = updaterData;
+ mOverrideCache = cache;
}
/**
@@ -145,20 +184,12 @@
* after each source is finished loaded. In return the callback tells the loader
* whether to continue loading sources.
*/
- public void loadPackages(
- DownloadCache downloadCache,
- final ISourceLoadedCallback sourceLoadedCallback) {
+ public void loadPackages(final ISourceLoadedCallback sourceLoadedCallback) {
try {
if (mUpdaterData == null) {
return;
}
- if (downloadCache == null) {
- downloadCache = mUpdaterData.getDownloadCache();
- }
-
- final DownloadCache downloadCache2 = downloadCache;
-
mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() {
@Override
public void run(ITaskMonitor monitor) {
@@ -176,7 +207,7 @@
// get remote packages
boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp();
- mUpdaterData.loadRemoteAddonsList(monitor.createSubMonitor(1));
+ loadRemoteAddonsList(monitor.createSubMonitor(1));
SdkSource[] sources = mUpdaterData.getSources().getAllSources();
try {
@@ -186,7 +217,7 @@
for (SdkSource source : sources) {
Package[] pkgs = source.getPackages();
if (pkgs == null) {
- source.load(downloadCache2,
+ source.load(getDownloadCache(),
subMonitor.createSubMonitor(1),
forceHttp);
pkgs = source.getPackages();
@@ -215,8 +246,7 @@
}
/**
- * Load packages, source by source using
- * {@link #loadPackages(DownloadCache, ISourceLoadedCallback)},
+ * Load packages, source by source using {@link #loadPackages(ISourceLoadedCallback)},
* and executes the given {@link IAutoInstallTask} on the current package list.
* That is for each package known, the install task is queried to find if
* the package is the one to be installed or updated.
@@ -248,8 +278,7 @@
final int installFlags,
final IAutoInstallTask installTask) {
- loadPackages(mUpdaterData.getDownloadCache(),
- new ISourceLoadedCallback() {
+ loadPackages(new ISourceLoadedCallback() {
List<Archive> mArchivesToInstall = new ArrayList<Archive>();
Map<Package, File> mInstallPaths = new HashMap<Package, File>();
@@ -367,4 +396,77 @@
}
});
}
+
+
+ /**
+ * Loads the remote add-ons list.
+ */
+ public void loadRemoteAddonsList(ITaskMonitor monitor) {
+
+ if (mStateFetchRemoteAddonsList != 0) {
+ return;
+ }
+
+ mUpdaterData.getTaskFactory().start("Load Add-ons List", monitor, new ITask() {
+ @Override
+ public void run(ITaskMonitor subMonitor) {
+ loadRemoteAddonsListInTask(subMonitor);
+ }
+ });
+ }
+
+ private void loadRemoteAddonsListInTask(ITaskMonitor monitor) {
+ mStateFetchRemoteAddonsList = -1;
+
+ String url = SdkAddonsListConstants.URL_ADDON_LIST;
+
+ // We override SdkRepoConstants.URL_GOOGLE_SDK_SITE if this is defined
+ String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
+ if (baseUrl != null) {
+ if (baseUrl.length() > 0 && baseUrl.endsWith("/")) { //$NON-NLS-1$
+ if (url.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
+ url = baseUrl + url.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
+ }
+ } else {
+ monitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl); //$NON-NLS-1$
+ }
+ }
+
+ if (mUpdaterData.getSettingsController().getForceHttp()) {
+ url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // Hook to bypass loading 3rd party addons lists.
+ boolean fetch3rdParties = System.getenv("SDK_SKIP_3RD_PARTIES") == null;
+
+ AddonsListFetcher fetcher = new AddonsListFetcher();
+ Site[] sites = fetcher.fetch(url, getDownloadCache(), monitor);
+ if (sites != null) {
+ SdkSources sources = mUpdaterData.getSources();
+ sources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
+
+ if (fetch3rdParties) {
+ for (Site s : sites) {
+ sources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
+ new SdkAddonSource(s.getUrl(), s.getUiName()));
+ }
+ }
+
+ sources.notifyChangeListeners();
+
+ mStateFetchRemoteAddonsList = 1;
+ }
+
+ monitor.setDescription("Fetched Add-ons List successfully");
+ }
+
+ /**
+ * Returns the {@link DownloadCache} to use.
+ *
+ * @return Returns {@link #mOverrideCache} if not null; otherwise returns the
+ * one from {@link UpdaterData} is used instead.
+ */
+ private DownloadCache getDownloadCache() {
+ return mOverrideCache != null ? mOverrideCache : mUpdaterData.getDownloadCache();
+ }
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java
index 46b5697..9e45748 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java
@@ -20,8 +20,10 @@
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.IPackageVersion;
+import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
+import com.android.sdklib.internal.repository.packages.IFullRevisionProvider;
import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
import com.android.sdklib.internal.repository.packages.PlatformPackage;
import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
import com.android.sdklib.internal.repository.packages.SystemImagePackage;
@@ -46,17 +48,11 @@
* so that we can test it using head-less unit tests.
*/
class PackagesDiffLogic {
- private final PackageLoader mPackageLoader;
private final UpdaterData mUpdaterData;
private boolean mFirstLoadComplete = true;
public PackagesDiffLogic(UpdaterData updaterData) {
mUpdaterData = updaterData;
- mPackageLoader = new PackageLoader(updaterData);
- }
-
- public PackageLoader getPackageLoader() {
- return mPackageLoader;
}
/**
@@ -80,11 +76,12 @@
/**
* Mark all new and update PkgItems as checked.
*
- * @param selectNew If true, select all new packages
- * @param selectUpdates If true, select all update packages
- * @param selectTop If true, select the top platform. If the top platform has nothing installed,
- * select all items in it; if it is partially installed, at least select the platform and
- * system images if none of the system images are installed.
+ * @param selectNew If true, select all new packages (except the rc/preview ones).
+ * @param selectUpdates If true, select all update packages.
+ * @param selectTop If true, select the top platform.
+ * If the top platform has nothing installed, select all items in it (except the rc/preview);
+ * If it is partially installed, at least select the platform and system images if none of
+ * the system images are installed.
* @param currentPlatform The {@link SdkConstants#currentPlatform()} value.
*/
public void checkNewUpdateItems(
@@ -97,7 +94,8 @@
SparseArray<List<PkgItem>> platformItems = new SparseArray<List<PkgItem>>();
// sort items in platforms... directly deal with new/update items
- for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) {
+ List<PkgItem> allItems = getAllPkgItems(true /*byApi*/, true /*bySource*/);
+ for (PkgItem item : allItems) {
if (!item.hasCompatibleArchive()) {
// Ignore items that have no archive compatible with the current platform.
continue;
@@ -107,8 +105,8 @@
// since by definition they should target the same API level.
int api = 0;
Package p = item.getMainPackage();
- if (p instanceof IPackageVersion) {
- api = ((IPackageVersion) p).getVersion().getApiLevel();
+ if (p instanceof IAndroidVersionProvider) {
+ api = ((IAndroidVersionProvider) p).getAndroidVersion().getApiLevel();
}
if (selectTop && api > 0) {
@@ -129,8 +127,42 @@
items.add(item);
}
- if ((selectNew && item.getState() == PkgState.NEW) ||
- (selectUpdates && item.hasUpdatePkg())) {
+ if ((selectUpdates || selectNew) &&
+ item.getState() == PkgState.NEW &&
+ !item.getRevision().isPreview()) {
+ boolean sameFound = false;
+ Package newPkg = item.getMainPackage();
+ if (newPkg instanceof IFullRevisionProvider) {
+ // We have a potential new non-preview package; but this kind of package
+ // supports having previews, which means we want to make sure we're not
+ // offering an older "new" non-preview if there's a newer preview installed.
+ //
+ // We should get into this odd situation only when updating an RC/preview
+ // by a final release pkg.
+
+ IFullRevisionProvider newPkg2 = (IFullRevisionProvider) newPkg;
+ for (PkgItem item2 : allItems) {
+ if (item2.getState() == PkgState.INSTALLED) {
+ Package installed = item2.getMainPackage();
+
+ if (installed.getRevision().isPreview() &&
+ newPkg2.sameItemAs(installed, true /*ignorePreviews*/)) {
+ sameFound = true;
+
+ if (installed.canBeUpdatedBy(newPkg) == UpdateInfo.UPDATE) {
+ item.setChecked(true);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (selectNew && !sameFound) {
+ item.setChecked(true);
+ }
+
+ } else if (selectUpdates && item.hasUpdatePkg()) {
item.setChecked(true);
}
}
@@ -140,7 +172,8 @@
if (!installedPlatforms.contains(maxApi)) {
// If the top platform has nothing installed at all, select everything in it
for (PkgItem item : items) {
- if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
+ if ((item.getState() == PkgState.NEW && !item.getRevision().isPreview()) ||
+ item.hasUpdatePkg()) {
item.setChecked(true);
}
}
@@ -151,7 +184,8 @@
// First make sure the platform package itself is installed, or select it.
for (PkgItem item : items) {
Package p = item.getMainPackage();
- if (p instanceof PlatformPackage && item.getState() == PkgState.NEW) {
+ if (p instanceof PlatformPackage &&
+ item.getState() == PkgState.NEW && !item.getRevision().isPreview()) {
item.setChecked(true);
break;
}
@@ -163,7 +197,7 @@
Package p = item.getMainPackage();
if (p instanceof PlatformPackage && item.getState() == PkgState.INSTALLED) {
if (item.hasUpdatePkg() && item.isChecked()) {
- // If the installed platform is schedule for update, look for the
+ // If the installed platform is scheduled for update, look for the
// system image in the update package, not the current one.
p = item.getUpdatePkg();
if (p instanceof PlatformPackage) {
@@ -190,6 +224,7 @@
Package p = item.getMainPackage();
if (p instanceof PlatformPackage) {
if (item.getState() == PkgState.NEW &&
+ !item.getRevision().isPreview() &&
((PlatformPackage) p).getIncludedAbi() != null) {
item.setChecked(true);
hasSysImg = true;
@@ -220,7 +255,9 @@
// On Windows, we'll also auto-select the USB driver
for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) {
Package p = item.getMainPackage();
- if (p instanceof ExtraPackage && item.getState() == PkgState.NEW) {
+ if (p instanceof ExtraPackage &&
+ item.getState() == PkgState.NEW &&
+ !item.getRevision().isPreview()) {
ExtraPackage ep = (ExtraPackage) p;
if (ep.getVendorId().equals("google") && //$NON-NLS-1$
ep.getPath().equals("usb_driver")) { //$NON-NLS-1$
@@ -519,7 +556,7 @@
switch (currItem.getState()) {
case NEW:
- if (newPkg.getRevision() < mainPkg.getRevision()) {
+ if (newPkg.getRevision().compareTo(mainPkg.getRevision()) < 0) {
if (!op.isKeep(currItem)) {
// The new item has a lower revision than the current one,
// but the current one hasn't been marked as being kept so
@@ -528,7 +565,7 @@
addNewItem(op, newPkg, PkgState.NEW);
hasChanged = true;
}
- } else if (newPkg.getRevision() > mainPkg.getRevision()) {
+ } else if (newPkg.getRevision().compareTo(mainPkg.getRevision()) > 0) {
// We have a more recent new version, remove the current one
// and replace by a new one
currItemIt.remove();
@@ -538,7 +575,7 @@
break;
case INSTALLED:
// if newPkg.revision<=mainPkg.revision: it's already installed, ignore.
- if (newPkg.getRevision() > mainPkg.getRevision()) {
+ if (newPkg.getRevision().compareTo(mainPkg.getRevision()) > 0) {
// This is a new update for the main package.
if (currItem.mergeUpdate(newPkg)) {
op.keep(currItem.getUpdatePkg());
@@ -607,12 +644,15 @@
public Object getCategoryKey(Package pkg) {
// Sort by API
- if (pkg instanceof IPackageVersion) {
- return ((IPackageVersion) pkg).getVersion();
+ if (pkg instanceof IAndroidVersionProvider) {
+ return ((IAndroidVersionProvider) pkg).getAndroidVersion();
} else if (pkg instanceof ToolPackage || pkg instanceof PlatformToolPackage) {
- return PkgCategoryApi.KEY_TOOLS;
-
+ if (pkg.getRevision().isPreview()) {
+ return PkgCategoryApi.KEY_TOOLS_PREVIEW;
+ } else {
+ return PkgCategoryApi.KEY_TOOLS;
+ }
} else {
return PkgCategoryApi.KEY_EXTRA;
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java
index 4ef7d6b..40a3538 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java
@@ -17,6 +17,8 @@
package com.android.sdkuilib.internal.repository.sdkman2;
import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.sdklib.internal.repository.DownloadCache.Strategy;
import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
@@ -129,6 +131,7 @@
private final SdkInvocationContext mContext;
private final UpdaterData mUpdaterData;
private final PackagesDiffLogic mDiffLogic;
+
private boolean mDisplayArchives = false;
private boolean mOperationPending;
@@ -168,8 +171,15 @@
}
public void performFirstLoad() {
- // Initialize the package list the first time the page is shown.
- loadPackages(true /*isFirstLoad*/);
+ // First a package loader is created that only checks
+ // the local cache xml files. It populates the package
+ // list based on what the client got last, essentially.
+ loadPackages(true /*useLocalCache*/);
+
+ // Next a regular package loader is created that will
+ // respect the expiration and refresh parameters of the
+ // download cache.
+ loadPackages(false /*useLocalCache*/);
}
@SuppressWarnings("unused")
@@ -576,11 +586,24 @@
loadPackages();
}
+ /**
+ * Performs a "normal" reload of the package information, use the default download
+ * cache and refreshing strategy as needed.
+ */
private void loadPackages() {
- loadPackages(false /*isFirstLoad*/);
+ loadPackages(false /*useLocalCache*/);
}
- private void loadPackages(final boolean isFirstLoad) {
+ /**
+ * Performs a reload of the package information.
+ *
+ * @param useLocalCache When true, the {@link PackageLoader} is switched to use
+ * a specific {@link DownloadCache} using the {@link Strategy#ONLY_CACHE}, meaning
+ * it will only use data from the local cache. It will not try to fetch or refresh
+ * manifests. This is used once the very first time the sdk manager window opens
+ * and is typically followed by a regular load with refresh.
+ */
+ private void loadPackages(final boolean useLocalCache) {
if (mUpdaterData == null) {
return;
}
@@ -593,15 +616,26 @@
final boolean displaySortByApi = isSortByApi();
- if (!mTreeColumnName.isDisposed()) {
- mTreeColumnName.setImage(
- getImage(displaySortByApi ? ICON_SORT_BY_API : ICON_SORT_BY_SOURCE));
+ if (mTreeColumnName.isDisposed()) {
+ // If the UI got disposed, don't try to load anything since we won't be
+ // able to display it anyway.
+ return;
}
+ mTreeColumnName.setImage(getImage(displaySortByApi ? ICON_SORT_BY_API
+ : ICON_SORT_BY_SOURCE));
+
+ PackageLoader packageLoader = null;
+ if (useLocalCache) {
+ packageLoader =
+ new PackageLoader(mUpdaterData, new DownloadCache(Strategy.ONLY_CACHE));
+ } else {
+ packageLoader = mUpdaterData.getPackageLoader();
+ }
+ assert packageLoader != null;
+
mDiffLogic.updateStart();
- mDiffLogic.getPackageLoader().loadPackages(
- mUpdaterData.getDownloadCache(), // TODO do a first pass with Cache=SERVE_CACHE
- new ISourceLoadedCallback() {
+ packageLoader.loadPackages(new ISourceLoadedCallback() {
@Override
public boolean onUpdateSource(SdkSource source, Package[] newPackages) {
// This runs in a thread and must not access UI directly.
@@ -640,7 +674,9 @@
refreshViewerInput();
}
- if (mDiffLogic.isFirstLoadComplete() && !mGroupPackages.isDisposed()) {
+ if (!useLocalCache &&
+ mDiffLogic.isFirstLoadComplete() &&
+ !mGroupPackages.isDisposed()) {
// At the end of the first load, if nothing is selected then
// automatically select all new and update packages.
Object[] checked = mTreeViewer.getCheckedElements();
@@ -1313,7 +1349,7 @@
} else if (mColumn == mColumnRevision) {
if (element instanceof PkgItem) {
PkgItem pkg = (PkgItem) element;
- return Integer.toString(pkg.getRevision());
+ return pkg.getRevision().toShortString();
}
} else if (mColumn == mColumnStatus) {
@@ -1343,7 +1379,7 @@
} else if (element instanceof Package) {
// This is an update package.
- return "New revision " + Integer.toString(((Package) element).getRevision());
+ return "New revision " + ((Package) element).getRevision().toShortString();
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgCategoryApi.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgCategoryApi.java
index 5bb4917..d1e3a28 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgCategoryApi.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgCategoryApi.java
@@ -31,7 +31,9 @@
// them.
// (Note: don't use integer.max to avoid integers wrapping in comparisons. We can
// revisit the day we get 2^30 platforms.)
- public final static AndroidVersion KEY_TOOLS = new AndroidVersion(Integer.MAX_VALUE / 2, null);;
+ public final static AndroidVersion KEY_TOOLS = new AndroidVersion(Integer.MAX_VALUE / 2, null);
+ public final static AndroidVersion KEY_TOOLS_PREVIEW =
+ new AndroidVersion(Integer.MAX_VALUE / 2 - 1, null);
public final static AndroidVersion KEY_EXTRA = new AndroidVersion(-1, null);
public PkgCategoryApi(AndroidVersion version, String platformName, Object iconRef) {
@@ -55,6 +57,8 @@
AndroidVersion key = (AndroidVersion) getKey();
if (key.equals(KEY_TOOLS)) {
return "TOOLS"; //$NON-NLS-1$ // for internal debug use only
+ } else if (key.equals(KEY_TOOLS_PREVIEW)) {
+ return "TOOLS-PREVIEW"; //$NON-NLS-1$ // for internal debug use only
} else if (key.equals(KEY_EXTRA)) {
return "EXTRAS"; //$NON-NLS-1$ // for internal debug use only
} else {
@@ -70,6 +74,8 @@
if (key.equals(KEY_TOOLS)) {
label = "Tools";
+ } else if (key.equals(KEY_TOOLS_PREVIEW)) {
+ label = "Tools (Beta Channel)";
} else if (key.equals(KEY_EXTRA)) {
label = "Extras";
} else {
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java
index 5b90375..89910cd 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java
@@ -17,7 +17,8 @@
package com.android.sdkuilib.internal.repository.sdkman2;
import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.packages.IPackageVersion;
+import com.android.sdklib.internal.repository.packages.FullRevision;
+import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
import com.android.sdklib.internal.repository.sources.SdkSource;
@@ -89,7 +90,7 @@
return mMainPkg.getListDescription();
}
- public int getRevision() {
+ public FullRevision getRevision() {
return mMainPkg.getRevision();
}
@@ -110,8 +111,8 @@
}
public int getApi() {
- return mMainPkg instanceof IPackageVersion ?
- ((IPackageVersion) mMainPkg).getVersion().getApiLevel() :
+ return mMainPkg instanceof IAndroidVersionProvider ?
+ ((IAndroidVersionProvider) mMainPkg).getAndroidVersion().getApiLevel() :
-1;
}
@@ -149,24 +150,24 @@
/**
* Checks whether the main packages are of the same type and are
- * not an update of each other.
+ * not an update of each other and have the same revision number.
*/
public boolean isSameMainPackageAs(Package pkg) {
if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
// package revision numbers must match
- return mMainPkg.getRevision() == pkg.getRevision();
+ return mMainPkg.getRevision().equals(pkg.getRevision());
}
return false;
}
/**
* Checks whether the update packages are of the same type and are
- * not an update of each other.
+ * not an update of each other and have the same revision numbers.
*/
public boolean isSameUpdatePackageAs(Package pkg) {
if (mUpdatePkg != null && mUpdatePkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
// package revision numbers must match
- return mUpdatePkg.getRevision() == pkg.getRevision();
+ return mUpdatePkg.getRevision().equals(pkg.getRevision());
}
return false;
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java
index 8444f9f..3448852 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java
@@ -100,6 +100,13 @@
/**
* Starts the task and block till it's either finished or canceled.
* This can be called from a non-UI thread safely.
+ * <p/>
+ * When a task is started from within a monitor, it reuses the thread
+ * from the parent. Otherwise it starts a new thread and runs it own
+ * UI loop. This means the task can perform UI operations using
+ * {@link Display#asyncExec(Runnable)}.
+ * <p/>
+ * In either case, the method only returns when the task has finished.
*/
public void startTask(
final String title,
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java
index a328ef1..26b39ed 100755
--- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java
@@ -18,6 +18,7 @@
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.repository.packages.BrokenPackage;
+import com.android.sdklib.internal.repository.packages.FullRevision;
import com.android.sdklib.internal.repository.packages.MockAddonPackage;
import com.android.sdklib.internal.repository.packages.MockBrokenPackage;
import com.android.sdklib.internal.repository.packages.MockEmptyPackage;
@@ -1290,7 +1291,6 @@
}
public void testBrokenAddon() {
-
SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
SdkSource src2 = new SdkRepoSource("http://2.example.com/url2", "repo2");
@@ -1383,8 +1383,6 @@
}
public void testToolsUpdate() {
- //
-
SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
SdkSource src2 = new SdkRepoSource("http://2.example.com/url2", "repo2");
MockPlatformPackage p1;
@@ -1427,6 +1425,327 @@
"-- <NEW, pkg:The addon B from vendor 1, Android API 1, revision 6>\n",
getTree(m, false /*displaySortByApi*/));
}
+
+ public void testToolsMinorUpdate() {
+ // Test: Check a minor revision updates an installed major revision.
+
+ SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
+
+ m.updateStart();
+ m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+ new MockToolPackage(3, 3), // Tools 3.0.0
+ new MockPlatformToolPackage(src1, 3),
+ });
+ m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+ new MockToolPackage(src1, new FullRevision(3, 0, 1), 3), // Tools 3.0.1
+ });
+ m.updateEnd(true /*sortByApi*/);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+ assertEquals(
+ "PkgCategorySource <source=Local Packages (no.source), #items=1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+ "PkgCategorySource <source=repo1 (1.example.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n",
+ getTree(m, false /*displaySortByApi*/));
+ }
+
+ public void testToolsPreviews() {
+ // Test: No local tools installed. The remote server has both tools and platforms
+ // in release and RC versions.
+
+ SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
+
+ m.updateStart();
+ m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+ new MockToolPackage(src1, new FullRevision(2, 0, 0), 3), // Tools 2
+ new MockToolPackage(src1, new FullRevision(4, 0, 0, 1), 3), // Tools 4 rc1
+ new MockPlatformToolPackage(src1, new FullRevision(3, 0, 0)), // Plat-T 3
+ new MockPlatformToolPackage(src1, new FullRevision(5, 0, 0, 1)), // Plat-T 5 rc1
+ });
+ m.updateEnd(true /*sortByApi*/);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 2>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 5 rc1>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+ assertEquals(
+ "PkgCategorySource <source=repo1 (1.example.com), #items=4>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 2>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 5 rc1>\n",
+ getTree(m, false /*displaySortByApi*/));
+ }
+
+ public void testPreviewUpdateInstalledRelease() {
+ // Test: Local release Tools 3.0.0 installed, server has both a release 3.0.1 available
+ // and a Tools Preview 4.0.0 rc1 available.
+ // => v3 is updated by 3.0.1
+ // => v4.0.0rc1 does not update 3.0.0, instead it's a separate download.
+
+ SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
+
+ m.updateStart();
+ m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+ new MockToolPackage(3, 3), // tool package has no source defined
+ new MockPlatformToolPackage(src1, 3),
+ new MockPlatformPackage(src1, 1, 2, 3), // API 1
+ });
+ m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+ new MockToolPackage(src1, 3, 3), // Tools 3
+ new MockToolPackage(src1, new FullRevision(3, 0, 1), 3), // Tools 3.0.1
+ new MockToolPackage(src1, new FullRevision(4, 0, 0, 1), 3), // Tools 4 rc1
+ new MockPlatformToolPackage(src1, new FullRevision(3, 0, 1)), // PT 3.0.1
+ new MockPlatformToolPackage(src1, new FullRevision(4, 0, 0, 1)), // PT 4 rc1
+ });
+ m.updateEnd(true /*sortByApi*/);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3, updated by:Android SDK Platform-tools, revision 3.0.1>\n" +
+ "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4 rc1>\n" +
+ "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+ assertEquals(
+ "PkgCategorySource <source=Local Packages (no.source), #items=1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+ "PkgCategorySource <source=repo1 (1.example.com), #items=4>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3, updated by:Android SDK Platform-tools, revision 3.0.1>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4 rc1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+ getTree(m, false /*displaySortByApi*/));
+
+ // Now request to check new items and updates:
+ // Tools 4 rc1 is greater than the installed Tools 3, but it's a preview so we will NOT
+ // auto-select it by default even though we requested to select "NEW" packages. We
+ // want the user to manually opt-in into the rc/preview package.
+ // However Tools 3 has a 3.0.1 update that we'll auto-select.
+ m.checkNewUpdateItems(true, true, false, SdkConstants.PLATFORM_LINUX);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- < * INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+ "-- < * INSTALLED, pkg:Android SDK Platform-tools, revision 3, updated by:Android SDK Platform-tools, revision 3.0.1>\n" +
+ "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4 rc1>\n" +
+ "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+ assertEquals(
+ "PkgCategorySource <source=Local Packages (no.source), #items=1>\n" +
+ "-- < * INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+ "PkgCategorySource <source=repo1 (1.example.com), #items=4>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+ "-- < * INSTALLED, pkg:Android SDK Platform-tools, revision 3, updated by:Android SDK Platform-tools, revision 3.0.1>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4 rc1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+ getTree(m, false /*displaySortByApi*/));
+
+ }
+
+ public void testPreviewUpdateInstalledPreview() {
+ // Test: Local preview Tools 3.0.1rc1 installed, server has both a release 3.0.0 available
+ // and a Tools Preview 3.0.1 rc2 available.
+ // => Installed 3.0.1rc1 can be updated by 3.0.1rc2
+ // => There's a separate "new" download for 3.0.0, not installed and NOT updating 3.0.1rc1.
+
+ SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
+
+ m.updateStart();
+ m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, new FullRevision(3, 0, 1, 1), 4), // T 3.0.1rc1
+ new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1, 1)), // PT 4.0.1rc1
+ new MockPlatformPackage(src1, 1, 2, 3), // API 1
+ });
+ m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+ new MockToolPackage(src1, new FullRevision(3, 0, 0), 4), // T 3.0.0
+ new MockToolPackage(src1, new FullRevision(3, 0, 1, 2), 4), // T 3.0.1rc2
+ new MockPlatformToolPackage(src1, new FullRevision(4, 0, 0)), // PT 4.0.0
+ new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1, 2)), // PT 4.0.1 rc2
+ });
+ m.updateEnd(true /*sortByApi*/);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 3>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4>\n" +
+ "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1, updated by:Android SDK Tools, revision 3.0.1 rc2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1, updated by:Android SDK Platform-tools, revision 4.0.1 rc2>\n" +
+ "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+ assertEquals(
+ "PkgCategorySource <source=repo1 (1.example.com), #items=5>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 3>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1, updated by:Android SDK Tools, revision 3.0.1 rc2>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1, updated by:Android SDK Platform-tools, revision 4.0.1 rc2>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+ getTree(m, false /*displaySortByApi*/));
+
+ // Auto select new and update items. In this case:
+ // - the previews have updates available.
+ // - we're not selecting the non-installed "3.0" version that is older than the
+ // currently installed "3.0.1rc1" version since that would be a downgrade.
+ m.checkNewUpdateItems(true, true, false, SdkConstants.PLATFORM_LINUX);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 3>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4>\n" +
+ "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+ "-- < * INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1, updated by:Android SDK Tools, revision 3.0.1 rc2>\n" +
+ "-- < * INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1, updated by:Android SDK Platform-tools, revision 4.0.1 rc2>\n" +
+ "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+ assertEquals(
+ "PkgCategorySource <source=repo1 (1.example.com), #items=5>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 3>\n" +
+ "-- < * INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1, updated by:Android SDK Tools, revision 3.0.1 rc2>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4>\n" +
+ "-- < * INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1, updated by:Android SDK Platform-tools, revision 4.0.1 rc2>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+ getTree(m, false /*displaySortByApi*/));
+
+ // -----
+
+ // Now simulate that the server has a final package (3.0.1) to replace the
+ // installed 3.0.1rc1 package. It's not installed yet, just available.
+ // - A new 3.0.1 will be available.
+ // - The server no longer lists the RC since there's a final package, yet it is
+ // still locally installed.
+ // - The 3.0.1 rc1 is not listed as having an update, since we treat the previews
+ // separately. TODO: consider having the 3.0.1 show up as both a new item /and/
+ // as an update to the 3.0.1rc1. That may have some other side effects.
+
+ m.uncheckAllItems();
+ m.updateStart();
+ m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, new FullRevision(3, 0, 1, 1), 4), // T 3.0.1rc1
+ new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1, 1)), // PT 4.0.1rc1
+ new MockPlatformPackage(src1, 1, 2, 3), // API 1
+ });
+ m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+ new MockToolPackage(src1, new FullRevision(3, 0, 1), 4), // T 3.0.1
+ new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1)), // PT 4.0.1
+ });
+ m.updateEnd(true /*sortByApi*/);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 3.0.1>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+ "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1>\n" +
+ "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+ assertEquals(
+ "PkgCategorySource <source=repo1 (1.example.com), #items=5>\n" +
+ "-- <NEW, pkg:Android SDK Tools, revision 3.0.1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1>\n" +
+ "-- <NEW, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+ getTree(m, false /*displaySortByApi*/));
+
+ // Auto select new and update items. In this case the new items are considered
+ // updates and yet new at the same time.
+ // Test by selecting new items only:
+ m.checkNewUpdateItems(true, false, false, SdkConstants.PLATFORM_LINUX);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- < * NEW, pkg:Android SDK Tools, revision 3.0.1>\n" +
+ "-- < * NEW, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+ "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1>\n" +
+ "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+
+ // Test by selecting update items only:
+ m.uncheckAllItems();
+ m.checkNewUpdateItems(false, true, false, SdkConstants.PLATFORM_LINUX);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- < * NEW, pkg:Android SDK Tools, revision 3.0.1>\n" +
+ "-- < * NEW, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+ "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1>\n" +
+ "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+
+
+ // -----
+
+ // Now simulate that the user has installed the final package (3.0.1) to replace the
+ // installed 3.0.1rc1 package.
+ // - The 3.0.1 is installed.
+ // - The 3.0.1 rc1 isn't listed anymore by the server.
+
+ m.uncheckAllItems();
+ m.updateStart();
+ m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, new FullRevision(3, 0, 1), 4), // T 3.0.1
+ new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1)), // PT 4.0.1
+ new MockPlatformPackage(src1, 1, 2, 3), // API 1
+ });
+ m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+ new MockToolPackage(src1, new FullRevision(3, 0, 1), 4), // T 3.0.1
+ new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1)), // PT 4.0.1
+ });
+ m.updateEnd(true /*sortByApi*/);
+
+ assertEquals(
+ "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+ "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m, true /*displaySortByApi*/));
+ assertEquals(
+ "PkgCategorySource <source=repo1 (1.example.com), #items=3>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+ getTree(m, false /*displaySortByApi*/));
+ }
+
+
+
// ----
/**